@kynesyslabs/demosdk 4.0.9 → 4.0.11
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 +160 -0
- 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/index.d.ts +14 -0
- package/build/l2ps/channel/index.js +14 -0
- package/build/l2ps/channel/index.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/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/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 +25 -0
- package/build/websdk/demosclass.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { demosAddressFromClaim, isDemosClaim, normalizeDemosAddress, signWithPrimaryClaim, verifyPrimaryClaimSignature, } from "../../identity/cci/index.js";
|
|
2
|
+
import { DemosTransactions } from "../../websdk/DemosTransactions.js";
|
|
3
|
+
import { StorageProgram } from "../../storage/StorageProgram.js";
|
|
4
|
+
import { bindingSigningBytes, signatureFromHex, signatureToHex, stripBindingSignature, } from "./canonical.js";
|
|
5
|
+
/**
|
|
6
|
+
* Deterministic Storage Program name for `(channelId, subnetMemberId)`.
|
|
7
|
+
* Public so `resolveMember` can re-derive it; included verbatim in tests.
|
|
8
|
+
*
|
|
9
|
+
* Components are percent-encoded so the layout stays one-to-one. Without
|
|
10
|
+
* this guard, the pairs `("a:b", "c")` and `("a", "b:c")` would collide on
|
|
11
|
+
* the same SP name and `resolveMember` could be tricked into returning a
|
|
12
|
+
* binding from one channel/member context for a lookup of another.
|
|
13
|
+
*/
|
|
14
|
+
export function bindingProgramName(channelId, subnetMemberId) {
|
|
15
|
+
return `l2ps-binding:${encodeURIComponent(channelId)}:${encodeURIComponent(subnetMemberId)}`;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Brief calls subnetMemberId "the member's L2PS/RSA identity within the
|
|
19
|
+
* subnet". L2PS already publishes a stable fingerprint of the RSA key —
|
|
20
|
+
* we reuse that so the value is derivable on both sides without a separate
|
|
21
|
+
* registry.
|
|
22
|
+
*/
|
|
23
|
+
export async function subnetMemberIdFromL2PS(l2ps) {
|
|
24
|
+
return await l2ps.getKeyFingerprint();
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Build and sign a binding. The signature is produced by the key
|
|
28
|
+
* controlling `claim`. Throws if `claim`'s scheme is not `demos:` or if
|
|
29
|
+
* the controlling address does not match the connected wallet — the
|
|
30
|
+
* invariant that ties channel signatures to on-chain identity.
|
|
31
|
+
*/
|
|
32
|
+
export async function createMembershipBinding(opts) {
|
|
33
|
+
const { channelId, subnetMemberId, claim, demos } = opts;
|
|
34
|
+
if (!channelId)
|
|
35
|
+
throw new Error("createMembershipBinding: channelId required");
|
|
36
|
+
if (!subnetMemberId)
|
|
37
|
+
throw new Error("createMembershipBinding: subnetMemberId required");
|
|
38
|
+
if (!isDemosClaim(claim))
|
|
39
|
+
throw new Error(`createMembershipBinding: claim must be a "demos:..." ClaimReference, got "${claim}"`);
|
|
40
|
+
const boundAt = opts.boundAt ?? Date.now();
|
|
41
|
+
if (!Number.isSafeInteger(boundAt) || boundAt < 0)
|
|
42
|
+
throw new Error("createMembershipBinding: boundAt must be a non-negative safe-integer unix-ms timestamp");
|
|
43
|
+
const unsigned = {
|
|
44
|
+
bindingVersion: "1",
|
|
45
|
+
channelId,
|
|
46
|
+
subnetMemberId,
|
|
47
|
+
cciPrimaryClaim: claim,
|
|
48
|
+
boundAt,
|
|
49
|
+
};
|
|
50
|
+
const payload = bindingSigningBytes(unsigned);
|
|
51
|
+
const sig = await signWithPrimaryClaim(claim, payload, demos);
|
|
52
|
+
return { ...unsigned, signature: signatureToHex(sig) };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Pure signature check — no chain access. Verifies the embedded signature
|
|
56
|
+
* against the Demos public key encoded in `cciPrimaryClaim`.
|
|
57
|
+
*
|
|
58
|
+
* Returns `false` (not throws) on any structural problem so callers can
|
|
59
|
+
* use it as a filter when iterating SP candidates in `resolveMember`.
|
|
60
|
+
*/
|
|
61
|
+
export function verifyMembershipBinding(binding) {
|
|
62
|
+
if (binding?.bindingVersion !== "1")
|
|
63
|
+
return false;
|
|
64
|
+
if (!binding.channelId || !binding.subnetMemberId)
|
|
65
|
+
return false;
|
|
66
|
+
if (!isDemosClaim(binding.cciPrimaryClaim))
|
|
67
|
+
return false;
|
|
68
|
+
if (!Number.isSafeInteger(binding.boundAt) ||
|
|
69
|
+
binding.boundAt < 0)
|
|
70
|
+
return false;
|
|
71
|
+
if (typeof binding.signature !== "string" || !binding.signature)
|
|
72
|
+
return false;
|
|
73
|
+
try {
|
|
74
|
+
const payload = bindingSigningBytes(stripBindingSignature(binding));
|
|
75
|
+
const sig = signatureFromHex(binding.signature);
|
|
76
|
+
return verifyPrimaryClaimSignature(binding.cciPrimaryClaim, payload, sig);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Anchor a signed binding to chain via SR-2 (Storage Program). The SP is
|
|
84
|
+
* deployed under `bindingProgramName(...)` so `resolveMember` can find it
|
|
85
|
+
* by name. The deployer (= connected Demos wallet) becomes the SP owner;
|
|
86
|
+
* that owner check is what makes the resolver safe against impostor SPs
|
|
87
|
+
* published under the same name.
|
|
88
|
+
*
|
|
89
|
+
* Returns once the broadcast succeeds. Callers that need on-chain finality
|
|
90
|
+
* should poll the returned tx hash before opening the channel.
|
|
91
|
+
*/
|
|
92
|
+
export async function anchorMembershipBinding(binding, demos) {
|
|
93
|
+
if (!verifyMembershipBinding(binding))
|
|
94
|
+
throw new Error("anchorMembershipBinding: refusing to anchor an unverifiable binding");
|
|
95
|
+
const claimAddress = demosAddressFromClaim(binding.cciPrimaryClaim);
|
|
96
|
+
const connected = normalizeDemosAddress(await demos.getEd25519Address());
|
|
97
|
+
if (claimAddress !== connected)
|
|
98
|
+
throw new Error(`anchorMembershipBinding: claim "${binding.cciPrimaryClaim}" does not match connected wallet ${connected}`);
|
|
99
|
+
const nonce = (await demos.getAddressNonce(connected)) + 1;
|
|
100
|
+
const payload = StorageProgram.createStorageProgram(connected, bindingProgramName(binding.channelId, binding.subnetMemberId), binding, "json", { mode: "public" }, { nonce });
|
|
101
|
+
const tx = DemosTransactions.empty();
|
|
102
|
+
tx.content.to = payload.storageAddress;
|
|
103
|
+
tx.content.nonce = nonce;
|
|
104
|
+
tx.content.amount = 0;
|
|
105
|
+
tx.content.type = "storageProgram";
|
|
106
|
+
tx.content.timestamp = Date.now();
|
|
107
|
+
tx.content.data = ["storageProgram", payload];
|
|
108
|
+
const signed = await demos.sign(tx);
|
|
109
|
+
const validity = await demos.confirm(signed);
|
|
110
|
+
await demos.broadcast(validity);
|
|
111
|
+
return { storageAddress: payload.storageAddress, txHash: signed.hash };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Find the bound `ClaimReference` for `(channelId, subnetMemberId)`.
|
|
115
|
+
*
|
|
116
|
+
* Two-stage check on every candidate Storage Program:
|
|
117
|
+
* 1. Embedded signature verifies under the embedded claim's key.
|
|
118
|
+
* 2. SP owner's Demos address matches that claim's address — so only
|
|
119
|
+
* the actual key-holder could have deployed this SP under this name.
|
|
120
|
+
*
|
|
121
|
+
* Both checks must pass. Returns `null` when no candidate qualifies.
|
|
122
|
+
* Membership is fixed for channel lifetime (CH-1) so callers SHOULD cache
|
|
123
|
+
* the result for the session.
|
|
124
|
+
*/
|
|
125
|
+
export async function resolveMember(channelId, subnetMemberId, rpcUrl) {
|
|
126
|
+
const name = bindingProgramName(channelId, subnetMemberId);
|
|
127
|
+
const list = await StorageProgram.searchByName(rpcUrl, name, {
|
|
128
|
+
exactMatch: true,
|
|
129
|
+
});
|
|
130
|
+
for (const item of list) {
|
|
131
|
+
const sp = await StorageProgram.getByAddress(rpcUrl, item.storageAddress);
|
|
132
|
+
if (sp?.encoding !== "json" || !sp.data)
|
|
133
|
+
continue;
|
|
134
|
+
if (typeof sp.data !== "object")
|
|
135
|
+
continue;
|
|
136
|
+
const binding = sp.data;
|
|
137
|
+
if (binding.channelId !== channelId)
|
|
138
|
+
continue;
|
|
139
|
+
if (binding.subnetMemberId !== subnetMemberId)
|
|
140
|
+
continue;
|
|
141
|
+
if (!verifyMembershipBinding(binding))
|
|
142
|
+
continue;
|
|
143
|
+
// Both parses are inside the same try so a single adversarially-
|
|
144
|
+
// crafted candidate (malformed claim ref OR malformed sp.owner)
|
|
145
|
+
// gets skipped instead of aborting the whole resolution loop —
|
|
146
|
+
// otherwise a squatter publishing a poisoned binding under our
|
|
147
|
+
// deterministic name could deny-of-service every consumer.
|
|
148
|
+
try {
|
|
149
|
+
const claimAddress = demosAddressFromClaim(binding.cciPrimaryClaim);
|
|
150
|
+
if (normalizeDemosAddress(sp.owner) !== claimAddress)
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
return binding.cciPrimaryClaim;
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.js","sourceRoot":"","sources":["../../../../src/l2ps/binding/binding.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,qBAAqB,EACrB,YAAY,EACZ,qBAAqB,EACrB,oBAAoB,EACpB,2BAA2B,GAE9B,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AAMzD,OAAO,EACH,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,qBAAqB,GACxB,MAAM,aAAa,CAAA;AAGpB;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAC9B,SAAiB,EACjB,cAAsB;IAEtB,OAAO,gBAAgB,kBAAkB,CAAC,SAAS,CAAC,IAAI,kBAAkB,CACtE,cAAc,CACjB,EAAE,CAAA;AACP,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,IAAU;IACnD,OAAO,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAA;AACzC,CAAC;AAWD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CACzC,IAAiC;IAEjC,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,IAAI,CAAA;IACxD,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;IAC9E,IAAI,CAAC,cAAc;QACf,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAA;IACvE,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC;QACpB,MAAM,IAAI,KAAK,CACX,6EAA6E,KAAK,GAAG,CACxF,CAAA;IAEL,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,CAAA;IAC1C,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC;QAC7C,MAAM,IAAI,KAAK,CACX,wFAAwF,CAC3F,CAAA;IAEL,MAAM,QAAQ,GAAkC;QAC5C,cAAc,EAAE,GAAG;QACnB,SAAS;QACT,cAAc;QACd,eAAe,EAAE,KAAK;QACtB,OAAO;KACV,CAAA;IAED,MAAM,OAAO,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IAC7C,MAAM,GAAG,GAAG,MAAM,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;IAE7D,OAAO,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,cAAc,CAAC,GAAG,CAAC,EAAE,CAAA;AAC1D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACnC,OAA8B;IAE9B,IAAI,OAAO,EAAE,cAAc,KAAK,GAAG;QAAE,OAAO,KAAK,CAAA;IACjD,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,cAAc;QAAE,OAAO,KAAK,CAAA;IAC/D,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,eAAe,CAAC;QAAE,OAAO,KAAK,CAAA;IACxD,IACI,CAAC,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC;QACrC,OAAO,CAAC,OAAkB,GAAG,CAAC;QAE/B,OAAO,KAAK,CAAA;IAChB,IAAI,OAAO,OAAO,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,SAAS;QAC3D,OAAO,KAAK,CAAA;IAEhB,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,mBAAmB,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAA;QACnE,MAAM,GAAG,GAAG,gBAAgB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;QAC/C,OAAO,2BAA2B,CAC9B,OAAO,CAAC,eAAe,EACvB,OAAO,EACP,GAAG,CACN,CAAA;IACL,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,KAAK,CAAA;IAChB,CAAC;AACL,CAAC;AAOD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CACzC,OAA8B,EAC9B,KAAY;IAEZ,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC;QACjC,MAAM,IAAI,KAAK,CACX,qEAAqE,CACxE,CAAA;IAEL,MAAM,YAAY,GAAG,qBAAqB,CAAC,OAAO,CAAC,eAAe,CAAC,CAAA;IACnE,MAAM,SAAS,GAAG,qBAAqB,CAAC,MAAM,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAA;IACxE,IAAI,YAAY,KAAK,SAAS;QAC1B,MAAM,IAAI,KAAK,CACX,mCAAmC,OAAO,CAAC,eAAe,qCAAqC,SAAS,EAAE,CAC7G,CAAA;IAEL,MAAM,KAAK,GAAG,CAAC,MAAM,KAAK,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAA;IAC1D,MAAM,OAAO,GAAG,cAAc,CAAC,oBAAoB,CAC/C,SAAS,EACT,kBAAkB,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,cAAc,CAAC,EAC7D,OAA6C,EAC7C,MAAM,EACN,EAAE,IAAI,EAAE,QAAQ,EAAE,EAClB,EAAE,KAAK,EAAE,CACZ,CAAA;IAED,MAAM,EAAE,GAAG,iBAAiB,CAAC,KAAK,EAAiB,CAAA;IACnD,EAAE,CAAC,OAAO,CAAC,EAAE,GAAG,OAAO,CAAC,cAAc,CAAA;IACtC,EAAE,CAAC,OAAO,CAAC,KAAK,GAAG,KAAK,CAAA;IACxB,EAAE,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAA;IACrB,EAAE,CAAC,OAAO,CAAC,IAAI,GAAG,gBAAgB,CAAA;IAClC,EAAE,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;IACjC,EAAE,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,gBAAgB,EAAE,OAAO,CAAQ,CAAA;IAEpD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACnC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAC5C,MAAM,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAA;IAE/B,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,CAAA;AAC1E,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAC/B,SAAiB,EACjB,cAAsB,EACtB,MAAc;IAEd,MAAM,IAAI,GAAG,kBAAkB,CAAC,SAAS,EAAE,cAAc,CAAC,CAAA;IAC1D,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE;QACzD,UAAU,EAAE,IAAI;KACnB,CAAC,CAAA;IAEF,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC;QACtB,MAAM,EAAE,GAAG,MAAM,cAAc,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,CAAC,CAAA;QACzE,IAAI,EAAE,EAAE,QAAQ,KAAK,MAAM,IAAI,CAAC,EAAE,CAAC,IAAI;YAAE,SAAQ;QACjD,IAAI,OAAO,EAAE,CAAC,IAAI,KAAK,QAAQ;YAAE,SAAQ;QAEzC,MAAM,OAAO,GAAG,EAAE,CAAC,IAAwC,CAAA;QAC3D,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS;YAAE,SAAQ;QAC7C,IAAI,OAAO,CAAC,cAAc,KAAK,cAAc;YAAE,SAAQ;QACvD,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC;YAAE,SAAQ;QAE/C,iEAAiE;QACjE,gEAAgE;QAChE,+DAA+D;QAC/D,+DAA+D;QAC/D,2DAA2D;QAC3D,IAAI,CAAC;YACD,MAAM,YAAY,GAAG,qBAAqB,CAAC,OAAO,CAAC,eAAe,CAAC,CAAA;YACnE,IAAI,qBAAqB,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,YAAY;gBAAE,SAAQ;QAClE,CAAC;QAAC,MAAM,CAAC;YACL,SAAQ;QACZ,CAAC;QAED,OAAO,OAAO,CAAC,eAAe,CAAA;IAClC,CAAC;IAED,OAAO,IAAI,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { signatureFromHex, signatureToHex } from "../utils/hex";
|
|
2
|
+
import type { L2PSMembershipBinding, UnsignedL2PSMembershipBinding } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Domain prefix for binding signatures. The brief specifies prefixes for
|
|
5
|
+
* channelmsg and transcript but not for the binding itself; per §5 of the
|
|
6
|
+
* brief, all signatures in this family must be domain-separated, so we
|
|
7
|
+
* align with the established `dacs-*:v1:` pattern.
|
|
8
|
+
*/
|
|
9
|
+
export declare const BINDING_DOMAIN_PREFIX = "dacs-binding:v1:";
|
|
10
|
+
/**
|
|
11
|
+
* Bytes that the binding signature covers:
|
|
12
|
+
* `dacs-binding:v1:` || sha256(JCS(binding_without_signature))
|
|
13
|
+
*
|
|
14
|
+
* Domain separation prevents this signature from being lifted into a
|
|
15
|
+
* channelmsg or transcript context (or vice versa).
|
|
16
|
+
*/
|
|
17
|
+
export declare function bindingSigningBytes(unsigned: UnsignedL2PSMembershipBinding): Uint8Array;
|
|
18
|
+
export declare function stripBindingSignature(b: L2PSMembershipBinding): UnsignedL2PSMembershipBinding;
|
|
19
|
+
export { signatureFromHex, signatureToHex };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { sha256 } from "@noble/hashes/sha2";
|
|
2
|
+
import { canonicalJSONStringify } from "../../websdk/utils/canonicalJson.js";
|
|
3
|
+
import { bytesToHex, signatureFromHex, signatureToHex } from "../utils/hex.js";
|
|
4
|
+
/**
|
|
5
|
+
* Domain prefix for binding signatures. The brief specifies prefixes for
|
|
6
|
+
* channelmsg and transcript but not for the binding itself; per §5 of the
|
|
7
|
+
* brief, all signatures in this family must be domain-separated, so we
|
|
8
|
+
* align with the established `dacs-*:v1:` pattern.
|
|
9
|
+
*/
|
|
10
|
+
export const BINDING_DOMAIN_PREFIX = "dacs-binding:v1:";
|
|
11
|
+
/**
|
|
12
|
+
* Bytes that the binding signature covers:
|
|
13
|
+
* `dacs-binding:v1:` || sha256(JCS(binding_without_signature))
|
|
14
|
+
*
|
|
15
|
+
* Domain separation prevents this signature from being lifted into a
|
|
16
|
+
* channelmsg or transcript context (or vice versa).
|
|
17
|
+
*/
|
|
18
|
+
export function bindingSigningBytes(unsigned) {
|
|
19
|
+
const canonical = canonicalJSONStringify(unsigned);
|
|
20
|
+
const digestHex = bytesToHex(sha256(new TextEncoder().encode(canonical)));
|
|
21
|
+
return new TextEncoder().encode(BINDING_DOMAIN_PREFIX + digestHex);
|
|
22
|
+
}
|
|
23
|
+
export function stripBindingSignature(b) {
|
|
24
|
+
const { signature: _signature, ...rest } = b;
|
|
25
|
+
return rest;
|
|
26
|
+
}
|
|
27
|
+
// Re-exported from the shared utility so existing imports of
|
|
28
|
+
// `signatureFromHex` / `signatureToHex` from this module keep working.
|
|
29
|
+
export { signatureFromHex, signatureToHex };
|
|
30
|
+
//# sourceMappingURL=canonical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../../../../src/l2ps/binding/canonical.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAM3E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,kBAAkB,CAAA;AAEvD;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAC/B,QAAuC;IAEvC,MAAM,SAAS,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAA;IAClD,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;IACzE,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAAA;AACtE,CAAC;AAED,MAAM,UAAU,qBAAqB,CACjC,CAAwB;IAExB,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,CAAC,CAAA;IAC5C,OAAO,IAAI,CAAA;AACf,CAAC;AAED,6DAA6D;AAC7D,uEAAuE;AACvE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L2PS membership binding — DACS-3 §8.3.2 (SR-4 WI-1).
|
|
3
|
+
*
|
|
4
|
+
* Anchors `(channelId, subnetMemberId) -> CCI ClaimReference` proofs to
|
|
5
|
+
* chain via SR-2 Storage Programs so channel signatures can be verified
|
|
6
|
+
* against the same identity used in DACS-2.
|
|
7
|
+
*/
|
|
8
|
+
export { BINDING_DOMAIN_PREFIX, bindingSigningBytes, signatureFromHex, signatureToHex, stripBindingSignature, } from "./canonical";
|
|
9
|
+
export type { L2PSMembershipBinding, UnsignedL2PSMembershipBinding, } from "./types";
|
|
10
|
+
export { anchorMembershipBinding, bindingProgramName, createMembershipBinding, resolveMember, subnetMemberIdFromL2PS, verifyMembershipBinding, type AnchorMembershipBindingResult, type CreateMembershipBindingOpts, } from "./binding";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* L2PS membership binding — DACS-3 §8.3.2 (SR-4 WI-1).
|
|
3
|
+
*
|
|
4
|
+
* Anchors `(channelId, subnetMemberId) -> CCI ClaimReference` proofs to
|
|
5
|
+
* chain via SR-2 Storage Programs so channel signatures can be verified
|
|
6
|
+
* against the same identity used in DACS-2.
|
|
7
|
+
*/
|
|
8
|
+
export { BINDING_DOMAIN_PREFIX, bindingSigningBytes, signatureFromHex, signatureToHex, stripBindingSignature, } from "./canonical.js";
|
|
9
|
+
export { anchorMembershipBinding, bindingProgramName, createMembershipBinding, resolveMember, subnetMemberIdFromL2PS, verifyMembershipBinding, } from "./binding.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/l2ps/binding/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACH,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,qBAAqB,GACxB,MAAM,aAAa,CAAA;AAOpB,OAAO,EACH,uBAAuB,EACvB,kBAAkB,EAClB,uBAAuB,EACvB,aAAa,EACb,sBAAsB,EACtB,uBAAuB,GAG1B,MAAM,WAAW,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ClaimReference } from "../../identity/cci";
|
|
2
|
+
/**
|
|
3
|
+
* DACS-3 §8.3.2 interim membership binding.
|
|
4
|
+
*
|
|
5
|
+
* Until L2PS is natively CCI-keyed, each subnet member publishes one of
|
|
6
|
+
* these so channel signatures can be tied back to the same identity used
|
|
7
|
+
* in DACS-2. The signature MUST be produced by the key controlling
|
|
8
|
+
* `cciPrimaryClaim` — on Demos, the Demos Ed25519 key (NOT the RSA subnet
|
|
9
|
+
* key the L2PS class is configured with).
|
|
10
|
+
*/
|
|
11
|
+
export interface L2PSMembershipBinding {
|
|
12
|
+
bindingVersion: "1";
|
|
13
|
+
channelId: string;
|
|
14
|
+
subnetMemberId: string;
|
|
15
|
+
cciPrimaryClaim: ClaimReference;
|
|
16
|
+
boundAt: number;
|
|
17
|
+
/** Hex (0x-prefixed) Ed25519 signature over `bindingSigningBytes`. */
|
|
18
|
+
signature: string;
|
|
19
|
+
}
|
|
20
|
+
export type UnsignedL2PSMembershipBinding = Omit<L2PSMembershipBinding, "signature">;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/l2ps/binding/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { signatureFromHex, signatureToHex } from "../utils/hex";
|
|
2
|
+
import type { ChannelMessage, ChannelTranscript, UnsignedChannelMessage, UnsignedChannelTranscript } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Domain separation prefixes — verbatim from the brief (§2 WI-2, WI-3) and
|
|
5
|
+
* the spec references at §B.7. A signature lifted from one domain (e.g.
|
|
6
|
+
* channelmsg) fails verification in any other (e.g. transcript / binding).
|
|
7
|
+
*/
|
|
8
|
+
export declare const CHANNEL_MESSAGE_DOMAIN_PREFIX = "dacs-channelmsg:v1:";
|
|
9
|
+
export declare const TRANSCRIPT_DOMAIN_PREFIX = "dacs-transcript:v1:";
|
|
10
|
+
/**
|
|
11
|
+
* Hex digest of `JCS(envelope_without_signature)`. Exposed as a primitive
|
|
12
|
+
* so auditors can re-derive the hash independently from the wire envelope.
|
|
13
|
+
*/
|
|
14
|
+
export declare function envelopeHashHex(unsigned: UnsignedChannelMessage): string;
|
|
15
|
+
/**
|
|
16
|
+
* Bytes the channel-message signature covers:
|
|
17
|
+
* `dacs-channelmsg:v1:` || envelope_hash_hex
|
|
18
|
+
*/
|
|
19
|
+
export declare function channelMessageSigningBytes(unsigned: UnsignedChannelMessage): Uint8Array;
|
|
20
|
+
export declare function stripChannelMessageSignature(msg: ChannelMessage): UnsignedChannelMessage;
|
|
21
|
+
export declare function transcriptHashHex(unsigned: UnsignedChannelTranscript): string;
|
|
22
|
+
export declare function transcriptSigningBytes(unsigned: UnsignedChannelTranscript): Uint8Array;
|
|
23
|
+
export declare function stripTranscriptSignatures(t: ChannelTranscript): UnsignedChannelTranscript;
|
|
24
|
+
export { signatureFromHex, signatureToHex };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { sha256 } from "@noble/hashes/sha2";
|
|
2
|
+
import { canonicalJSONStringify } from "../../websdk/utils/canonicalJson.js";
|
|
3
|
+
import { bytesToHex, signatureFromHex, signatureToHex } from "../utils/hex.js";
|
|
4
|
+
/**
|
|
5
|
+
* Domain separation prefixes — verbatim from the brief (§2 WI-2, WI-3) and
|
|
6
|
+
* the spec references at §B.7. A signature lifted from one domain (e.g.
|
|
7
|
+
* channelmsg) fails verification in any other (e.g. transcript / binding).
|
|
8
|
+
*/
|
|
9
|
+
export const CHANNEL_MESSAGE_DOMAIN_PREFIX = "dacs-channelmsg:v1:";
|
|
10
|
+
export const TRANSCRIPT_DOMAIN_PREFIX = "dacs-transcript:v1:";
|
|
11
|
+
/**
|
|
12
|
+
* Hex digest of `JCS(envelope_without_signature)`. Exposed as a primitive
|
|
13
|
+
* so auditors can re-derive the hash independently from the wire envelope.
|
|
14
|
+
*/
|
|
15
|
+
export function envelopeHashHex(unsigned) {
|
|
16
|
+
const canonical = canonicalJSONStringify(unsigned);
|
|
17
|
+
return bytesToHex(sha256(new TextEncoder().encode(canonical)));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Bytes the channel-message signature covers:
|
|
21
|
+
* `dacs-channelmsg:v1:` || envelope_hash_hex
|
|
22
|
+
*/
|
|
23
|
+
export function channelMessageSigningBytes(unsigned) {
|
|
24
|
+
return new TextEncoder().encode(CHANNEL_MESSAGE_DOMAIN_PREFIX + envelopeHashHex(unsigned));
|
|
25
|
+
}
|
|
26
|
+
export function stripChannelMessageSignature(msg) {
|
|
27
|
+
const { signature: _signature, ...rest } = msg;
|
|
28
|
+
return rest;
|
|
29
|
+
}
|
|
30
|
+
export function transcriptHashHex(unsigned) {
|
|
31
|
+
const canonical = canonicalJSONStringify(unsigned);
|
|
32
|
+
return bytesToHex(sha256(new TextEncoder().encode(canonical)));
|
|
33
|
+
}
|
|
34
|
+
export function transcriptSigningBytes(unsigned) {
|
|
35
|
+
return new TextEncoder().encode(TRANSCRIPT_DOMAIN_PREFIX + transcriptHashHex(unsigned));
|
|
36
|
+
}
|
|
37
|
+
export function stripTranscriptSignatures(t) {
|
|
38
|
+
const { signatures: _signatures, ...rest } = t;
|
|
39
|
+
return rest;
|
|
40
|
+
}
|
|
41
|
+
// Re-exported from the shared utility so existing imports of
|
|
42
|
+
// `signatureFromHex` / `signatureToHex` from this module keep working.
|
|
43
|
+
export { signatureFromHex, signatureToHex };
|
|
44
|
+
//# sourceMappingURL=canonical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../../../../src/l2ps/channel/canonical.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAQ3E;;;;GAIG;AACH,MAAM,CAAC,MAAM,6BAA6B,GAAG,qBAAqB,CAAA;AAClE,MAAM,CAAC,MAAM,wBAAwB,GAAG,qBAAqB,CAAA;AAE7D;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgC;IAC5D,MAAM,SAAS,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAA;IAClD,OAAO,UAAU,CAAC,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;AAClE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACtC,QAAgC;IAEhC,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAC3B,6BAA6B,GAAG,eAAe,CAAC,QAAQ,CAAC,CAC5D,CAAA;AACL,CAAC;AAED,MAAM,UAAU,4BAA4B,CACxC,GAAmB;IAEnB,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,GAAG,CAAA;IAC9C,OAAO,IAAI,CAAA;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC7B,QAAmC;IAEnC,MAAM,SAAS,GAAG,sBAAsB,CAAC,QAAQ,CAAC,CAAA;IAClD,OAAO,UAAU,CAAC,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;AAClE,CAAC;AAED,MAAM,UAAU,sBAAsB,CAClC,QAAmC;IAEnC,OAAO,IAAI,WAAW,EAAE,CAAC,MAAM,CAC3B,wBAAwB,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CACzD,CAAA;AACL,CAAC;AAED,MAAM,UAAU,yBAAyB,CACrC,CAAoB;IAEpB,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,GAAG,CAAC,CAAA;IAC9C,OAAO,IAAI,CAAA;AACf,CAAC;AAED,6DAA6D;AAC7D,uEAAuE;AACvE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,CAAA"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CH-6 per-session channelId uniqueness — pluggable persistence.
|
|
3
|
+
*
|
|
4
|
+
* The substrate (L2PS subnet UID) already produces a unique value per
|
|
5
|
+
* session in honest deployments, but the brief requires explicit rejection
|
|
6
|
+
* of any session reusing a prior `channelId`. SDK ships an in-memory
|
|
7
|
+
* default; production callers wire their own store (durable DB, chain
|
|
8
|
+
* lookup, app-side persistence).
|
|
9
|
+
*/
|
|
10
|
+
export interface ChannelIdRegistry {
|
|
11
|
+
/** Throws if `channelId` has been registered before. */
|
|
12
|
+
register(channelId: string): Promise<void>;
|
|
13
|
+
has(channelId: string): Promise<boolean>;
|
|
14
|
+
}
|
|
15
|
+
export declare class InMemoryChannelIdRegistry implements ChannelIdRegistry {
|
|
16
|
+
private readonly seen;
|
|
17
|
+
register(channelId: string): Promise<void>;
|
|
18
|
+
has(channelId: string): Promise<boolean>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export class InMemoryChannelIdRegistry {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.seen = new Set();
|
|
4
|
+
}
|
|
5
|
+
async register(channelId) {
|
|
6
|
+
if (!channelId)
|
|
7
|
+
throw new Error("ChannelIdRegistry: channelId required");
|
|
8
|
+
if (this.seen.has(channelId))
|
|
9
|
+
throw new Error(`ChannelIdRegistry: channelId "${channelId}" already registered (CH-6 violation)`);
|
|
10
|
+
this.seen.add(channelId);
|
|
11
|
+
}
|
|
12
|
+
async has(channelId) {
|
|
13
|
+
return this.seen.has(channelId);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=channelIdRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channelIdRegistry.js","sourceRoot":"","sources":["../../../../src/l2ps/channel/channelIdRegistry.ts"],"names":[],"mappings":"AAeA,MAAM,OAAO,yBAAyB;IAAtC;QACqB,SAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAc7C,CAAC;IAZG,KAAK,CAAC,QAAQ,CAAC,SAAiB;QAC5B,IAAI,CAAC,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAA;QACxE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC;YACxB,MAAM,IAAI,KAAK,CACX,iCAAiC,SAAS,uCAAuC,CACpF,CAAA;QACL,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAC5B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,SAAiB;QACvB,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACnC,CAAC;CACJ"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type ClaimReference } from "../../identity/cci";
|
|
2
|
+
import { Demos } from "../../websdk/demosclass";
|
|
3
|
+
import type { ChannelMessage, UnsignedChannelMessage } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* Sign a `ChannelMessage` envelope with the sender's primary-claim key.
|
|
6
|
+
*
|
|
7
|
+
* The signature covers `dacs-channelmsg:v1:` || sha256(JCS(envelope w/o
|
|
8
|
+
* signature)). Transport-level wrappers MUST NOT alter the bytes returned
|
|
9
|
+
* here. Throws if `unsigned.sender` is not a demos: ClaimReference or
|
|
10
|
+
* does not match the connected wallet — preserves the
|
|
11
|
+
* in-channel-signer == on-chain-party invariant.
|
|
12
|
+
*/
|
|
13
|
+
export declare function signChannelMessage(unsigned: UnsignedChannelMessage, demos: Demos): Promise<ChannelMessage>;
|
|
14
|
+
/**
|
|
15
|
+
* Pure signature check: verifies `msg.signature` covers JCS(msg without
|
|
16
|
+
* signature) with the `dacs-channelmsg:v1:` prefix under the key encoded
|
|
17
|
+
* in `msg.sender`. Membership (`sender ∈ members`) and sequence enforcement
|
|
18
|
+
* are handled by `ChannelSession` — this function does crypto only.
|
|
19
|
+
*/
|
|
20
|
+
export declare function verifyChannelMessage(msg: ChannelMessage): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Pure membership check. Kept separate so `verifyChannelMessage` can be
|
|
23
|
+
* called in contexts where the member set hasn't been resolved yet (e.g.
|
|
24
|
+
* audit replay from a transcript).
|
|
25
|
+
*/
|
|
26
|
+
export declare function isSenderInMembers(msg: ChannelMessage, members: Iterable<ClaimReference>): boolean;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { isDemosClaim, signWithPrimaryClaim, verifyPrimaryClaimSignature, } from "../../identity/cci/index.js";
|
|
2
|
+
import { channelMessageSigningBytes, signatureFromHex, signatureToHex, stripChannelMessageSignature, } from "./canonical.js";
|
|
3
|
+
/**
|
|
4
|
+
* Sign a `ChannelMessage` envelope with the sender's primary-claim key.
|
|
5
|
+
*
|
|
6
|
+
* The signature covers `dacs-channelmsg:v1:` || sha256(JCS(envelope w/o
|
|
7
|
+
* signature)). Transport-level wrappers MUST NOT alter the bytes returned
|
|
8
|
+
* here. Throws if `unsigned.sender` is not a demos: ClaimReference or
|
|
9
|
+
* does not match the connected wallet — preserves the
|
|
10
|
+
* in-channel-signer == on-chain-party invariant.
|
|
11
|
+
*/
|
|
12
|
+
export async function signChannelMessage(unsigned, demos) {
|
|
13
|
+
if (!isDemosClaim(unsigned.sender))
|
|
14
|
+
throw new Error(`signChannelMessage: sender must be a demos: ClaimReference, got "${unsigned.sender}"`);
|
|
15
|
+
if (!Number.isInteger(unsigned.sequence) || unsigned.sequence < 1)
|
|
16
|
+
throw new Error(`signChannelMessage: sequence must be a positive integer (got ${unsigned.sequence})`);
|
|
17
|
+
if (!unsigned.channelId)
|
|
18
|
+
throw new Error("signChannelMessage: channelId required");
|
|
19
|
+
const payload = channelMessageSigningBytes(unsigned);
|
|
20
|
+
const sigBytes = await signWithPrimaryClaim(unsigned.sender, payload, demos);
|
|
21
|
+
const signature = {
|
|
22
|
+
sigVersion: "1",
|
|
23
|
+
signature: signatureToHex(sigBytes),
|
|
24
|
+
};
|
|
25
|
+
return { ...unsigned, signature };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Pure signature check: verifies `msg.signature` covers JCS(msg without
|
|
29
|
+
* signature) with the `dacs-channelmsg:v1:` prefix under the key encoded
|
|
30
|
+
* in `msg.sender`. Membership (`sender ∈ members`) and sequence enforcement
|
|
31
|
+
* are handled by `ChannelSession` — this function does crypto only.
|
|
32
|
+
*/
|
|
33
|
+
export function verifyChannelMessage(msg) {
|
|
34
|
+
if (msg?.signature?.sigVersion !== "1")
|
|
35
|
+
return false;
|
|
36
|
+
if (!isDemosClaim(msg.sender))
|
|
37
|
+
return false;
|
|
38
|
+
if (!Number.isInteger(msg.sequence) || msg.sequence < 1)
|
|
39
|
+
return false;
|
|
40
|
+
if (!msg.channelId)
|
|
41
|
+
return false;
|
|
42
|
+
try {
|
|
43
|
+
const payload = channelMessageSigningBytes(stripChannelMessageSignature(msg));
|
|
44
|
+
const sig = signatureFromHex(msg.signature.signature);
|
|
45
|
+
return verifyPrimaryClaimSignature(msg.sender, payload, sig);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Pure membership check. Kept separate so `verifyChannelMessage` can be
|
|
53
|
+
* called in contexts where the member set hasn't been resolved yet (e.g.
|
|
54
|
+
* audit replay from a transcript).
|
|
55
|
+
*/
|
|
56
|
+
export function isSenderInMembers(msg, members) {
|
|
57
|
+
for (const m of members)
|
|
58
|
+
if (m === msg.sender)
|
|
59
|
+
return true;
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=envelope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../../../src/l2ps/channel/envelope.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,YAAY,EACZ,oBAAoB,EACpB,2BAA2B,GAE9B,MAAM,gBAAgB,CAAA;AAOvB,OAAO,EACH,0BAA0B,EAC1B,gBAAgB,EAChB,cAAc,EACd,4BAA4B,GAC/B,MAAM,aAAa,CAAA;AAEpB;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACpC,QAAgC,EAChC,KAAY;IAEZ,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC9B,MAAM,IAAI,KAAK,CACX,oEAAoE,QAAQ,CAAC,MAAM,GAAG,CACzF,CAAA;IACL,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,QAAQ,GAAG,CAAC;QAC7D,MAAM,IAAI,KAAK,CACX,gEAAgE,QAAQ,CAAC,QAAQ,GAAG,CACvF,CAAA;IACL,IAAI,CAAC,QAAQ,CAAC,SAAS;QACnB,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAA;IAE7D,MAAM,OAAO,GAAG,0BAA0B,CAAC,QAAQ,CAAC,CAAA;IACpD,MAAM,QAAQ,GAAG,MAAM,oBAAoB,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;IAC5E,MAAM,SAAS,GAA4B;QACvC,UAAU,EAAE,GAAG;QACf,SAAS,EAAE,cAAc,CAAC,QAAQ,CAAC;KACtC,CAAA;IACD,OAAO,EAAE,GAAG,QAAQ,EAAE,SAAS,EAAE,CAAA;AACrC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAmB;IACpD,IAAI,GAAG,EAAE,SAAS,EAAE,UAAU,KAAK,GAAG;QAAE,OAAO,KAAK,CAAA;IACpD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IAC3C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,GAAG,CAAC;QAAE,OAAO,KAAK,CAAA;IACrE,IAAI,CAAC,GAAG,CAAC,SAAS;QAAE,OAAO,KAAK,CAAA;IAEhC,IAAI,CAAC;QACD,MAAM,OAAO,GAAG,0BAA0B,CACtC,4BAA4B,CAAC,GAAG,CAAC,CACpC,CAAA;QACD,MAAM,GAAG,GAAG,gBAAgB,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;QACrD,OAAO,2BAA2B,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAA;IAChE,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,KAAK,CAAA;IAChB,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC7B,GAAmB,EACnB,OAAiC;IAEjC,KAAK,MAAM,CAAC,IAAI,OAAO;QAAE,IAAI,CAAC,KAAK,GAAG,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA;IAC1D,OAAO,KAAK,CAAA;AAChB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
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";
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
//# 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"}
|
|
@@ -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
|
+
}
|