@kynesyslabs/demosdk 4.0.10 → 4.0.12
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/build/abstraction/Identities.d.ts +70 -0
- package/build/abstraction/Identities.js +170 -2
- package/build/abstraction/Identities.js.map +1 -1
- package/build/identity/cci/claim.d.ts +47 -0
- package/build/identity/cci/claim.js +86 -0
- package/build/identity/cci/claim.js.map +1 -0
- package/build/identity/cci/index.d.ts +11 -0
- package/build/identity/cci/index.js +11 -0
- package/build/identity/cci/index.js.map +1 -0
- package/build/identity/cci/signing.d.ts +44 -0
- package/build/identity/cci/signing.js +83 -0
- package/build/identity/cci/signing.js.map +1 -0
- package/build/identity/cci/types.d.ts +14 -0
- package/build/identity/cci/types.js +10 -0
- package/build/identity/cci/types.js.map +1 -0
- package/build/identity/index.d.ts +2 -0
- package/build/identity/index.js +3 -0
- package/build/identity/index.js.map +1 -0
- package/build/l2ps/anchor/anchor.d.ts +92 -0
- package/build/l2ps/anchor/anchor.js +186 -0
- package/build/l2ps/anchor/anchor.js.map +1 -0
- package/build/l2ps/anchor/index.d.ts +10 -0
- package/build/l2ps/anchor/index.js +10 -0
- package/build/l2ps/anchor/index.js.map +1 -0
- package/build/l2ps/anchor/types.d.ts +36 -0
- package/build/l2ps/anchor/types.js +2 -0
- package/build/l2ps/anchor/types.js.map +1 -0
- package/build/l2ps/binding/binding.d.ts +72 -0
- package/build/l2ps/binding/binding.js +160 -0
- package/build/l2ps/binding/binding.js.map +1 -0
- package/build/l2ps/binding/canonical.d.ts +19 -0
- package/build/l2ps/binding/canonical.js +30 -0
- package/build/l2ps/binding/canonical.js.map +1 -0
- package/build/l2ps/binding/index.d.ts +10 -0
- package/build/l2ps/binding/index.js +10 -0
- package/build/l2ps/binding/index.js.map +1 -0
- package/build/l2ps/binding/types.d.ts +20 -0
- package/build/l2ps/binding/types.js +2 -0
- package/build/l2ps/binding/types.js.map +1 -0
- package/build/l2ps/channel/canonical.d.ts +24 -0
- package/build/l2ps/channel/canonical.js +44 -0
- package/build/l2ps/channel/canonical.js.map +1 -0
- package/build/l2ps/channel/channelIdRegistry.d.ts +19 -0
- package/build/l2ps/channel/channelIdRegistry.js +16 -0
- package/build/l2ps/channel/channelIdRegistry.js.map +1 -0
- package/build/l2ps/channel/envelope.d.ts +26 -0
- package/build/l2ps/channel/envelope.js +62 -0
- package/build/l2ps/channel/envelope.js.map +1 -0
- package/build/l2ps/channel/finalize.d.ts +63 -0
- package/build/l2ps/channel/finalize.js +85 -0
- package/build/l2ps/channel/finalize.js.map +1 -0
- package/build/l2ps/channel/index.d.ts +17 -0
- package/build/l2ps/channel/index.js +17 -0
- package/build/l2ps/channel/index.js.map +1 -0
- package/build/l2ps/channel/negotiate.d.ts +106 -0
- package/build/l2ps/channel/negotiate.js +206 -0
- package/build/l2ps/channel/negotiate.js.map +1 -0
- package/build/l2ps/channel/session.d.ts +66 -0
- package/build/l2ps/channel/session.js +101 -0
- package/build/l2ps/channel/session.js.map +1 -0
- package/build/l2ps/channel/transcript.d.ts +48 -0
- package/build/l2ps/channel/transcript.js +120 -0
- package/build/l2ps/channel/transcript.js.map +1 -0
- package/build/l2ps/channel/transport.d.ts +117 -0
- package/build/l2ps/channel/transport.js +178 -0
- package/build/l2ps/channel/transport.js.map +1 -0
- package/build/l2ps/channel/types.d.ts +55 -0
- package/build/l2ps/channel/types.js +2 -0
- package/build/l2ps/channel/types.js.map +1 -0
- package/build/l2ps/index.d.ts +5 -2
- package/build/l2ps/index.js +3 -0
- package/build/l2ps/index.js.map +1 -1
- package/build/l2ps/l2ps.d.ts +32 -0
- package/build/l2ps/l2ps.js +60 -0
- package/build/l2ps/l2ps.js.map +1 -1
- package/build/l2ps/utils/hex.d.ts +26 -0
- package/build/l2ps/utils/hex.js +45 -0
- package/build/l2ps/utils/hex.js.map +1 -0
- package/build/types/abstraction/index.d.ts +17 -2
- package/build/websdk/BroadcastFailedError.js.map +1 -1
- package/build/websdk/TransportError.js.map +1 -1
- package/build/websdk/Web2Calls.js +2 -0
- package/build/websdk/Web2Calls.js.map +1 -1
- package/build/websdk/demosclass.d.ts +15 -0
- package/build/websdk/demosclass.js +14 -0
- package/build/websdk/demosclass.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"finalize.js","sourceRoot":"","sources":["../../../../src/l2ps/channel/finalize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DACS-3 §8.3.3 / §8.7 — channel message envelope, session-level
|
|
3
|
+
* sequence + channelId enforcement, transcript export (SR-4 WI-2).
|
|
4
|
+
*
|
|
5
|
+
* All signatures use the sender's primary-claim key (on Demos: the
|
|
6
|
+
* connected Demos Ed25519 keypair) via WI-0's `signWithPrimaryClaim`;
|
|
7
|
+
* never the RSA L2PS subnet key.
|
|
8
|
+
*/
|
|
9
|
+
export type { ChannelMessage, ChannelMessageSignature, ChannelMessageType, ChannelTranscript, TranscriptSignature, UnsignedChannelMessage, UnsignedChannelTranscript, } from "./types";
|
|
10
|
+
export { CHANNEL_MESSAGE_DOMAIN_PREFIX, TRANSCRIPT_DOMAIN_PREFIX, channelMessageSigningBytes, envelopeHashHex, signatureFromHex, signatureToHex, stripChannelMessageSignature, stripTranscriptSignatures, transcriptHashHex, transcriptSigningBytes, } from "./canonical";
|
|
11
|
+
export { isSenderInMembers, signChannelMessage, verifyChannelMessage, } from "./envelope";
|
|
12
|
+
export { InMemoryChannelIdRegistry, type ChannelIdRegistry, } from "./channelIdRegistry";
|
|
13
|
+
export { ChannelSession, type ChannelSessionOpts } from "./session";
|
|
14
|
+
export { buildUnsignedTranscript, exportTranscript, signTranscript, verifyTranscript, type TranscriptVerificationResult, } from "./transcript";
|
|
15
|
+
export { L2PSChannelTransport, type L2PSChannelTransportOpts, type ChannelSessionLike, type L2PSMessagingPeerLike, type SerializedEncryptedMessage, type IncomingMessagePayload, } from "./transport";
|
|
16
|
+
export { RfqSession, type RfqState, type RfqOutcome, type RfqSessionOpts, type RfqProposalBody, type RfqAcceptBody, type RfqEndBody, type StandingProposal, } from "./negotiate";
|
|
17
|
+
export { finalizeRfq, type FinalizeRfqOpts, type RfqFinalizeResult, type RfqLike, type ChannelSessionView, } from "./finalize";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DACS-3 §8.3.3 / §8.7 — channel message envelope, session-level
|
|
3
|
+
* sequence + channelId enforcement, transcript export (SR-4 WI-2).
|
|
4
|
+
*
|
|
5
|
+
* All signatures use the sender's primary-claim key (on Demos: the
|
|
6
|
+
* connected Demos Ed25519 keypair) via WI-0's `signWithPrimaryClaim`;
|
|
7
|
+
* never the RSA L2PS subnet key.
|
|
8
|
+
*/
|
|
9
|
+
export { CHANNEL_MESSAGE_DOMAIN_PREFIX, TRANSCRIPT_DOMAIN_PREFIX, channelMessageSigningBytes, envelopeHashHex, signatureFromHex, signatureToHex, stripChannelMessageSignature, stripTranscriptSignatures, transcriptHashHex, transcriptSigningBytes, } from "./canonical.js";
|
|
10
|
+
export { isSenderInMembers, signChannelMessage, verifyChannelMessage, } from "./envelope.js";
|
|
11
|
+
export { InMemoryChannelIdRegistry, } from "./channelIdRegistry.js";
|
|
12
|
+
export { ChannelSession } from "./session.js";
|
|
13
|
+
export { buildUnsignedTranscript, exportTranscript, signTranscript, verifyTranscript, } from "./transcript.js";
|
|
14
|
+
export { L2PSChannelTransport, } from "./transport.js";
|
|
15
|
+
export { RfqSession, } from "./negotiate.js";
|
|
16
|
+
export { finalizeRfq, } from "./finalize.js";
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/l2ps/channel/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAYH,OAAO,EACH,6BAA6B,EAC7B,wBAAwB,EACxB,0BAA0B,EAC1B,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,4BAA4B,EAC5B,yBAAyB,EACzB,iBAAiB,EACjB,sBAAsB,GACzB,MAAM,aAAa,CAAA;AAEpB,OAAO,EACH,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,GACvB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACH,yBAAyB,GAE5B,MAAM,qBAAqB,CAAA;AAE5B,OAAO,EAAE,cAAc,EAA2B,MAAM,WAAW,CAAA;AAEnE,OAAO,EACH,uBAAuB,EACvB,gBAAgB,EAChB,cAAc,EACd,gBAAgB,GAEnB,MAAM,cAAc,CAAA;AAErB,OAAO,EACH,oBAAoB,GAMvB,MAAM,aAAa,CAAA;AAEpB,OAAO,EACH,UAAU,GAQb,MAAM,aAAa,CAAA;AAEpB,OAAO,EACH,WAAW,GAKd,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SR-4 WI-B — `negotiate-rfq` phase logic.
|
|
3
|
+
*
|
|
4
|
+
* A request-for-quote negotiation is a turn-based exchange of `offer` /
|
|
5
|
+
* `counter` proposals terminating in `accept`, `reject`, or `abort`
|
|
6
|
+
* (CH-5 termination; DACS-3 §8.3). Per the SR-4 brief the RFQ message
|
|
7
|
+
* *bodies* are implementation-defined (only sealed-envelope bodies are
|
|
8
|
+
* spec-locked, §8.4.3), so this layer fixes a minimal body schema and
|
|
9
|
+
* the state machine while staying agnostic to the actual `terms`.
|
|
10
|
+
*
|
|
11
|
+
* `RfqSession` is a pure protocol state machine. It does not move bytes:
|
|
12
|
+
* the caller wires its `send` callback to a transport (e.g.
|
|
13
|
+
* `L2PSChannelTransport.send`) and feeds verified inbound envelopes to
|
|
14
|
+
* `onIncoming`. By the time a message reaches `onIncoming` the channel
|
|
15
|
+
* layer has already verified the signature, `sender ∈ members`, the
|
|
16
|
+
* channelId, and monotonic sequence — so this layer only enforces
|
|
17
|
+
* protocol-level rules (legal transitions, body shape, referencing an
|
|
18
|
+
* existing offer).
|
|
19
|
+
*/
|
|
20
|
+
import type { ChannelMessage, ChannelMessageType } from "./types";
|
|
21
|
+
import type { ClaimReference } from "../../identity/cci";
|
|
22
|
+
/** Terminal + in-progress states (CH-5). */
|
|
23
|
+
export type RfqState = "open" | "accepted" | "rejected" | "aborted";
|
|
24
|
+
/** Body of an `offer` / `counter` — `terms` is opaque (impl-defined). */
|
|
25
|
+
export interface RfqProposalBody {
|
|
26
|
+
terms: unknown;
|
|
27
|
+
}
|
|
28
|
+
/** Body of an `accept` — references the proposal sequence being accepted. */
|
|
29
|
+
export interface RfqAcceptBody {
|
|
30
|
+
acceptedSequence: number;
|
|
31
|
+
}
|
|
32
|
+
/** Body of a `reject` / `abort`. */
|
|
33
|
+
export interface RfqEndBody {
|
|
34
|
+
reason?: string;
|
|
35
|
+
}
|
|
36
|
+
/** A standing proposal on the table. */
|
|
37
|
+
export interface StandingProposal {
|
|
38
|
+
sequence: number;
|
|
39
|
+
sender: ClaimReference;
|
|
40
|
+
terms: unknown;
|
|
41
|
+
}
|
|
42
|
+
export interface RfqOutcome {
|
|
43
|
+
state: RfqState;
|
|
44
|
+
/** Set when `state === "accepted"`: the terms that were agreed. */
|
|
45
|
+
agreedTerms?: unknown;
|
|
46
|
+
/** Sequence of the proposal that was accepted. */
|
|
47
|
+
acceptedSequence?: number;
|
|
48
|
+
/** Reason for a reject / abort, if given. */
|
|
49
|
+
reason?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface RfqSessionOpts {
|
|
52
|
+
/** This party's CCI primary claim. */
|
|
53
|
+
me: ClaimReference;
|
|
54
|
+
/**
|
|
55
|
+
* Delivers a signed channel message. Wire to the transport's send
|
|
56
|
+
* (e.g. `L2PSChannelTransport.send`). Returns the signed envelope so
|
|
57
|
+
* the session can track the sequence it produced.
|
|
58
|
+
*/
|
|
59
|
+
send: (opts: {
|
|
60
|
+
type: ChannelMessageType;
|
|
61
|
+
body: unknown;
|
|
62
|
+
repliesTo?: number;
|
|
63
|
+
}) => Promise<ChannelMessage>;
|
|
64
|
+
/** Fired on every state transition (including the terminal one). */
|
|
65
|
+
onStateChange?: (outcome: RfqOutcome) => void;
|
|
66
|
+
/** Fired when a fresh proposal (offer/counter) lands, ours or theirs. */
|
|
67
|
+
onProposal?: (proposal: StandingProposal) => void;
|
|
68
|
+
}
|
|
69
|
+
export declare class RfqSession {
|
|
70
|
+
private readonly me;
|
|
71
|
+
private readonly sendFn;
|
|
72
|
+
private readonly onStateChange?;
|
|
73
|
+
private readonly onProposal?;
|
|
74
|
+
private _state;
|
|
75
|
+
private _standing;
|
|
76
|
+
/** Every proposal seen, by sequence — so `accept` can resolve its terms. */
|
|
77
|
+
private readonly proposals;
|
|
78
|
+
private _outcome;
|
|
79
|
+
constructor(opts: RfqSessionOpts);
|
|
80
|
+
get state(): RfqState;
|
|
81
|
+
/** The proposal currently on the table, or null before the first offer. */
|
|
82
|
+
get standingProposal(): StandingProposal | null;
|
|
83
|
+
outcome(): RfqOutcome;
|
|
84
|
+
/** Open a negotiation with the first proposal. */
|
|
85
|
+
offer(terms: unknown): Promise<ChannelMessage>;
|
|
86
|
+
/** Counter the standing proposal with new terms. */
|
|
87
|
+
counter(terms: unknown): Promise<ChannelMessage>;
|
|
88
|
+
/** Accept the standing proposal — terminal (CH-5). */
|
|
89
|
+
accept(): Promise<ChannelMessage>;
|
|
90
|
+
/** Reject the negotiation — terminal (CH-5). */
|
|
91
|
+
reject(reason?: string): Promise<ChannelMessage>;
|
|
92
|
+
/** Abort the negotiation — terminal (CH-5). */
|
|
93
|
+
abort(reason?: string): Promise<ChannelMessage>;
|
|
94
|
+
/**
|
|
95
|
+
* Feed a verified inbound channel message. No-op for non-RFQ types or
|
|
96
|
+
* messages arriving after a terminal state (a late duplicate of an
|
|
97
|
+
* end message). Throws only on a protocol violation that the channel
|
|
98
|
+
* layer can't catch (e.g. accept referencing an unknown proposal).
|
|
99
|
+
*/
|
|
100
|
+
onIncoming(msg: ChannelMessage): void;
|
|
101
|
+
/** Record a verified inbound offer/counter as the standing proposal. */
|
|
102
|
+
private acceptProposal;
|
|
103
|
+
private proposeOutgoing;
|
|
104
|
+
private settle;
|
|
105
|
+
private assertOpen;
|
|
106
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SR-4 WI-B — `negotiate-rfq` phase logic.
|
|
3
|
+
*
|
|
4
|
+
* A request-for-quote negotiation is a turn-based exchange of `offer` /
|
|
5
|
+
* `counter` proposals terminating in `accept`, `reject`, or `abort`
|
|
6
|
+
* (CH-5 termination; DACS-3 §8.3). Per the SR-4 brief the RFQ message
|
|
7
|
+
* *bodies* are implementation-defined (only sealed-envelope bodies are
|
|
8
|
+
* spec-locked, §8.4.3), so this layer fixes a minimal body schema and
|
|
9
|
+
* the state machine while staying agnostic to the actual `terms`.
|
|
10
|
+
*
|
|
11
|
+
* `RfqSession` is a pure protocol state machine. It does not move bytes:
|
|
12
|
+
* the caller wires its `send` callback to a transport (e.g.
|
|
13
|
+
* `L2PSChannelTransport.send`) and feeds verified inbound envelopes to
|
|
14
|
+
* `onIncoming`. By the time a message reaches `onIncoming` the channel
|
|
15
|
+
* layer has already verified the signature, `sender ∈ members`, the
|
|
16
|
+
* channelId, and monotonic sequence — so this layer only enforces
|
|
17
|
+
* protocol-level rules (legal transitions, body shape, referencing an
|
|
18
|
+
* existing offer).
|
|
19
|
+
*/
|
|
20
|
+
const RFQ_TYPES = new Set([
|
|
21
|
+
"offer",
|
|
22
|
+
"counter",
|
|
23
|
+
"accept",
|
|
24
|
+
"reject",
|
|
25
|
+
"abort",
|
|
26
|
+
]);
|
|
27
|
+
export class RfqSession {
|
|
28
|
+
constructor(opts) {
|
|
29
|
+
this._state = "open";
|
|
30
|
+
this._standing = null;
|
|
31
|
+
/** Every proposal seen, by sequence — so `accept` can resolve its terms. */
|
|
32
|
+
this.proposals = new Map();
|
|
33
|
+
this._outcome = { state: "open" };
|
|
34
|
+
this.me = opts.me;
|
|
35
|
+
this.sendFn = opts.send;
|
|
36
|
+
this.onStateChange = opts.onStateChange;
|
|
37
|
+
this.onProposal = opts.onProposal;
|
|
38
|
+
}
|
|
39
|
+
get state() {
|
|
40
|
+
return this._state;
|
|
41
|
+
}
|
|
42
|
+
/** The proposal currently on the table, or null before the first offer. */
|
|
43
|
+
get standingProposal() {
|
|
44
|
+
return this._standing;
|
|
45
|
+
}
|
|
46
|
+
outcome() {
|
|
47
|
+
return this._outcome;
|
|
48
|
+
}
|
|
49
|
+
/** Open a negotiation with the first proposal. */
|
|
50
|
+
async offer(terms) {
|
|
51
|
+
this.assertOpen("offer");
|
|
52
|
+
if (this._standing)
|
|
53
|
+
throw new Error("RfqSession: a proposal already stands; use counter()");
|
|
54
|
+
return this.proposeOutgoing("offer", terms);
|
|
55
|
+
}
|
|
56
|
+
/** Counter the standing proposal with new terms. */
|
|
57
|
+
async counter(terms) {
|
|
58
|
+
this.assertOpen("counter");
|
|
59
|
+
if (!this._standing)
|
|
60
|
+
throw new Error("RfqSession: nothing to counter — no standing offer");
|
|
61
|
+
return this.proposeOutgoing("counter", terms, this._standing.sequence);
|
|
62
|
+
}
|
|
63
|
+
/** Accept the standing proposal — terminal (CH-5). */
|
|
64
|
+
async accept() {
|
|
65
|
+
this.assertOpen("accept");
|
|
66
|
+
if (!this._standing)
|
|
67
|
+
throw new Error("RfqSession: nothing to accept — no standing offer");
|
|
68
|
+
// You cannot accept your own proposal: acceptance is the
|
|
69
|
+
// counterparty agreeing to the terms on the table. Accepting a
|
|
70
|
+
// self-authored standing proposal would settle the negotiation
|
|
71
|
+
// (and produce a transcript) without the other side ever agreeing.
|
|
72
|
+
if (this._standing.sender === this.me)
|
|
73
|
+
throw new Error("RfqSession: cannot accept your own proposal — wait for the counterparty");
|
|
74
|
+
const accepted = this._standing;
|
|
75
|
+
const body = { acceptedSequence: accepted.sequence };
|
|
76
|
+
const msg = await this.sendFn({
|
|
77
|
+
type: "accept",
|
|
78
|
+
body,
|
|
79
|
+
repliesTo: accepted.sequence,
|
|
80
|
+
});
|
|
81
|
+
this.settle({
|
|
82
|
+
state: "accepted",
|
|
83
|
+
agreedTerms: accepted.terms,
|
|
84
|
+
acceptedSequence: accepted.sequence,
|
|
85
|
+
});
|
|
86
|
+
return msg;
|
|
87
|
+
}
|
|
88
|
+
/** Reject the negotiation — terminal (CH-5). */
|
|
89
|
+
async reject(reason) {
|
|
90
|
+
this.assertOpen("reject");
|
|
91
|
+
const body = reason ? { reason } : {};
|
|
92
|
+
const msg = await this.sendFn({ type: "reject", body });
|
|
93
|
+
this.settle({ state: "rejected", reason });
|
|
94
|
+
return msg;
|
|
95
|
+
}
|
|
96
|
+
/** Abort the negotiation — terminal (CH-5). */
|
|
97
|
+
async abort(reason) {
|
|
98
|
+
this.assertOpen("abort");
|
|
99
|
+
const body = reason ? { reason } : {};
|
|
100
|
+
const msg = await this.sendFn({ type: "abort", body });
|
|
101
|
+
this.settle({ state: "aborted", reason });
|
|
102
|
+
return msg;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Feed a verified inbound channel message. No-op for non-RFQ types or
|
|
106
|
+
* messages arriving after a terminal state (a late duplicate of an
|
|
107
|
+
* end message). Throws only on a protocol violation that the channel
|
|
108
|
+
* layer can't catch (e.g. accept referencing an unknown proposal).
|
|
109
|
+
*/
|
|
110
|
+
onIncoming(msg) {
|
|
111
|
+
if (!RFQ_TYPES.has(msg.type))
|
|
112
|
+
return;
|
|
113
|
+
if (msg.sender === this.me)
|
|
114
|
+
return; // our own echo, already applied
|
|
115
|
+
if (this._state !== "open")
|
|
116
|
+
return; // terminal — ignore trailing traffic
|
|
117
|
+
switch (msg.type) {
|
|
118
|
+
case "offer": {
|
|
119
|
+
// An offer opens a negotiation: it is only legal when no
|
|
120
|
+
// proposal stands yet. A second inbound offer must not
|
|
121
|
+
// overwrite the standing proposal — `offer()` rejects the
|
|
122
|
+
// same case outbound, so the inbound path enforces it too.
|
|
123
|
+
if (this._standing)
|
|
124
|
+
throw new Error(`RfqSession: inbound offer (seq ${msg.sequence}) but a proposal already stands (seq ${this._standing.sequence})`);
|
|
125
|
+
this.acceptProposal(msg);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case "counter": {
|
|
129
|
+
// A counter replaces the standing proposal — illegal before
|
|
130
|
+
// any offer exists, mirroring `counter()` outbound.
|
|
131
|
+
if (!this._standing)
|
|
132
|
+
throw new Error(`RfqSession: inbound counter (seq ${msg.sequence}) but no standing offer to counter`);
|
|
133
|
+
this.acceptProposal(msg);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case "accept": {
|
|
137
|
+
const seq = msg.body?.acceptedSequence;
|
|
138
|
+
const accepted = this.proposals.get(seq);
|
|
139
|
+
if (!accepted)
|
|
140
|
+
throw new Error(`RfqSession: accept references unknown proposal sequence ${seq}`);
|
|
141
|
+
// The accept must reference the proposal currently on the
|
|
142
|
+
// table. Once a counter supersedes an earlier offer, that
|
|
143
|
+
// older offer is off the table — accepting it would settle
|
|
144
|
+
// obsolete terms instead of what is currently being
|
|
145
|
+
// negotiated.
|
|
146
|
+
if (!this._standing || seq !== this._standing.sequence)
|
|
147
|
+
throw new Error(`RfqSession: accept references stale proposal seq ${seq}; standing proposal is seq ${this._standing?.sequence ?? "none"}`);
|
|
148
|
+
this.settle({
|
|
149
|
+
state: "accepted",
|
|
150
|
+
agreedTerms: accepted.terms,
|
|
151
|
+
acceptedSequence: seq,
|
|
152
|
+
});
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
case "reject": {
|
|
156
|
+
this.settle({
|
|
157
|
+
state: "rejected",
|
|
158
|
+
reason: msg.body?.reason,
|
|
159
|
+
});
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
case "abort": {
|
|
163
|
+
this.settle({
|
|
164
|
+
state: "aborted",
|
|
165
|
+
reason: msg.body?.reason,
|
|
166
|
+
});
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/** Record a verified inbound offer/counter as the standing proposal. */
|
|
172
|
+
acceptProposal(msg) {
|
|
173
|
+
const terms = msg.body?.terms;
|
|
174
|
+
const proposal = {
|
|
175
|
+
sequence: msg.sequence,
|
|
176
|
+
sender: msg.sender,
|
|
177
|
+
terms,
|
|
178
|
+
};
|
|
179
|
+
this.proposals.set(msg.sequence, proposal);
|
|
180
|
+
this._standing = proposal;
|
|
181
|
+
this.onProposal?.(proposal);
|
|
182
|
+
}
|
|
183
|
+
async proposeOutgoing(type, terms, repliesTo) {
|
|
184
|
+
const body = { terms };
|
|
185
|
+
const msg = await this.sendFn({ type, body, repliesTo });
|
|
186
|
+
const proposal = {
|
|
187
|
+
sequence: msg.sequence,
|
|
188
|
+
sender: this.me,
|
|
189
|
+
terms,
|
|
190
|
+
};
|
|
191
|
+
this.proposals.set(msg.sequence, proposal);
|
|
192
|
+
this._standing = proposal;
|
|
193
|
+
this.onProposal?.(proposal);
|
|
194
|
+
return msg;
|
|
195
|
+
}
|
|
196
|
+
settle(outcome) {
|
|
197
|
+
this._state = outcome.state;
|
|
198
|
+
this._outcome = outcome;
|
|
199
|
+
this.onStateChange?.(outcome);
|
|
200
|
+
}
|
|
201
|
+
assertOpen(action) {
|
|
202
|
+
if (this._state !== "open")
|
|
203
|
+
throw new Error(`RfqSession: cannot ${action} — negotiation is ${this._state}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=negotiate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negotiate.js","sourceRoot":"","sources":["../../../../src/l2ps/channel/negotiate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Demos } from "../../websdk/demosclass";
|
|
2
|
+
import type { ClaimReference } from "../../identity/cci";
|
|
3
|
+
import type { ChannelMessage, ChannelMessageType } from "./types";
|
|
4
|
+
import type { ChannelIdRegistry } from "./channelIdRegistry";
|
|
5
|
+
export interface ChannelSessionOpts {
|
|
6
|
+
channelId: string;
|
|
7
|
+
members: ClaimReference[];
|
|
8
|
+
/** This party's primary claim — must be in `members`. */
|
|
9
|
+
me: ClaimReference;
|
|
10
|
+
demos: Demos;
|
|
11
|
+
/**
|
|
12
|
+
* Optional CH-6 enforcement. If provided, `open()` registers the
|
|
13
|
+
* channelId here and throws on reuse.
|
|
14
|
+
*/
|
|
15
|
+
channelIdRegistry?: ChannelIdRegistry;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Per-channel session — owns the monotonic sequence counter and applies
|
|
19
|
+
* the four invariants from §8.3.3 / §8.12 on every message:
|
|
20
|
+
*
|
|
21
|
+
* 1. signature verifies under sender's CCI key
|
|
22
|
+
* 2. sender ∈ members (CH-1)
|
|
23
|
+
* 3. sequence is monotonic per channel (anti-replay)
|
|
24
|
+
* 4. channelId matches the session's channelId
|
|
25
|
+
*
|
|
26
|
+
* A single counter is shared across senders, as the brief specifies
|
|
27
|
+
* (`sequence: number // monotonic per channel, starts at 1`). Outgoing
|
|
28
|
+
* messages get `current + 1`; incoming messages must use a sequence
|
|
29
|
+
* strictly greater than every value already seen.
|
|
30
|
+
*/
|
|
31
|
+
export declare class ChannelSession {
|
|
32
|
+
readonly channelId: string;
|
|
33
|
+
readonly members: ReadonlyArray<ClaimReference>;
|
|
34
|
+
readonly me: ClaimReference;
|
|
35
|
+
private readonly demos;
|
|
36
|
+
private readonly registry;
|
|
37
|
+
private opened;
|
|
38
|
+
private highestSeen;
|
|
39
|
+
private readonly transcript;
|
|
40
|
+
constructor(opts: ChannelSessionOpts);
|
|
41
|
+
/**
|
|
42
|
+
* Register the channelId (CH-6) and prepare for messaging. Idempotent
|
|
43
|
+
* within a session — calling twice throws via the registry.
|
|
44
|
+
*/
|
|
45
|
+
open(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Build, sign, append-to-local-transcript, and return the next
|
|
48
|
+
* outgoing `ChannelMessage`. Sequence is `highestSeen + 1` so it is
|
|
49
|
+
* monotonic across both directions.
|
|
50
|
+
*/
|
|
51
|
+
sendOutgoing(opts: {
|
|
52
|
+
type: ChannelMessageType;
|
|
53
|
+
body: unknown;
|
|
54
|
+
sentAt?: number;
|
|
55
|
+
repliesTo?: number;
|
|
56
|
+
}): Promise<ChannelMessage>;
|
|
57
|
+
/**
|
|
58
|
+
* Validate an incoming `ChannelMessage`. Throws with a precise reason
|
|
59
|
+
* on any failure — caller should treat throw as channel-fatal per
|
|
60
|
+
* §8.12 (a single tampered message invalidates the session's record).
|
|
61
|
+
*/
|
|
62
|
+
receiveIncoming(msg: ChannelMessage): Promise<void>;
|
|
63
|
+
/** Read-only snapshot of all messages this session has signed or accepted. */
|
|
64
|
+
messages(): ReadonlyArray<ChannelMessage>;
|
|
65
|
+
private assertOpened;
|
|
66
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { isSenderInMembers, signChannelMessage, verifyChannelMessage, } from "./envelope.js";
|
|
2
|
+
/**
|
|
3
|
+
* Per-channel session — owns the monotonic sequence counter and applies
|
|
4
|
+
* the four invariants from §8.3.3 / §8.12 on every message:
|
|
5
|
+
*
|
|
6
|
+
* 1. signature verifies under sender's CCI key
|
|
7
|
+
* 2. sender ∈ members (CH-1)
|
|
8
|
+
* 3. sequence is monotonic per channel (anti-replay)
|
|
9
|
+
* 4. channelId matches the session's channelId
|
|
10
|
+
*
|
|
11
|
+
* A single counter is shared across senders, as the brief specifies
|
|
12
|
+
* (`sequence: number // monotonic per channel, starts at 1`). Outgoing
|
|
13
|
+
* messages get `current + 1`; incoming messages must use a sequence
|
|
14
|
+
* strictly greater than every value already seen.
|
|
15
|
+
*/
|
|
16
|
+
export class ChannelSession {
|
|
17
|
+
constructor(opts) {
|
|
18
|
+
this.opened = false;
|
|
19
|
+
this.highestSeen = 0;
|
|
20
|
+
this.transcript = [];
|
|
21
|
+
if (!opts.channelId)
|
|
22
|
+
throw new Error("ChannelSession: channelId required");
|
|
23
|
+
if (!opts.members?.length)
|
|
24
|
+
throw new Error("ChannelSession: members required");
|
|
25
|
+
if (!opts.members.includes(opts.me))
|
|
26
|
+
throw new Error(`ChannelSession: me (${opts.me}) is not in members`);
|
|
27
|
+
this.channelId = opts.channelId;
|
|
28
|
+
this.members = [...opts.members];
|
|
29
|
+
this.me = opts.me;
|
|
30
|
+
this.demos = opts.demos;
|
|
31
|
+
this.registry = opts.channelIdRegistry;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Register the channelId (CH-6) and prepare for messaging. Idempotent
|
|
35
|
+
* within a session — calling twice throws via the registry.
|
|
36
|
+
*/
|
|
37
|
+
async open() {
|
|
38
|
+
if (this.opened)
|
|
39
|
+
throw new Error("ChannelSession: already opened");
|
|
40
|
+
if (this.registry)
|
|
41
|
+
await this.registry.register(this.channelId);
|
|
42
|
+
this.opened = true;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Build, sign, append-to-local-transcript, and return the next
|
|
46
|
+
* outgoing `ChannelMessage`. Sequence is `highestSeen + 1` so it is
|
|
47
|
+
* monotonic across both directions.
|
|
48
|
+
*/
|
|
49
|
+
async sendOutgoing(opts) {
|
|
50
|
+
this.assertOpened();
|
|
51
|
+
// Reserve the sequence slot SYNCHRONOUSLY before any await — two
|
|
52
|
+
// concurrent sendOutgoing() calls would otherwise read the same
|
|
53
|
+
// `highestSeen`, sign two messages with identical sequence
|
|
54
|
+
// numbers, and silently violate the anti-replay invariant on the
|
|
55
|
+
// receiver side.
|
|
56
|
+
const sequence = ++this.highestSeen;
|
|
57
|
+
const unsigned = {
|
|
58
|
+
channelId: this.channelId,
|
|
59
|
+
sequence,
|
|
60
|
+
sender: this.me,
|
|
61
|
+
sentAt: opts.sentAt ?? Date.now(),
|
|
62
|
+
type: opts.type,
|
|
63
|
+
body: opts.body,
|
|
64
|
+
...(opts.repliesTo !== undefined && {
|
|
65
|
+
refs: { repliesTo: opts.repliesTo },
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
const signed = await signChannelMessage(unsigned, this.demos);
|
|
69
|
+
this.transcript.push(signed);
|
|
70
|
+
return signed;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Validate an incoming `ChannelMessage`. Throws with a precise reason
|
|
74
|
+
* on any failure — caller should treat throw as channel-fatal per
|
|
75
|
+
* §8.12 (a single tampered message invalidates the session's record).
|
|
76
|
+
*/
|
|
77
|
+
async receiveIncoming(msg) {
|
|
78
|
+
this.assertOpened();
|
|
79
|
+
if (msg.channelId !== this.channelId)
|
|
80
|
+
throw new Error(`ChannelSession: channelId mismatch (expected ${this.channelId}, got ${msg.channelId})`);
|
|
81
|
+
if (!isSenderInMembers(msg, this.members))
|
|
82
|
+
throw new Error(`ChannelSession: sender "${msg.sender}" not in members (CH-1)`);
|
|
83
|
+
if (!Number.isInteger(msg.sequence) || msg.sequence < 1)
|
|
84
|
+
throw new Error(`ChannelSession: invalid sequence ${msg.sequence}`);
|
|
85
|
+
if (msg.sequence <= this.highestSeen)
|
|
86
|
+
throw new Error(`ChannelSession: non-monotonic sequence ${msg.sequence} (highest seen ${this.highestSeen})`);
|
|
87
|
+
if (!verifyChannelMessage(msg))
|
|
88
|
+
throw new Error("ChannelSession: signature verification failed");
|
|
89
|
+
this.highestSeen = msg.sequence;
|
|
90
|
+
this.transcript.push(msg);
|
|
91
|
+
}
|
|
92
|
+
/** Read-only snapshot of all messages this session has signed or accepted. */
|
|
93
|
+
messages() {
|
|
94
|
+
return this.transcript;
|
|
95
|
+
}
|
|
96
|
+
assertOpened() {
|
|
97
|
+
if (!this.opened)
|
|
98
|
+
throw new Error("ChannelSession: call open() before sending or receiving");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../../../src/l2ps/channel/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type ClaimReference } from "../../identity/cci";
|
|
2
|
+
import { Demos } from "../../websdk/demosclass";
|
|
3
|
+
import type { ChannelMessage, ChannelTranscript, TranscriptSignature, UnsignedChannelTranscript } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* Build the unsigned transcript shell. Messages must already be in
|
|
6
|
+
* sequence order — typically `session.messages()`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildUnsignedTranscript(opts: {
|
|
9
|
+
channelId: string;
|
|
10
|
+
members: ClaimReference[];
|
|
11
|
+
messages: ReadonlyArray<ChannelMessage>;
|
|
12
|
+
generatedAt?: number;
|
|
13
|
+
}): UnsignedChannelTranscript;
|
|
14
|
+
/**
|
|
15
|
+
* Produce one party's `TranscriptSignature` over the unsigned transcript.
|
|
16
|
+
* Domain-separated under `dacs-transcript:v1:` — cannot be confused with
|
|
17
|
+
* a channel-message or binding signature.
|
|
18
|
+
*/
|
|
19
|
+
export declare function signTranscript(unsigned: UnsignedChannelTranscript, signer: ClaimReference, demos: Demos): Promise<TranscriptSignature>;
|
|
20
|
+
/**
|
|
21
|
+
* Convenience: assemble the full transcript with one or more party
|
|
22
|
+
* signatures. Pass signers in the order their signatures should appear.
|
|
23
|
+
*/
|
|
24
|
+
export declare function exportTranscript(opts: {
|
|
25
|
+
channelId: string;
|
|
26
|
+
members: ClaimReference[];
|
|
27
|
+
messages: ReadonlyArray<ChannelMessage>;
|
|
28
|
+
signers: Array<{
|
|
29
|
+
claim: ClaimReference;
|
|
30
|
+
demos: Demos;
|
|
31
|
+
}>;
|
|
32
|
+
generatedAt?: number;
|
|
33
|
+
}): Promise<ChannelTranscript>;
|
|
34
|
+
export interface TranscriptVerificationResult {
|
|
35
|
+
ok: boolean;
|
|
36
|
+
errors: string[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Verify every claim about a transcript a non-member can check given only
|
|
40
|
+
* the public keys: (a) each `TranscriptSignature` is valid, (b) each
|
|
41
|
+
* `ChannelMessage` carries a valid signature, (c) every message's sender
|
|
42
|
+
* is in `members`, (d) sequences are strictly monotonic per channel,
|
|
43
|
+
* (e) every message's `channelId` matches the transcript.
|
|
44
|
+
*
|
|
45
|
+
* Returns a list of errors rather than throwing so auditors can collect
|
|
46
|
+
* every failure in one pass.
|
|
47
|
+
*/
|
|
48
|
+
export declare function verifyTranscript(t: ChannelTranscript): TranscriptVerificationResult;
|