@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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export * from '../crypto.js';
|
|
2
|
+
export * from '../identity.js';
|
|
3
|
+
export * from '../encounter.js';
|
|
4
|
+
import type { SealEntropy, Envelope as Env } from '../delivery.js';
|
|
5
|
+
export declare const seal: (document: any, recipientKeyAgreement: string, ent?: SealEntropy) => Promise<Env>;
|
|
6
|
+
export { unseal } from '../delivery.js';
|
|
7
|
+
/**
|
|
8
|
+
* Every probe receiver enters through HERE (review 2, B-3): the probe
|
|
9
|
+
* hint relieves the probe TRANSPORT FORMS of wire stability — never the
|
|
10
|
+
* sealed envelope of its MUST checks. Runs the generic stages 1–4
|
|
11
|
+
* against the person's contexts and completed-effect cache.
|
|
12
|
+
*/
|
|
13
|
+
export declare function openEnvelope(p: Person, env: unknown): Promise<{
|
|
14
|
+
doc?: any;
|
|
15
|
+
digest?: string;
|
|
16
|
+
duplicate?: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* The probe's form gate (review 2, M-3): a valid signature or MAC binds
|
|
21
|
+
* whatever shape was signed — the FORM check is a separate, earlier gate.
|
|
22
|
+
* Field spec: 'string' | 'number' | 'boolean' | 'object' | 'array'.
|
|
23
|
+
*/
|
|
24
|
+
export declare const shaped: (o: any, fields: Record<string, "string" | "number" | "boolean" | "object" | "array">) => boolean;
|
|
25
|
+
/** Canonical decimal integer string: no leading zeros, >= 1, <= 18 digits — the form every sequence field carries. */
|
|
26
|
+
export declare const intStr: (v: any) => boolean;
|
|
27
|
+
/** Whole-second ISO timestamp in calendar-valid form. */
|
|
28
|
+
export { calOK } from '../core.js';
|
|
29
|
+
/** The cache contract: ONLY a completed effect's digest goes in — a rejection stays repeatable. */
|
|
30
|
+
export declare const effectDone: (p: Person, digest?: string) => void;
|
|
31
|
+
export type { Envelope } from '../delivery.js';
|
|
32
|
+
import type { Context } from '../identity.js';
|
|
33
|
+
/**
|
|
34
|
+
* The demo community's genesis digest — a PROBE FIXTURE, not protocol.
|
|
35
|
+
* A real deployment derives its community anchor from its own genesis;
|
|
36
|
+
* this constant exists so the probe world has one without ceremony.
|
|
37
|
+
*/
|
|
38
|
+
export declare const COMMUNITY_GENESIS = "uEiDYLnFbXqm2cwuJWuk9yNzRmlzWDpCTH6yA_4aP_1z_RA";
|
|
39
|
+
/** One person of the probe world: root secret, derived contexts, what they hold. */
|
|
40
|
+
export interface Person {
|
|
41
|
+
name: string;
|
|
42
|
+
rootIkm: Uint8Array;
|
|
43
|
+
/** anchor AND keyAgreement -> pair context (fresh-always) */
|
|
44
|
+
contexts: Map<string, Context>;
|
|
45
|
+
/** counterpart anchor -> { name, card, provenance, state, channel, … } */
|
|
46
|
+
contacts: Map<string, any>;
|
|
47
|
+
/** received offers/invites awaiting the human */
|
|
48
|
+
inbox: any[];
|
|
49
|
+
/** genesisDigest -> group state */
|
|
50
|
+
groups: Map<string, any>;
|
|
51
|
+
online: boolean;
|
|
52
|
+
/** actions deferred while offline (device ferry of ONESELF) */
|
|
53
|
+
queue: any[];
|
|
54
|
+
log: string[];
|
|
55
|
+
communityGenesis: string;
|
|
56
|
+
selfCtx?: Context;
|
|
57
|
+
starInfo?: any;
|
|
58
|
+
/** digests of COMPLETED effects (Delivery §4 stage 4) — rejections are never cached */
|
|
59
|
+
deliveryCache?: Set<string>;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=deps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../src/probe/deps.ts"],"names":[],"mappings":"AAQA,cAAc,cAAc,CAAA;AAC5B,cAAc,gBAAgB,CAAA;AAC9B,cAAc,iBAAiB,CAAA;AAE/B,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,IAAI,GAAG,EAAE,MAAM,gBAAgB,CAAA;AAIlE,eAAO,MAAM,IAAI,GAAI,UAAU,GAAG,EAAE,uBAAuB,MAAM,EAAE,MAAK,WAAgB,KAAG,OAAO,CAAC,GAAG,CACpD,CAAA;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AAGvC;;;;;GAKG;AACH,wBAAsB,YAAY,CAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,GAAG,CAAC,EAAE,GAAG,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAKzI;AAED;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,GAAG,GAAG,EAAE,QAAQ,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC,KAAG,OAItF,CAAA;AAExB,sHAAsH;AACtH,eAAO,MAAM,MAAM,GAAI,GAAG,GAAG,KAAG,OAAgE,CAAA;AAChG,yDAAyD;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAElC,mGAAmG;AACnG,eAAO,MAAM,UAAU,GAAI,GAAG,MAAM,EAAE,SAAS,MAAM,KAAG,IAAmE,CAAA;AAC3H,YAAY,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAE7C;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,oDAAoD,CAAA;AAElF,oFAAoF;AACpF,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,UAAU,CAAA;IACnB,6DAA6D;IAC7D,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,0EAA0E;IAC1E,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC1B,iDAAiD;IACjD,KAAK,EAAE,GAAG,EAAE,CAAA;IACZ,mCAAmC;IACnC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACxB,MAAM,EAAE,OAAO,CAAA;IACf,+DAA+D;IAC/D,KAAK,EAAE,GAAG,EAAE,CAAA;IACZ,GAAG,EAAE,MAAM,EAAE,CAAA;IACb,gBAAgB,EAAE,MAAM,CAAA;IACxB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,GAAG,CAAA;IACd,uFAAuF;IACvF,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAC5B"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// probe/deps — the bridge the probe modules stand on.
|
|
2
|
+
//
|
|
3
|
+
// The probe modules were written against the simulator's flat
|
|
4
|
+
// rltp-crypto surface; the library cut that surface along the spec
|
|
5
|
+
// layers. This module reassembles it — so the probe bodies stay
|
|
6
|
+
// byte-comparable to their .mjs originals, and the layer cut stays
|
|
7
|
+
// clean for everyone else. When a probe module graduates, it trades
|
|
8
|
+
// this bridge for direct layer imports.
|
|
9
|
+
export * from '../crypto.js';
|
|
10
|
+
export * from '../identity.js';
|
|
11
|
+
export * from '../encounter.js';
|
|
12
|
+
import { seal as sealStrict } from '../delivery.js';
|
|
13
|
+
// bridge: the probe's wire documents are structurally typed interfaces,
|
|
14
|
+
// which TypeScript will not index-match against Json — the strict layer
|
|
15
|
+
// keeps its Json edge, the probe pays the cast HERE, once
|
|
16
|
+
export const seal = (document, recipientKeyAgreement, ent = {}) => sealStrict(document, recipientKeyAgreement, ent);
|
|
17
|
+
export { unseal } from '../delivery.js';
|
|
18
|
+
import { receive } from '../delivery.js';
|
|
19
|
+
/**
|
|
20
|
+
* Every probe receiver enters through HERE (review 2, B-3): the probe
|
|
21
|
+
* hint relieves the probe TRANSPORT FORMS of wire stability — never the
|
|
22
|
+
* sealed envelope of its MUST checks. Runs the generic stages 1–4
|
|
23
|
+
* against the person's contexts and completed-effect cache.
|
|
24
|
+
*/
|
|
25
|
+
export async function openEnvelope(p, env) {
|
|
26
|
+
const r = await receive(env, (rkid) => p.contexts.get(rkid)?.x.priv, (p.deliveryCache ??= new Set()));
|
|
27
|
+
if (r.disposition === 'duplicate-known')
|
|
28
|
+
return { doc: r.document, digest: r.digest, duplicate: true };
|
|
29
|
+
if (r.disposition !== 'unique')
|
|
30
|
+
return { error: r.disposition };
|
|
31
|
+
return { doc: r.document, digest: r.digest };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The probe's form gate (review 2, M-3): a valid signature or MAC binds
|
|
35
|
+
* whatever shape was signed — the FORM check is a separate, earlier gate.
|
|
36
|
+
* Field spec: 'string' | 'number' | 'boolean' | 'object' | 'array'.
|
|
37
|
+
*/
|
|
38
|
+
export const shaped = (o, fields) => o !== null && typeof o === 'object' && !Array.isArray(o) && Object.entries(fields).every(([k, t]) => t === 'array' ? Array.isArray(o[k])
|
|
39
|
+
: t === 'object' ? (o[k] !== null && typeof o[k] === 'object' && !Array.isArray(o[k]))
|
|
40
|
+
: typeof o[k] === t);
|
|
41
|
+
/** Canonical decimal integer string: no leading zeros, >= 1, <= 18 digits — the form every sequence field carries. */
|
|
42
|
+
export const intStr = (v) => typeof v === 'string' && /^[1-9][0-9]{0,17}$/.test(v);
|
|
43
|
+
/** Whole-second ISO timestamp in calendar-valid form. */
|
|
44
|
+
export { calOK } from '../core.js';
|
|
45
|
+
/** The cache contract: ONLY a completed effect's digest goes in — a rejection stays repeatable. */
|
|
46
|
+
export const effectDone = (p, digest) => { if (digest)
|
|
47
|
+
(p.deliveryCache ??= new Set()).add(digest); };
|
|
48
|
+
/**
|
|
49
|
+
* The demo community's genesis digest — a PROBE FIXTURE, not protocol.
|
|
50
|
+
* A real deployment derives its community anchor from its own genesis;
|
|
51
|
+
* this constant exists so the probe world has one without ceremony.
|
|
52
|
+
*/
|
|
53
|
+
export const COMMUNITY_GENESIS = 'uEiDYLnFbXqm2cwuJWuk9yNzRmlzWDpCTH6yA_4aP_1z_RA';
|
|
54
|
+
//# sourceMappingURL=deps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deps.js","sourceRoot":"","sources":["../../src/probe/deps.ts"],"names":[],"mappings":"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","sourcesContent":["// probe/deps — the bridge the probe modules stand on.\n//\n// The probe modules were written against the simulator's flat\n// rltp-crypto surface; the library cut that surface along the spec\n// layers. This module reassembles it — so the probe bodies stay\n// byte-comparable to their .mjs originals, and the layer cut stays\n// clean for everyone else. When a probe module graduates, it trades\n// this bridge for direct layer imports.\nexport * from '../crypto.js'\nexport * from '../identity.js'\nexport * from '../encounter.js'\nimport { seal as sealStrict } from '../delivery.js'\nimport type { SealEntropy, Envelope as Env } from '../delivery.js'\n// bridge: the probe's wire documents are structurally typed interfaces,\n// which TypeScript will not index-match against Json — the strict layer\n// keeps its Json edge, the probe pays the cast HERE, once\nexport const seal = (document: any, recipientKeyAgreement: string, ent: SealEntropy = {}): Promise<Env> =>\n sealStrict(document, recipientKeyAgreement, ent)\nexport { unseal } from '../delivery.js'\nimport { receive } from '../delivery.js'\n\n/**\n * Every probe receiver enters through HERE (review 2, B-3): the probe\n * hint relieves the probe TRANSPORT FORMS of wire stability — never the\n * sealed envelope of its MUST checks. Runs the generic stages 1–4\n * against the person's contexts and completed-effect cache.\n */\nexport async function openEnvelope (p: Person, env: unknown): Promise<{ doc?: any, digest?: string, duplicate?: boolean, error?: string }> {\n const r = await receive(env, (rkid) => p.contexts.get(rkid)?.x.priv, (p.deliveryCache ??= new Set()))\n if (r.disposition === 'duplicate-known') return { doc: r.document, digest: r.digest, duplicate: true }\n if (r.disposition !== 'unique') return { error: r.disposition }\n return { doc: r.document, digest: r.digest }\n}\n\n/**\n * The probe's form gate (review 2, M-3): a valid signature or MAC binds\n * whatever shape was signed — the FORM check is a separate, earlier gate.\n * Field spec: 'string' | 'number' | 'boolean' | 'object' | 'array'.\n */\nexport const shaped = (o: any, fields: Record<string, 'string' | 'number' | 'boolean' | 'object' | 'array'>): boolean =>\n o !== null && typeof o === 'object' && !Array.isArray(o) && Object.entries(fields).every(([k, t]) =>\n t === 'array' ? Array.isArray(o[k])\n : t === 'object' ? (o[k] !== null && typeof o[k] === 'object' && !Array.isArray(o[k]))\n : typeof o[k] === t)\n\n/** Canonical decimal integer string: no leading zeros, >= 1, <= 18 digits — the form every sequence field carries. */\nexport const intStr = (v: any): boolean => typeof v === 'string' && /^[1-9][0-9]{0,17}$/.test(v)\n/** Whole-second ISO timestamp in calendar-valid form. */\nexport { calOK } from '../core.js'\n\n/** The cache contract: ONLY a completed effect's digest goes in — a rejection stays repeatable. */\nexport const effectDone = (p: Person, digest?: string): void => { if (digest) (p.deliveryCache ??= new Set()).add(digest) }\nexport type { Envelope } from '../delivery.js'\n\nimport type { Context } from '../identity.js'\n\n/**\n * The demo community's genesis digest — a PROBE FIXTURE, not protocol.\n * A real deployment derives its community anchor from its own genesis;\n * this constant exists so the probe world has one without ceremony.\n */\nexport const COMMUNITY_GENESIS = 'uEiDYLnFbXqm2cwuJWuk9yNzRmlzWDpCTH6yA_4aP_1z_RA'\n\n/** One person of the probe world: root secret, derived contexts, what they hold. */\nexport interface Person {\n name: string\n rootIkm: Uint8Array\n /** anchor AND keyAgreement -> pair context (fresh-always) */\n contexts: Map<string, Context>\n /** counterpart anchor -> { name, card, provenance, state, channel, … } */\n contacts: Map<string, any>\n /** received offers/invites awaiting the human */\n inbox: any[]\n /** genesisDigest -> group state */\n groups: Map<string, any>\n online: boolean\n /** actions deferred while offline (device ferry of ONESELF) */\n queue: any[]\n log: string[]\n communityGenesis: string\n selfCtx?: Context\n starInfo?: any\n /** digests of COMPLETED effects (Delivery §4 stage 4) — rejections are never cached */\n deliveryCache?: Set<string>\n}\n"]}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as C from './deps.js';
|
|
2
|
+
import type { Person } from './deps.js';
|
|
3
|
+
export declare function createPerson(name: string, rootIkm?: Uint8Array): Person;
|
|
4
|
+
export declare function freshCtx(p: Person, nonce?: Uint8Array): Promise<C.Context>;
|
|
5
|
+
export declare function ceremonyShow(p: Person, ent?: any): Promise<{
|
|
6
|
+
ctx: C.Context;
|
|
7
|
+
ch: string;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function ceremonyComplete(a: Person, sa: any, b: Person, sb: any, when: number): Promise<{
|
|
10
|
+
ctxA: any;
|
|
11
|
+
ctxB: any;
|
|
12
|
+
bind: string;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function ceremony(a: Person, b: Person, when: number, ent?: any): Promise<{
|
|
15
|
+
ctxA: any;
|
|
16
|
+
ctxB: any;
|
|
17
|
+
bind: string;
|
|
18
|
+
}>;
|
|
19
|
+
export declare function createDrop(): {
|
|
20
|
+
slots: Map<string, string>;
|
|
21
|
+
log: any[];
|
|
22
|
+
put(topic: string, blob: string, by: string): void;
|
|
23
|
+
get(topic: string, by: string): string | null;
|
|
24
|
+
};
|
|
25
|
+
export declare function introduce(m: Person, anchorA: string, anchorB: string, when: number, ent?: any): Promise<{
|
|
26
|
+
act: string;
|
|
27
|
+
offers: {
|
|
28
|
+
to: string;
|
|
29
|
+
env: C.Envelope;
|
|
30
|
+
}[];
|
|
31
|
+
}>;
|
|
32
|
+
export declare function receiveOffer(p: Person, env: any): Promise<{
|
|
33
|
+
entry: {
|
|
34
|
+
offer: any;
|
|
35
|
+
mediator: any;
|
|
36
|
+
mediatorAnchor: string;
|
|
37
|
+
decided: boolean;
|
|
38
|
+
};
|
|
39
|
+
error?: undefined;
|
|
40
|
+
} | {
|
|
41
|
+
duplicate: boolean;
|
|
42
|
+
error?: undefined;
|
|
43
|
+
} | {
|
|
44
|
+
error: string;
|
|
45
|
+
duplicate?: undefined;
|
|
46
|
+
}>;
|
|
47
|
+
export declare function consent(p: Person, entry: any, drop: any, when: number, ent?: any): Promise<{
|
|
48
|
+
queued: boolean;
|
|
49
|
+
ctx?: undefined;
|
|
50
|
+
mutual?: undefined;
|
|
51
|
+
} | {
|
|
52
|
+
ctx: C.Context;
|
|
53
|
+
mutual: boolean;
|
|
54
|
+
queued?: undefined;
|
|
55
|
+
}>;
|
|
56
|
+
export declare function checkDrop(p: Person, entry: any, drop: any): Promise<{
|
|
57
|
+
mutual: boolean;
|
|
58
|
+
error?: undefined;
|
|
59
|
+
card?: undefined;
|
|
60
|
+
} | {
|
|
61
|
+
error: string;
|
|
62
|
+
mutual?: undefined;
|
|
63
|
+
card?: undefined;
|
|
64
|
+
} | {
|
|
65
|
+
mutual: boolean;
|
|
66
|
+
card: any;
|
|
67
|
+
error?: undefined;
|
|
68
|
+
} | null>;
|
|
69
|
+
export declare function setOnline(p: Person, online: boolean, drop: any): Promise<void>;
|
|
70
|
+
//# sourceMappingURL=introduce.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"introduce.d.ts","sourceRoot":"","sources":["../../src/probe/introduce.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,CAAC,MAAM,WAAW,CAAA;AAC9B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAOvC,wBAAgB,YAAY,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,GAAG,MAAM,CAexE;AAED,wBAAsB,QAAQ,CAAE,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,UAAU,sBAI5D;AAMD,wBAAsB,YAAY,CAAE,CAAC,EAAE,MAAM,EAAE,GAAG,GAAE,GAAQ;;;GAG3D;AACD,wBAAsB,gBAAgB,CAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM;;;;GAc3F;AAED,wBAAsB,QAAQ,CAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,GAAQ;;;;GAGhF;AAGD,wBAAgB,UAAU;;SAGX,GAAG,EAAE;eACJ,MAAM,QAAQ,MAAM,MAAM,MAAM;eAChC,MAAM,MAAM,MAAM;EAEjC;AAwBD,wBAAsB,SAAS,CAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,GAAQ;;;;;;GAwBxG;AAGD,wBAAsB,YAAY,CAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG;;;;;;;;;;;;;;GAOtD;AA8BD,wBAAsB,OAAO,CAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,GAAQ;;;;;;;;GAqB3F;AAKD,wBAAsB,SAAS,CAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG;;;;;;;;;;;;UAkChE;AAGD,wBAAsB,SAAS,CAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,iBAQrE"}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// introduce.mjs — the introduction act, PROBE for the §8 recast.
|
|
2
|
+
// DOM-free, deterministic-capable, on rltp-core + rltp-crypto.
|
|
3
|
+
//
|
|
4
|
+
// THE MODEL (Anton's cuts, 25.08. — supersedes converged §8@1's
|
|
5
|
+
// requester-initiated ferry):
|
|
6
|
+
// 1. MEDIATOR-INITIATED: only M can point at both parties (a requester
|
|
7
|
+
// has no in-app handle for the target; the verbal wish lives in real
|
|
8
|
+
// life). M opens a contact, taps "Vorstellen…", picks the other.
|
|
9
|
+
// 2. INDEPENDENT RELEASE (ceremony-parallel): both sides receive the
|
|
10
|
+
// offer simultaneously; EACH consent releases that person's OWN
|
|
11
|
+
// fresh card — one-sided outcomes stay first-class (◇), mutuality
|
|
12
|
+
// arises when both have released (⇄).
|
|
13
|
+
// 3. RENDEZVOUS DROP, not ferry: the offers carry a per-act secret;
|
|
14
|
+
// consenting devices deposit their sealed card at a derived topic
|
|
15
|
+
// and fetch the counterpart's — THE MEDIATOR IS NEVER NEEDED AGAIN
|
|
16
|
+
// after the initiating act (airplane-mode scenario). The drop is a
|
|
17
|
+
// PORT (put/get by derived topic, opaque blobs, TTL); relay-topic /
|
|
18
|
+
// DHT / iroh node / M's device are adapters below it.
|
|
19
|
+
// 4. The mediator VOUCHER binds the ACT, not the card (the drop model
|
|
20
|
+
// makes card-digest vouchers impossible — M never sees the cards;
|
|
21
|
+
// harvest finding for the recast). Each offer carries the voucher
|
|
22
|
+
// FOR THE COUNTERPART, signed under M's channel anchor toward that
|
|
23
|
+
// counterpart; the consenting side deposits it beside its card, so
|
|
24
|
+
// the fetcher verifies "this introduction came from MY M".
|
|
25
|
+
// Trust: M remains the authenticity anchor of the introduction either
|
|
26
|
+
// way; the systemic control is the first real encounter (◇→✓,
|
|
27
|
+
// continuity). Deniability/linkability hardening is RECAST WORK — this
|
|
28
|
+
// probe signs plainly and says so.
|
|
29
|
+
import { jcs, makeValidator, tsec } from '../core.js';
|
|
30
|
+
import { SCHEMAS } from '../schemas.js';
|
|
31
|
+
const VAL = makeValidator(SCHEMAS);
|
|
32
|
+
import * as C from './deps.js';
|
|
33
|
+
import { snapshotPriors } from './continuity.js';
|
|
34
|
+
const te = new TextEncoder(), td = new TextDecoder();
|
|
35
|
+
const S = globalThis.crypto.subtle;
|
|
36
|
+
// ── people ──────────────────────────────────────────────────────────────
|
|
37
|
+
export function createPerson(name, rootIkm) {
|
|
38
|
+
return {
|
|
39
|
+
name,
|
|
40
|
+
rootIkm: rootIkm ?? C.rand(64),
|
|
41
|
+
contexts: new Map(), // anchor/keyAgreement -> pair ctx (fresh-always)
|
|
42
|
+
contacts: new Map(), // counterpart anchor -> { name, card, provenance, state, channel }
|
|
43
|
+
inbox: [], // received offers/invites awaiting the human
|
|
44
|
+
groups: new Map(), // genesisDigest -> group state (Stage C, groups.mjs)
|
|
45
|
+
online: true,
|
|
46
|
+
queue: [], // actions deferred while offline (device ferry of ONESELF)
|
|
47
|
+
log: [],
|
|
48
|
+
// the demo community's genesis — a PROBE FIXTURE the person carries,
|
|
49
|
+
// so the strict identity layer never needs a default
|
|
50
|
+
communityGenesis: C.COMMUNITY_GENESIS,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const say = (p, m) => p.log.push(m);
|
|
54
|
+
export async function freshCtx(p, nonce) {
|
|
55
|
+
const ctx = await C.pairContext(p.rootIkm, nonce ?? C.rand(32));
|
|
56
|
+
p.contexts.set(ctx.anchor, ctx);
|
|
57
|
+
p.contexts.set(ctx.keyAgreement, ctx);
|
|
58
|
+
return ctx;
|
|
59
|
+
}
|
|
60
|
+
// ── die Zeremonie in SCHRITTEN — die App-Choreografie (Encounter 5.2):
|
|
61
|
+
// Code zeigen → scannen → BEIDE bestätigen bewusst; erst dann entsteht
|
|
62
|
+
// das Enactment. Der gezeigte Anker ist immer frisch (4.4) und verrät
|
|
63
|
+
// nichts — wer ihn wirklich trifft, klärt hinterher die §6a-Probe.
|
|
64
|
+
export async function ceremonyShow(p, ent = {}) {
|
|
65
|
+
const ctx = await freshCtx(p, ent.nonce);
|
|
66
|
+
return { ctx, ch: C.challengeOf(ent.ch ?? C.rand(17)) };
|
|
67
|
+
}
|
|
68
|
+
export async function ceremonyComplete(a, sa, b, sb, when) {
|
|
69
|
+
const ctxA = sa.ctx, ctxB = sb.ctx, chA = sa.ch, chB = sb.ch;
|
|
70
|
+
const bind = await C.binding(C.CEREMONY, chA, chB);
|
|
71
|
+
const iso = C.iso(when);
|
|
72
|
+
const cardA = await C.signCard(ctxA, C.cardBody(ctxA, { name: a.name, challenge: { value: chA, issuedAt: iso } }), iso);
|
|
73
|
+
const cardB = await C.signCard(ctxB, C.cardBody(ctxB, { name: b.name, challenge: { value: chB, issuedAt: iso } }), iso);
|
|
74
|
+
const credA = await C.issueCredential(ctxA, ctxB.anchor, C.CEREMONY, chB, bind, iso);
|
|
75
|
+
const credB = await C.issueCredential(ctxB, ctxA.anchor, C.CEREMONY, chA, bind, iso);
|
|
76
|
+
const priorsA = snapshotPriors(a), priorsB = snapshotPriors(b); // Schnappschuss VOR dem frischen Tupel (6a)
|
|
77
|
+
a.contacts.set(ctxB.anchor, { name: b.name, card: cardB, credential: credA, provenance: 'ceremony', state: '✓', since: iso, channel: { own: ctxA, counterpartKa: cardB.keyAgreement }, priorCands: priorsA });
|
|
78
|
+
b.contacts.set(ctxA.anchor, { name: a.name, card: cardA, credential: credB, provenance: 'ceremony', state: '✓', since: iso, channel: { own: ctxB, counterpartKa: cardA.keyAgreement }, priorCands: priorsB });
|
|
79
|
+
say(a, `Zeremonie mit ${b.name}: ✓ verifiziert (frisches Tupel)`);
|
|
80
|
+
say(b, `Zeremonie mit ${a.name}: ✓ verifiziert (frisches Tupel)`);
|
|
81
|
+
return { ctxA, ctxB, bind };
|
|
82
|
+
}
|
|
83
|
+
// Test-Shortcut: beide Schritte in einem Zug (Vorgeschichte in Szenarien)
|
|
84
|
+
export async function ceremony(a, b, when, ent = {}) {
|
|
85
|
+
return ceremonyComplete(a, await ceremonyShow(a, { nonce: ent.nonceA, ch: ent.chA }), b, await ceremonyShow(b, { nonce: ent.nonceB, ch: ent.chB }), when);
|
|
86
|
+
}
|
|
87
|
+
// ── the rendezvous drop PORT: put/get by derived topic, opaque blobs ────
|
|
88
|
+
export function createDrop() {
|
|
89
|
+
return {
|
|
90
|
+
slots: new Map(), // topic -> b64u blob
|
|
91
|
+
log: [], // { n, op, topic, by }
|
|
92
|
+
put(topic, blob, by) { this.slots.set(topic, blob); this.log.push({ n: this.log.length, op: 'put', topic: topic.slice(0, 16) + '…', by }); },
|
|
93
|
+
get(topic, by) { this.log.push({ n: this.log.length, op: 'get', topic: topic.slice(0, 16) + '…', by }); return this.slots.get(topic) ?? null; },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const dropTopic = async (secret, dir) => C.b64uOf(await C.hkdf(secret, 'rltp/introduce/topic/' + dir));
|
|
97
|
+
const dropKey = async (secret, dir) => S.importKey('raw', await C.hkdf(secret, 'rltp/introduce/key/' + dir), { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']);
|
|
98
|
+
async function dropSeal(secret, dir, obj, nonce) {
|
|
99
|
+
const n = nonce ?? C.rand(12);
|
|
100
|
+
const ct = new Uint8Array(await S.encrypt({ name: 'AES-GCM', iv: n }, await dropKey(secret, dir), te.encode(jcs(obj))));
|
|
101
|
+
return C.b64uOf(C.cat(n, ct));
|
|
102
|
+
}
|
|
103
|
+
async function dropOpen(secret, dir, blob) {
|
|
104
|
+
try {
|
|
105
|
+
const raw = C.fromB64u(blob);
|
|
106
|
+
const pt = await S.decrypt({ name: 'AES-GCM', iv: raw.subarray(0, 12) }, await dropKey(secret, dir), raw.subarray(12));
|
|
107
|
+
return JSON.parse(td.decode(new Uint8Array(pt)));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// ── 1. the mediator's ONE act: "Vorstellen…" ────────────────────────────
|
|
114
|
+
// Produces two sealed offers over the EXISTING channels. After this, the
|
|
115
|
+
// mediator's device is done — nothing below ever touches it again.
|
|
116
|
+
// Introducible are ✓ AND ⇄ contacts (Anton's cut, 24.08.) — but the offer
|
|
117
|
+
// carries the mediator's OWN provenance toward the peer (met | introduced,
|
|
118
|
+
// the vouch@2 vocabulary): the recipient decides informed, and a chain of
|
|
119
|
+
// introductions never washes amber to blue — reach spreads, verification
|
|
120
|
+
// does not. ◇ cannot mediate: no own channel context exists.
|
|
121
|
+
export async function introduce(m, anchorA, anchorB, when, ent = {}) {
|
|
122
|
+
const A = m.contacts.get(anchorA), B = m.contacts.get(anchorB);
|
|
123
|
+
if (!A || !B)
|
|
124
|
+
throw new Error('mediator does not hold both contacts');
|
|
125
|
+
if (!A.channel?.own || !B.channel?.own)
|
|
126
|
+
throw new Error('introducing needs a mutual channel to BOTH (◇ cannot mediate)');
|
|
127
|
+
const secret = ent.secret ?? C.rand(32);
|
|
128
|
+
const act = await C.digestBytes(secret); // act id: public handle, secret stays sealed
|
|
129
|
+
const iso = C.iso(when);
|
|
130
|
+
const provOf = (c) => c.provenance === 'ceremony' ? 'met' : 'introduced';
|
|
131
|
+
const mk = async (mine, other, dir) => {
|
|
132
|
+
// voucher FOR the counterpart, under M's channel anchor toward the counterpart;
|
|
133
|
+
// it carries M's self-attested provenance toward the person it vouches for
|
|
134
|
+
const voucher = await C.diSign(other.channel.own, { type: 'introduction-voucher@2', act, direction: dir, provenance: provOf(mine), issuedAt: iso }, iso);
|
|
135
|
+
const offer = await C.diSign(mine.channel.own, {
|
|
136
|
+
type: 'introduction-offer@2', act, direction: dir, issuedAt: iso,
|
|
137
|
+
peer: { name: other.name, provenance: provOf(other) }, // M's LOCAL memory — words + honest basis, no anchors
|
|
138
|
+
rendezvous: C.b64uOf(secret),
|
|
139
|
+
voucherForCounterpart: voucher,
|
|
140
|
+
}, iso);
|
|
141
|
+
return C.seal(offer, mine.channel.counterpartKa, { ephSeed: ent.ephSeed, nonce: ent.sealNonce });
|
|
142
|
+
};
|
|
143
|
+
const envA = await mk(A, B, 'a');
|
|
144
|
+
const envB = await mk(B, A, 'b');
|
|
145
|
+
say(m, `Vorstellen: ${A.name} ⇠⇢ ${B.name} — zwei Angebote versiegelt, Akt ${act.slice(0, 12)}… · mein Gerät ist ab jetzt nicht mehr nötig`);
|
|
146
|
+
return { act, offers: [{ to: anchorA, env: envA }, { to: anchorB, env: envB }] };
|
|
147
|
+
}
|
|
148
|
+
// ── 2. receiving the offer (each side, independently) ───────────────────
|
|
149
|
+
export async function receiveOffer(p, env) {
|
|
150
|
+
const opened = await C.openEnvelope(p, env); // Stufen 1–4, Cache-Lesung
|
|
151
|
+
if (opened.duplicate)
|
|
152
|
+
return { duplicate: true }; // kein Fehler: der Effekt ist längst da
|
|
153
|
+
if (opened.error)
|
|
154
|
+
return { error: opened.error };
|
|
155
|
+
const r = await receiveOfferInner(p, env, opened.doc);
|
|
156
|
+
if (!r.error)
|
|
157
|
+
C.effectDone(p, opened.digest);
|
|
158
|
+
return r;
|
|
159
|
+
}
|
|
160
|
+
async function receiveOfferInner(p, env, offer) {
|
|
161
|
+
// form BEFORE fields (M-3; value forms per review 9, P-B1): the offer
|
|
162
|
+
// binds ITS artifact form before anything enters the inbox
|
|
163
|
+
if (!C.shaped(offer, { type: 'string', act: 'string', direction: 'string', rendezvous: 'string', issuedAt: 'string', peer: 'object', proof: 'object' })
|
|
164
|
+
|| offer.type !== 'introduction-offer@2'
|
|
165
|
+
|| (offer.direction !== 'a' && offer.direction !== 'b')
|
|
166
|
+
|| !C.calOK(offer.issuedAt)
|
|
167
|
+
|| !C.shaped(offer.peer, { name: 'string', provenance: 'string' })
|
|
168
|
+
|| (offer.peer.provenance !== 'met' && offer.peer.provenance !== 'introduced'))
|
|
169
|
+
return { error: 'malformed offer' };
|
|
170
|
+
try {
|
|
171
|
+
if (C.fromB64u(offer.rendezvous).length !== 32)
|
|
172
|
+
return { error: 'malformed offer' };
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return { error: 'malformed offer' };
|
|
176
|
+
}
|
|
177
|
+
// der mitreisende Voucher ist Teil der Offer-Form (Review 10, P-B1-Rest):
|
|
178
|
+
// er wird beim Consent unbesehen ins Drop-Paket übernommen
|
|
179
|
+
const v = offer.voucherForCounterpart;
|
|
180
|
+
if (!C.shaped(v, { type: 'string', act: 'string', direction: 'string', provenance: 'string', issuedAt: 'string', proof: 'object' })
|
|
181
|
+
|| v.type !== 'introduction-voucher@2' || v.act !== offer.act || !C.calOK(v.issuedAt))
|
|
182
|
+
return { error: 'malformed offer' };
|
|
183
|
+
const from = [...p.contacts.entries()].find(([anchor, c]) => c.channel && offer.proof?.verificationMethod?.startsWith(anchor));
|
|
184
|
+
if (!from)
|
|
185
|
+
return { error: 'offer not from a held contact' };
|
|
186
|
+
if (!(await C.diVerify(offer, from[0])))
|
|
187
|
+
return { error: 'offer proof fails' };
|
|
188
|
+
const entry = { offer, mediator: from[1].name, mediatorAnchor: from[0], decided: false };
|
|
189
|
+
p.inbox.push(entry);
|
|
190
|
+
say(p, `${from[1].name} möchte dich mit ${offer.peer.name} verbinden`);
|
|
191
|
+
return { entry };
|
|
192
|
+
}
|
|
193
|
+
// ── 3. consent: release the OWN card — independent of the other side.
|
|
194
|
+
// Releasing and receiving are SEPARATE (ceremony-parallel): consent only
|
|
195
|
+
// controls the own card; the counterpart's card arrives via sync
|
|
196
|
+
// (checkDrop) whether or not this side ever consents.
|
|
197
|
+
export async function consent(p, entry, drop, when, ent = {}) {
|
|
198
|
+
if (!p.online) {
|
|
199
|
+
p.queue.push(() => consent(p, entry, drop, when, ent));
|
|
200
|
+
say(p, 'offline — Freigabe wartet auf Netz');
|
|
201
|
+
return { queued: true };
|
|
202
|
+
}
|
|
203
|
+
const { offer } = entry;
|
|
204
|
+
const secret = C.fromB64u(offer.rendezvous);
|
|
205
|
+
const myDir = offer.direction;
|
|
206
|
+
const ctx = await freshCtx(p, ent.nonce);
|
|
207
|
+
const iso = C.iso(when);
|
|
208
|
+
const card = await C.signCard(ctx, C.cardBody(ctx, { name: p.name, challenge: { value: C.challengeOf(ent.ch ?? C.rand(17)), issuedAt: iso } }), iso);
|
|
209
|
+
drop.put(await dropTopic(secret, myDir), await dropSeal(secret, myDir, { card, voucher: offer.voucherForCounterpart }, ent.dropNonce), p.name);
|
|
210
|
+
entry.decided = true;
|
|
211
|
+
entry.released = true;
|
|
212
|
+
entry.ownCtx = ctx;
|
|
213
|
+
entry.ownCard = card;
|
|
214
|
+
say(p, `Verbinden mit ${offer.peer.name}: eigene Karte freigegeben (◇ gesendet)`);
|
|
215
|
+
// if the counterpart's card is already held, the relationship is mutual now
|
|
216
|
+
if (entry.counterpartAnchor) {
|
|
217
|
+
const c = p.contacts.get(entry.counterpartAnchor);
|
|
218
|
+
if (c) {
|
|
219
|
+
c.state = '⇄';
|
|
220
|
+
c.channel.own = ctx;
|
|
221
|
+
say(p, `⇄ verbunden mit ${c.name} — beide Karten liegen vor`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const fetched = await checkDrop(p, entry, drop);
|
|
225
|
+
return { ctx, mutual: !!(entry.released && entry.counterpartAnchor) };
|
|
226
|
+
}
|
|
227
|
+
// ── 4. sync: fetch the counterpart's card — PASSIVE receipt, independent
|
|
228
|
+
// of the own decision (like a shared contact arriving; ignoring the offer
|
|
229
|
+
// never blocks what the other side chose to release) ────────────────────
|
|
230
|
+
export async function checkDrop(p, entry, drop) {
|
|
231
|
+
if (!p.online || entry.done)
|
|
232
|
+
return null;
|
|
233
|
+
const { offer } = entry;
|
|
234
|
+
const secret = C.fromB64u(offer.rendezvous);
|
|
235
|
+
const otherDir = offer.direction === 'a' ? 'b' : 'a';
|
|
236
|
+
const blob = drop.get(await dropTopic(secret, otherDir), p.name);
|
|
237
|
+
if (!blob)
|
|
238
|
+
return { mutual: false };
|
|
239
|
+
const pkg = await dropOpen(secret, otherDir, blob);
|
|
240
|
+
if (!pkg) {
|
|
241
|
+
say(p, 'Drop-Blob unlesbar (falsches Geheimnis?)');
|
|
242
|
+
return { error: 'undecryptable' };
|
|
243
|
+
}
|
|
244
|
+
// form BEFORE fields (M-3), THEN the cryptography — and the card runs
|
|
245
|
+
// through its CLOSED wire-0.25 schema, not an ad-hoc field list
|
|
246
|
+
// (review 3, M-4: a valid signature binds a schema-violating card too)
|
|
247
|
+
if (!C.shaped(pkg, { card: 'object', voucher: 'object' })
|
|
248
|
+
|| !C.shaped(pkg.voucher, { act: 'string' })) {
|
|
249
|
+
say(p, 'Drop-Paket formwidrig');
|
|
250
|
+
return { error: 'malformed' };
|
|
251
|
+
}
|
|
252
|
+
{
|
|
253
|
+
const s = SCHEMAS['contact-card.schema.json'];
|
|
254
|
+
const errs = VAL.validate(pkg.card, s, s);
|
|
255
|
+
if (errs.length) {
|
|
256
|
+
say(p, 'Karte schema-widrig: ' + errs[0]);
|
|
257
|
+
return { error: 'card schema' };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// the counterpart card: schema + DI; the voucher: signed by MY mediator
|
|
261
|
+
if (!(await C.diVerify(pkg.card, pkg.card.anchor))) {
|
|
262
|
+
say(p, 'Karte verifiziert nicht');
|
|
263
|
+
return { error: 'card' };
|
|
264
|
+
}
|
|
265
|
+
if (!(await C.diVerify(pkg.voucher, entry.mediatorAnchor)) || pkg.voucher.act !== offer.act) {
|
|
266
|
+
say(p, 'Voucher verifiziert nicht unter meinem Vermittler');
|
|
267
|
+
return { error: 'voucher' };
|
|
268
|
+
}
|
|
269
|
+
entry.done = true;
|
|
270
|
+
entry.counterpartAnchor = pkg.card.anchor;
|
|
271
|
+
const mutual = !!entry.released;
|
|
272
|
+
const priors = snapshotPriors(p); // Schnappschuss VOR dem frischen Tupel (6a)
|
|
273
|
+
p.contacts.set(pkg.card.anchor, {
|
|
274
|
+
name: pkg.card.name, card: pkg.card, provenance: `introduced via ${entry.mediator}`, since: entry.offer.issuedAt,
|
|
275
|
+
mediatorProvenance: entry.offer.peer.provenance ?? 'met', priorCands: priors,
|
|
276
|
+
state: mutual ? '⇄' : '◇', channel: { own: entry.ownCtx ?? null, counterpartKa: pkg.card.keyAgreement },
|
|
277
|
+
voucher: pkg.voucher,
|
|
278
|
+
});
|
|
279
|
+
say(p, mutual ? `⇄ verbunden mit ${pkg.card.name} — via ${entry.mediator} (Voucher geprüft)` : `◇ ${pkg.card.name} erhalten — vorgestellt via ${entry.mediator} (einseitig; du hast nichts freigegeben)`);
|
|
280
|
+
return { mutual, card: pkg.card };
|
|
281
|
+
}
|
|
282
|
+
// ── device lifecycle ────────────────────────────────────────────────────
|
|
283
|
+
export async function setOnline(p, online, drop) {
|
|
284
|
+
p.online = online;
|
|
285
|
+
say(p, online ? 'online' : 'offline (Flugmodus)');
|
|
286
|
+
if (online) {
|
|
287
|
+
const q = p.queue.splice(0);
|
|
288
|
+
for (const fn of q)
|
|
289
|
+
await fn();
|
|
290
|
+
for (const e of p.inbox)
|
|
291
|
+
if (!e.kind)
|
|
292
|
+
await checkDrop(p, e, drop); // nur Vorstellungs-Angebote haben einen Drop
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=introduce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"introduce.js","sourceRoot":"","sources":["../../src/probe/introduce.ts"],"names":[],"mappings":"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","sourcesContent":["// introduce.mjs — the introduction act, PROBE for the §8 recast.\n// DOM-free, deterministic-capable, on rltp-core + rltp-crypto.\n//\n// THE MODEL (Anton's cuts, 25.08. — supersedes converged §8@1's\n// requester-initiated ferry):\n// 1. MEDIATOR-INITIATED: only M can point at both parties (a requester\n// has no in-app handle for the target; the verbal wish lives in real\n// life). M opens a contact, taps \"Vorstellen…\", picks the other.\n// 2. INDEPENDENT RELEASE (ceremony-parallel): both sides receive the\n// offer simultaneously; EACH consent releases that person's OWN\n// fresh card — one-sided outcomes stay first-class (◇), mutuality\n// arises when both have released (⇄).\n// 3. RENDEZVOUS DROP, not ferry: the offers carry a per-act secret;\n// consenting devices deposit their sealed card at a derived topic\n// and fetch the counterpart's — THE MEDIATOR IS NEVER NEEDED AGAIN\n// after the initiating act (airplane-mode scenario). The drop is a\n// PORT (put/get by derived topic, opaque blobs, TTL); relay-topic /\n// DHT / iroh node / M's device are adapters below it.\n// 4. The mediator VOUCHER binds the ACT, not the card (the drop model\n// makes card-digest vouchers impossible — M never sees the cards;\n// harvest finding for the recast). Each offer carries the voucher\n// FOR THE COUNTERPART, signed under M's channel anchor toward that\n// counterpart; the consenting side deposits it beside its card, so\n// the fetcher verifies \"this introduction came from MY M\".\n// Trust: M remains the authenticity anchor of the introduction either\n// way; the systemic control is the first real encounter (◇→✓,\n// continuity). Deniability/linkability hardening is RECAST WORK — this\n// probe signs plainly and says so.\nimport { jcs, makeValidator, tsec } from '../core.js'\nimport { SCHEMAS } from '../schemas.js'\nconst VAL = makeValidator(SCHEMAS)\nimport * as C from './deps.js'\nimport type { Person } from './deps.js'\nimport { snapshotPriors } from './continuity.js'\n\nconst te = new TextEncoder(), td = new TextDecoder()\nconst S = globalThis.crypto.subtle\n\n// ── people ──────────────────────────────────────────────────────────────\nexport function createPerson (name: string, rootIkm?: Uint8Array): Person {\n return {\n name,\n rootIkm: rootIkm ?? C.rand(64),\n contexts: new Map(), // anchor/keyAgreement -> pair ctx (fresh-always)\n contacts: new Map(), // counterpart anchor -> { name, card, provenance, state, channel }\n inbox: [], // received offers/invites awaiting the human\n groups: new Map(), // genesisDigest -> group state (Stage C, groups.mjs)\n online: true,\n queue: [], // actions deferred while offline (device ferry of ONESELF)\n log: [],\n // the demo community's genesis — a PROBE FIXTURE the person carries,\n // so the strict identity layer never needs a default\n communityGenesis: C.COMMUNITY_GENESIS,\n }\n}\nconst say = (p: Person, m: string) => p.log.push(m)\nexport async function freshCtx (p: Person, nonce?: Uint8Array) {\n const ctx = await C.pairContext(p.rootIkm, nonce ?? C.rand(32))\n p.contexts.set(ctx.anchor, ctx); p.contexts.set(ctx.keyAgreement, ctx)\n return ctx\n}\n\n// ── die Zeremonie in SCHRITTEN — die App-Choreografie (Encounter 5.2):\n// Code zeigen → scannen → BEIDE bestätigen bewusst; erst dann entsteht\n// das Enactment. Der gezeigte Anker ist immer frisch (4.4) und verrät\n// nichts — wer ihn wirklich trifft, klärt hinterher die §6a-Probe.\nexport async function ceremonyShow (p: Person, ent: any = {}) {\n const ctx = await freshCtx(p, ent.nonce)\n return { ctx, ch: C.challengeOf(ent.ch ?? C.rand(17)) }\n}\nexport async function ceremonyComplete (a: Person, sa: any, b: Person, sb: any, when: number) {\n const ctxA = sa.ctx, ctxB = sb.ctx, chA = sa.ch, chB = sb.ch\n const bind = await C.binding(C.CEREMONY, chA, chB)\n const iso = C.iso(when)\n const cardA = await C.signCard(ctxA, C.cardBody(ctxA, { name: a.name, challenge: { value: chA, issuedAt: iso } }), iso)\n const cardB = await C.signCard(ctxB, C.cardBody(ctxB, { name: b.name, challenge: { value: chB, issuedAt: iso } }), iso)\n const credA = await C.issueCredential(ctxA, ctxB.anchor, C.CEREMONY, chB, bind, iso)\n const credB = await C.issueCredential(ctxB, ctxA.anchor, C.CEREMONY, chA, bind, iso)\n const priorsA = snapshotPriors(a), priorsB = snapshotPriors(b) // Schnappschuss VOR dem frischen Tupel (6a)\n a.contacts.set(ctxB.anchor, { name: b.name, card: cardB, credential: credA, provenance: 'ceremony', state: '✓', since: iso, channel: { own: ctxA, counterpartKa: cardB.keyAgreement }, priorCands: priorsA })\n b.contacts.set(ctxA.anchor, { name: a.name, card: cardA, credential: credB, provenance: 'ceremony', state: '✓', since: iso, channel: { own: ctxB, counterpartKa: cardA.keyAgreement }, priorCands: priorsB })\n say(a, `Zeremonie mit ${b.name}: ✓ verifiziert (frisches Tupel)`)\n say(b, `Zeremonie mit ${a.name}: ✓ verifiziert (frisches Tupel)`)\n return { ctxA, ctxB, bind }\n}\n// Test-Shortcut: beide Schritte in einem Zug (Vorgeschichte in Szenarien)\nexport async function ceremony (a: Person, b: Person, when: number, ent: any = {}) {\n return ceremonyComplete(a, await ceremonyShow(a, { nonce: ent.nonceA, ch: ent.chA }),\n b, await ceremonyShow(b, { nonce: ent.nonceB, ch: ent.chB }), when)\n}\n\n// ── the rendezvous drop PORT: put/get by derived topic, opaque blobs ────\nexport function createDrop () {\n return {\n slots: new Map<string, string>(), // topic -> b64u blob\n log: [] as any[], // { n, op, topic, by }\n put (topic: string, blob: string, by: string) { this.slots.set(topic, blob); this.log.push({ n: this.log.length, op: 'put', topic: topic.slice(0, 16) + '…', by }) },\n get (topic: string, by: string) { this.log.push({ n: this.log.length, op: 'get', topic: topic.slice(0, 16) + '…', by }); return this.slots.get(topic) ?? null },\n }\n}\nconst dropTopic = async (secret: Uint8Array, dir: string) => C.b64uOf(await C.hkdf(secret, 'rltp/introduce/topic/' + dir))\nconst dropKey = async (secret: Uint8Array, dir: string) => S.importKey('raw', await C.hkdf(secret, 'rltp/introduce/key/' + dir), { name: 'AES-GCM' }, false, ['encrypt', 'decrypt'])\nasync function dropSeal (secret: Uint8Array, dir: string, obj: any, nonce: Uint8Array | undefined) {\n const n = nonce ?? C.rand(12)\n const ct = new Uint8Array(await S.encrypt({ name: 'AES-GCM', iv: n }, await dropKey(secret, dir), te.encode(jcs(obj))))\n return C.b64uOf(C.cat(n, ct))\n}\nasync function dropOpen (secret: Uint8Array, dir: string, blob: string) {\n try {\n const raw = C.fromB64u(blob)\n const pt = await S.decrypt({ name: 'AES-GCM', iv: raw.subarray(0, 12) }, await dropKey(secret, dir), raw.subarray(12))\n return JSON.parse(td.decode(new Uint8Array(pt)))\n } catch { return null }\n}\n\n// ── 1. the mediator's ONE act: \"Vorstellen…\" ────────────────────────────\n// Produces two sealed offers over the EXISTING channels. After this, the\n// mediator's device is done — nothing below ever touches it again.\n// Introducible are ✓ AND ⇄ contacts (Anton's cut, 24.08.) — but the offer\n// carries the mediator's OWN provenance toward the peer (met | introduced,\n// the vouch@2 vocabulary): the recipient decides informed, and a chain of\n// introductions never washes amber to blue — reach spreads, verification\n// does not. ◇ cannot mediate: no own channel context exists.\nexport async function introduce (m: Person, anchorA: string, anchorB: string, when: number, ent: any = {}) {\n const A = m.contacts.get(anchorA), B = m.contacts.get(anchorB)\n if (!A || !B) throw new Error('mediator does not hold both contacts')\n if (!A.channel?.own || !B.channel?.own) throw new Error('introducing needs a mutual channel to BOTH (◇ cannot mediate)')\n const secret = ent.secret ?? C.rand(32)\n const act = await C.digestBytes(secret) // act id: public handle, secret stays sealed\n const iso = C.iso(when)\n const provOf = (c: any) => c.provenance === 'ceremony' ? 'met' : 'introduced'\n const mk = async (mine: any, other: any, dir: string) => {\n // voucher FOR the counterpart, under M's channel anchor toward the counterpart;\n // it carries M's self-attested provenance toward the person it vouches for\n const voucher = await C.diSign(other.channel.own, { type: 'introduction-voucher@2', act, direction: dir, provenance: provOf(mine), issuedAt: iso }, iso)\n const offer = await C.diSign(mine.channel.own, {\n type: 'introduction-offer@2', act, direction: dir, issuedAt: iso,\n peer: { name: other.name, provenance: provOf(other) }, // M's LOCAL memory — words + honest basis, no anchors\n rendezvous: C.b64uOf(secret),\n voucherForCounterpart: voucher,\n }, iso)\n return C.seal(offer, mine.channel.counterpartKa, { ephSeed: ent.ephSeed, nonce: ent.sealNonce })\n }\n const envA = await mk(A, B, 'a')\n const envB = await mk(B, A, 'b')\n say(m, `Vorstellen: ${A.name} ⇠⇢ ${B.name} — zwei Angebote versiegelt, Akt ${act.slice(0, 12)}… · mein Gerät ist ab jetzt nicht mehr nötig`)\n return { act, offers: [{ to: anchorA, env: envA }, { to: anchorB, env: envB }] }\n}\n\n// ── 2. receiving the offer (each side, independently) ───────────────────\nexport async function receiveOffer (p: Person, env: any) {\n const opened = await C.openEnvelope(p, env) // Stufen 1–4, Cache-Lesung\n if (opened.duplicate) return { duplicate: true } // kein Fehler: der Effekt ist längst da\n if (opened.error) return { error: opened.error }\n const r = await receiveOfferInner(p, env, opened.doc)\n if (!(r as any).error) C.effectDone(p, opened.digest)\n return r\n}\nasync function receiveOfferInner (p: Person, env: any, offer: any) {\n // form BEFORE fields (M-3; value forms per review 9, P-B1): the offer\n // binds ITS artifact form before anything enters the inbox\n if (!C.shaped(offer, { type: 'string', act: 'string', direction: 'string', rendezvous: 'string', issuedAt: 'string', peer: 'object', proof: 'object' })\n || offer.type !== 'introduction-offer@2'\n || (offer.direction !== 'a' && offer.direction !== 'b')\n || !C.calOK(offer.issuedAt)\n || !C.shaped(offer.peer, { name: 'string', provenance: 'string' })\n || (offer.peer.provenance !== 'met' && offer.peer.provenance !== 'introduced')) return { error: 'malformed offer' }\n try { if (C.fromB64u(offer.rendezvous).length !== 32) return { error: 'malformed offer' } }\n catch { return { error: 'malformed offer' } }\n // der mitreisende Voucher ist Teil der Offer-Form (Review 10, P-B1-Rest):\n // er wird beim Consent unbesehen ins Drop-Paket übernommen\n const v = offer.voucherForCounterpart\n if (!C.shaped(v, { type: 'string', act: 'string', direction: 'string', provenance: 'string', issuedAt: 'string', proof: 'object' })\n || v.type !== 'introduction-voucher@2' || v.act !== offer.act || !C.calOK(v.issuedAt)) return { error: 'malformed offer' }\n const from = [...p.contacts.entries()].find(([anchor, c]) => c.channel && offer.proof?.verificationMethod?.startsWith(anchor))\n if (!from) return { error: 'offer not from a held contact' }\n if (!(await C.diVerify(offer, from[0]))) return { error: 'offer proof fails' }\n const entry = { offer, mediator: from[1].name, mediatorAnchor: from[0], decided: false }\n p.inbox.push(entry)\n say(p, `${from[1].name} möchte dich mit ${offer.peer.name} verbinden`)\n return { entry }\n}\n\n// ── 3. consent: release the OWN card — independent of the other side.\n// Releasing and receiving are SEPARATE (ceremony-parallel): consent only\n// controls the own card; the counterpart's card arrives via sync\n// (checkDrop) whether or not this side ever consents.\nexport async function consent (p: Person, entry: any, drop: any, when: number, ent: any = {}) {\n if (!p.online) { p.queue.push(() => consent(p, entry, drop, when, ent)); say(p, 'offline — Freigabe wartet auf Netz'); return { queued: true } }\n const { offer } = entry\n const secret = C.fromB64u(offer.rendezvous)\n const myDir = offer.direction\n const ctx = await freshCtx(p, ent.nonce)\n const iso = C.iso(when)\n const card = await C.signCard(ctx, C.cardBody(ctx, { name: p.name, challenge: { value: C.challengeOf(ent.ch ?? C.rand(17)), issuedAt: iso } }), iso)\n drop.put(await dropTopic(secret, myDir), await dropSeal(secret, myDir, { card, voucher: offer.voucherForCounterpart }, ent.dropNonce), p.name)\n entry.decided = true\n entry.released = true\n entry.ownCtx = ctx\n entry.ownCard = card\n say(p, `Verbinden mit ${offer.peer.name}: eigene Karte freigegeben (◇ gesendet)`)\n // if the counterpart's card is already held, the relationship is mutual now\n if (entry.counterpartAnchor) {\n const c = p.contacts.get(entry.counterpartAnchor)\n if (c) { c.state = '⇄'; c.channel.own = ctx; say(p, `⇄ verbunden mit ${c.name} — beide Karten liegen vor`) }\n }\n const fetched = await checkDrop(p, entry, drop)\n return { ctx, mutual: !!(entry.released && entry.counterpartAnchor) }\n}\n\n// ── 4. sync: fetch the counterpart's card — PASSIVE receipt, independent\n// of the own decision (like a shared contact arriving; ignoring the offer\n// never blocks what the other side chose to release) ────────────────────\nexport async function checkDrop (p: Person, entry: any, drop: any) {\n if (!p.online || entry.done) return null\n const { offer } = entry\n const secret = C.fromB64u(offer.rendezvous)\n const otherDir = offer.direction === 'a' ? 'b' : 'a'\n const blob = drop.get(await dropTopic(secret, otherDir), p.name)\n if (!blob) return { mutual: false }\n const pkg = await dropOpen(secret, otherDir, blob)\n if (!pkg) { say(p, 'Drop-Blob unlesbar (falsches Geheimnis?)'); return { error: 'undecryptable' } }\n // form BEFORE fields (M-3), THEN the cryptography — and the card runs\n // through its CLOSED wire-0.25 schema, not an ad-hoc field list\n // (review 3, M-4: a valid signature binds a schema-violating card too)\n if (!C.shaped(pkg, { card: 'object', voucher: 'object' })\n || !C.shaped(pkg.voucher, { act: 'string' })) { say(p, 'Drop-Paket formwidrig'); return { error: 'malformed' } }\n {\n const s = SCHEMAS['contact-card.schema.json']\n const errs = VAL.validate(pkg.card, s, s)\n if (errs.length) { say(p, 'Karte schema-widrig: ' + errs[0]); return { error: 'card schema' } }\n }\n // the counterpart card: schema + DI; the voucher: signed by MY mediator\n if (!(await C.diVerify(pkg.card, pkg.card.anchor))) { say(p, 'Karte verifiziert nicht'); return { error: 'card' } }\n if (!(await C.diVerify(pkg.voucher, entry.mediatorAnchor)) || pkg.voucher.act !== offer.act) { say(p, 'Voucher verifiziert nicht unter meinem Vermittler'); return { error: 'voucher' } }\n entry.done = true\n entry.counterpartAnchor = pkg.card.anchor\n const mutual = !!entry.released\n const priors = snapshotPriors(p) // Schnappschuss VOR dem frischen Tupel (6a)\n p.contacts.set(pkg.card.anchor, {\n name: pkg.card.name, card: pkg.card, provenance: `introduced via ${entry.mediator}`, since: entry.offer.issuedAt,\n mediatorProvenance: entry.offer.peer.provenance ?? 'met', priorCands: priors,\n state: mutual ? '⇄' : '◇', channel: { own: entry.ownCtx ?? null, counterpartKa: pkg.card.keyAgreement },\n voucher: pkg.voucher,\n })\n say(p, mutual ? `⇄ verbunden mit ${pkg.card.name} — via ${entry.mediator} (Voucher geprüft)` : `◇ ${pkg.card.name} erhalten — vorgestellt via ${entry.mediator} (einseitig; du hast nichts freigegeben)`)\n return { mutual, card: pkg.card }\n}\n\n// ── device lifecycle ────────────────────────────────────────────────────\nexport async function setOnline (p: Person, online: boolean, drop: any) {\n p.online = online\n say(p, online ? 'online' : 'offline (Flugmodus)')\n if (online) {\n const q = p.queue.splice(0)\n for (const fn of q) await fn()\n for (const e of p.inbox) if (!e.kind) await checkDrop(p, e, drop) // nur Vorstellungs-Angebote haben einen Drop\n }\n}\n"]}
|