@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,83 @@
|
|
|
1
|
+
import { Cryptography } from "../../encryption/Cryptography.js";
|
|
2
|
+
import { demosAddressFromClaim, normalizeDemosAddress, parseClaimRef, } from "./claim.js";
|
|
3
|
+
/**
|
|
4
|
+
* Sign a payload with the key controlling a CCI primary claim.
|
|
5
|
+
*
|
|
6
|
+
* The brief invariant (DACS-3 §0 / §8.3.2): in-channel signatures MUST be
|
|
7
|
+
* produced by the key that controls the sender's primary claim — the same
|
|
8
|
+
* key that holds value on-chain. On Demos that is the Demos Ed25519 key,
|
|
9
|
+
* NOT the RSA L2PS subnet key.
|
|
10
|
+
*
|
|
11
|
+
* Domain separation is the caller's responsibility — pass the full payload
|
|
12
|
+
* including the `dacs-*:v1:` prefix. WI-0 stays scheme-agnostic.
|
|
13
|
+
*
|
|
14
|
+
* @param claim - The signer's primary `demos:` ClaimReference. Refuses any
|
|
15
|
+
* other scheme until a controlling-key resolver for them
|
|
16
|
+
* is added.
|
|
17
|
+
* @param payload - Pre-domain-separated bytes to cover. Caller composes
|
|
18
|
+
* `dacs-*:v1: || canonical_hash`.
|
|
19
|
+
* @param demos - Connected `Demos` instance. Its Ed25519 address must
|
|
20
|
+
* match `claim`'s identifier — otherwise we refuse to
|
|
21
|
+
* produce a misattributable signature.
|
|
22
|
+
* @returns Raw 64-byte Ed25519 signature.
|
|
23
|
+
* @throws {Error} If the scheme is not `demos`, the wallet is not
|
|
24
|
+
* connected, or the connected address does not match the
|
|
25
|
+
* claim's address.
|
|
26
|
+
*/
|
|
27
|
+
export async function signWithPrimaryClaim(claim, payload, demos) {
|
|
28
|
+
const { scheme } = parseClaimRef(claim);
|
|
29
|
+
if (scheme !== "demos") {
|
|
30
|
+
throw new Error(`signWithPrimaryClaim: unsupported scheme "${scheme}". Only "demos" is currently supported.`);
|
|
31
|
+
}
|
|
32
|
+
if (!demos.walletConnected) {
|
|
33
|
+
throw new Error("signWithPrimaryClaim: Demos wallet not connected");
|
|
34
|
+
}
|
|
35
|
+
const claimAddress = demosAddressFromClaim(claim);
|
|
36
|
+
const connected = normalizeDemosAddress(await demos.getEd25519Address());
|
|
37
|
+
if (claimAddress !== connected) {
|
|
38
|
+
throw new Error(`signWithPrimaryClaim: claim "${claim}" does not match connected Demos wallet ${connected}`);
|
|
39
|
+
}
|
|
40
|
+
const signed = await demos.crypto.sign("ed25519", payload);
|
|
41
|
+
return toUint8Array(signed.signature);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Verify an Ed25519 signature produced by `signWithPrimaryClaim`.
|
|
45
|
+
*
|
|
46
|
+
* Recovers the public key from the claim identifier and verifies forge-style.
|
|
47
|
+
* Caller-supplied payload must match the bytes that were signed (including
|
|
48
|
+
* any `dacs-*:v1:` domain prefix).
|
|
49
|
+
*
|
|
50
|
+
* @param claim - The claimed signer's `demos:` ClaimReference.
|
|
51
|
+
* @param payload - Exact bytes that were signed (must roundtrip through
|
|
52
|
+
* UTF-8). Caller composes `dacs-*:v1: || hash`.
|
|
53
|
+
* @param signature - Raw 64-byte Ed25519 signature.
|
|
54
|
+
* @returns `true` iff the signature verifies under the public key
|
|
55
|
+
* encoded in `claim`; `false` for cryptographic mismatch.
|
|
56
|
+
* @throws {Error} If the scheme is not `demos` or the recovered public
|
|
57
|
+
* key is not 32 bytes (malformed claim).
|
|
58
|
+
*/
|
|
59
|
+
export function verifyPrimaryClaimSignature(claim, payload, signature) {
|
|
60
|
+
const { scheme } = parseClaimRef(claim);
|
|
61
|
+
if (scheme !== "demos") {
|
|
62
|
+
throw new Error(`verifyPrimaryClaimSignature: unsupported scheme "${scheme}"`);
|
|
63
|
+
}
|
|
64
|
+
const publicKey = hexAddressToBytes(demosAddressFromClaim(claim));
|
|
65
|
+
if (publicKey.length !== 32) {
|
|
66
|
+
throw new Error(`verifyPrimaryClaimSignature: expected 32-byte Ed25519 public key, got ${publicKey.length}`);
|
|
67
|
+
}
|
|
68
|
+
return Cryptography.verify(new TextDecoder().decode(payload), Buffer.from(signature), Buffer.from(publicKey));
|
|
69
|
+
}
|
|
70
|
+
function hexAddressToBytes(address) {
|
|
71
|
+
const hex = address.startsWith("0x") ? address.slice(2) : address;
|
|
72
|
+
const out = new Uint8Array(hex.length / 2);
|
|
73
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
74
|
+
out[i / 2] = Number.parseInt(hex.slice(i, i + 2), 16);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
function toUint8Array(buf) {
|
|
79
|
+
if (buf instanceof Uint8Array)
|
|
80
|
+
return buf;
|
|
81
|
+
return Uint8Array.from(buf);
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=signing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signing.js","sourceRoot":"","sources":["../../../../src/identity/cci/signing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Context Identity (CCI) — type definitions.
|
|
3
|
+
*
|
|
4
|
+
* A ClaimReference is the label that names an identity across substrates.
|
|
5
|
+
* It is NOT a key; the key controlling the claim signs in-channel messages.
|
|
6
|
+
* See `docs/l2ps-sr4-implementation-brief.md` §0 in the node repo for the
|
|
7
|
+
* full claim-vs-key distinction.
|
|
8
|
+
*/
|
|
9
|
+
export type ClaimScheme = "demos" | (string & {});
|
|
10
|
+
export type ClaimReference = `${string}:${string}`;
|
|
11
|
+
export interface ParsedClaimRef {
|
|
12
|
+
scheme: ClaimScheme;
|
|
13
|
+
identifier: string;
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Context Identity (CCI) — type definitions.
|
|
3
|
+
*
|
|
4
|
+
* A ClaimReference is the label that names an identity across substrates.
|
|
5
|
+
* It is NOT a key; the key controlling the claim signs in-channel messages.
|
|
6
|
+
* See `docs/l2ps-sr4-implementation-brief.md` §0 in the node repo for the
|
|
7
|
+
* full claim-vs-key distinction.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/identity/cci/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/identity/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,aAAa,MAAM,iBAAiB,CAAA;AAChD,OAAO,KAAK,GAAG,MAAM,OAAO,CAAA"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { type ClaimReference } from "../../identity/cci";
|
|
2
|
+
import { Demos } from "../../websdk/demosclass";
|
|
3
|
+
import L2PS from "../l2ps";
|
|
4
|
+
import type { ChannelTranscript } from "../channel/types";
|
|
5
|
+
import type { AttestationRef, TranscriptDisclosurePolicy } from "./types";
|
|
6
|
+
/**
|
|
7
|
+
* Deterministic per-channel Storage Program name. One anchor per
|
|
8
|
+
* channelId; a redo of the same channel reuses the slot (the SP's
|
|
9
|
+
* `mode: "owner"` ACL guarantees only the original signer can overwrite).
|
|
10
|
+
*
|
|
11
|
+
* @param channelId - Per-session `channelId` (matches `ChannelTranscript.channelId`).
|
|
12
|
+
* @returns The SR-2 program name used by `anchorEncryptedTranscript`.
|
|
13
|
+
*/
|
|
14
|
+
export declare function anchorProgramName(channelId: string): string;
|
|
15
|
+
export interface AnchorEncryptedTranscriptOpts {
|
|
16
|
+
transcript: ChannelTranscript;
|
|
17
|
+
/** Provides the AES-GCM key for encrypt-to-member-set. */
|
|
18
|
+
l2ps: L2PS;
|
|
19
|
+
demos: Demos;
|
|
20
|
+
/** Must be a member of `transcript.members` and match the connected Demos wallet. */
|
|
21
|
+
signer: ClaimReference;
|
|
22
|
+
policy: TranscriptDisclosurePolicy;
|
|
23
|
+
/** Required to actually anchor when `policy === "encrypted-anchored-recommended"`. */
|
|
24
|
+
consent?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* §8.7 anchor entrypoint. Encrypts a `ChannelTranscript` to the L2PS
|
|
28
|
+
* member set, anchors the ciphertext via SR-2, and signs the plaintext
|
|
29
|
+
* hash with the connected Demos key.
|
|
30
|
+
*
|
|
31
|
+
* Policy behaviour:
|
|
32
|
+
* - `none` → throws (caller should not have invoked this).
|
|
33
|
+
* - `recommended` → only anchors when `consent === true`; otherwise returns `null`.
|
|
34
|
+
* - `required` → always anchors; broadcast failures propagate so
|
|
35
|
+
* the caller's phase can fail per §8.7.
|
|
36
|
+
*
|
|
37
|
+
* The SP is deployed via SR-2 with `mode: "public"` (anyone reads the
|
|
38
|
+
* ciphertext + the public content hash for tamper-evidence; only the
|
|
39
|
+
* owner can overwrite).
|
|
40
|
+
*
|
|
41
|
+
* @param opts - Transcript + L2PS instance (for the AES-GCM key) +
|
|
42
|
+
* connected Demos (for the signing key + broadcast) +
|
|
43
|
+
* signer claim + policy/consent.
|
|
44
|
+
* @returns `{ anchor, contentHash }` on success, `null` only when the
|
|
45
|
+
* recommended policy is in effect and consent is missing.
|
|
46
|
+
* @throws {Error} On `policy === "none"`, non-demos signer, non-member
|
|
47
|
+
* signer, signer-wallet mismatch, or broadcast failure
|
|
48
|
+
* under `required` policy.
|
|
49
|
+
*/
|
|
50
|
+
export declare function anchorEncryptedTranscript(opts: AnchorEncryptedTranscriptOpts): Promise<AttestationRef | null>;
|
|
51
|
+
export interface DecryptAnchoredTranscriptOpts {
|
|
52
|
+
rpcUrl: string;
|
|
53
|
+
storageAddress: string;
|
|
54
|
+
l2ps: L2PS;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Fetch + decrypt + fully verify the original transcript.
|
|
58
|
+
*
|
|
59
|
+
* @param opts - RPC URL + Storage Program address + L2PS instance (caller
|
|
60
|
+
* must be a subnet member; otherwise AES-GCM auth fails).
|
|
61
|
+
* @returns A `ChannelTranscript` whose every component has been
|
|
62
|
+
* validated end-to-end — callers do not need to run
|
|
63
|
+
* `verifyTranscript` again.
|
|
64
|
+
* @throws {Error} On the first of:
|
|
65
|
+
* - SP not found / wrong shape / unknown transcriptVersion
|
|
66
|
+
* - ciphertext hash does not match the stored `contentHash` (tamper)
|
|
67
|
+
* - AES-GCM auth fails (= caller's L2PS instance is not a subnet member)
|
|
68
|
+
* - decrypted `channelId` does not match the anchor's `channelId`
|
|
69
|
+
* - anchor-party transcript signature is invalid
|
|
70
|
+
* - any embedded ChannelMessage signature, sequence, sender ∈ members,
|
|
71
|
+
* or per-message channelId check fails (delegated to `verifyTranscript`).
|
|
72
|
+
*/
|
|
73
|
+
export declare function decryptAnchoredTranscript(opts: DecryptAnchoredTranscriptOpts): Promise<ChannelTranscript>;
|
|
74
|
+
export interface VerifyAnchorIntegrityResult {
|
|
75
|
+
ok: boolean;
|
|
76
|
+
errors: string[];
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Public tamper-check that needs no decryption keys. Hashes the on-chain
|
|
80
|
+
* ciphertext and compares with the stored `contentHash`; also asserts the
|
|
81
|
+
* SP owner address matches the embedded signer's CCI claim. Useful for
|
|
82
|
+
* auditors and non-members verifying an anchor without subnet access.
|
|
83
|
+
*
|
|
84
|
+
* @param opts - RPC URL + Storage Program address.
|
|
85
|
+
* @returns `{ ok, errors }` — `ok` only when every public check passes;
|
|
86
|
+
* `errors` contains every failure (collected in one pass so
|
|
87
|
+
* auditors see the full picture, not the first failure).
|
|
88
|
+
*/
|
|
89
|
+
export declare function verifyAnchorIntegrity(opts: {
|
|
90
|
+
rpcUrl: string;
|
|
91
|
+
storageAddress: string;
|
|
92
|
+
}): Promise<VerifyAnchorIntegrityResult>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { sha256 } from "@noble/hashes/sha2";
|
|
2
|
+
import { canonicalJSONStringify } from "../../websdk/utils/canonicalJson.js";
|
|
3
|
+
import { demosAddressFromClaim, isDemosClaim, normalizeDemosAddress, signWithPrimaryClaim, verifyPrimaryClaimSignature, } from "../../identity/cci/index.js";
|
|
4
|
+
import { DemosTransactions } from "../../websdk/DemosTransactions.js";
|
|
5
|
+
import { StorageProgram } from "../../storage/StorageProgram.js";
|
|
6
|
+
import { stripTranscriptSignatures, transcriptSigningBytes, } from "../channel/canonical.js";
|
|
7
|
+
import { verifyTranscript } from "../channel/transcript.js";
|
|
8
|
+
import { bytesToHex, signatureFromHex, signatureToHex } from "../utils/hex.js";
|
|
9
|
+
/**
|
|
10
|
+
* Deterministic per-channel Storage Program name. One anchor per
|
|
11
|
+
* channelId; a redo of the same channel reuses the slot (the SP's
|
|
12
|
+
* `mode: "owner"` ACL guarantees only the original signer can overwrite).
|
|
13
|
+
*
|
|
14
|
+
* @param channelId - Per-session `channelId` (matches `ChannelTranscript.channelId`).
|
|
15
|
+
* @returns The SR-2 program name used by `anchorEncryptedTranscript`.
|
|
16
|
+
*/
|
|
17
|
+
export function anchorProgramName(channelId) {
|
|
18
|
+
return `l2ps-transcript:${encodeURIComponent(channelId)}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* §8.7 anchor entrypoint. Encrypts a `ChannelTranscript` to the L2PS
|
|
22
|
+
* member set, anchors the ciphertext via SR-2, and signs the plaintext
|
|
23
|
+
* hash with the connected Demos key.
|
|
24
|
+
*
|
|
25
|
+
* Policy behaviour:
|
|
26
|
+
* - `none` → throws (caller should not have invoked this).
|
|
27
|
+
* - `recommended` → only anchors when `consent === true`; otherwise returns `null`.
|
|
28
|
+
* - `required` → always anchors; broadcast failures propagate so
|
|
29
|
+
* the caller's phase can fail per §8.7.
|
|
30
|
+
*
|
|
31
|
+
* The SP is deployed via SR-2 with `mode: "public"` (anyone reads the
|
|
32
|
+
* ciphertext + the public content hash for tamper-evidence; only the
|
|
33
|
+
* owner can overwrite).
|
|
34
|
+
*
|
|
35
|
+
* @param opts - Transcript + L2PS instance (for the AES-GCM key) +
|
|
36
|
+
* connected Demos (for the signing key + broadcast) +
|
|
37
|
+
* signer claim + policy/consent.
|
|
38
|
+
* @returns `{ anchor, contentHash }` on success, `null` only when the
|
|
39
|
+
* recommended policy is in effect and consent is missing.
|
|
40
|
+
* @throws {Error} On `policy === "none"`, non-demos signer, non-member
|
|
41
|
+
* signer, signer-wallet mismatch, or broadcast failure
|
|
42
|
+
* under `required` policy.
|
|
43
|
+
*/
|
|
44
|
+
export async function anchorEncryptedTranscript(opts) {
|
|
45
|
+
if (opts.policy === "none") {
|
|
46
|
+
throw new Error("anchorEncryptedTranscript: policy is 'none' — do not call this helper");
|
|
47
|
+
}
|
|
48
|
+
if (opts.policy === "encrypted-anchored-recommended" &&
|
|
49
|
+
opts.consent !== true) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
if (!isDemosClaim(opts.signer)) {
|
|
53
|
+
throw new Error(`anchorEncryptedTranscript: signer must be a demos: ClaimReference, got "${opts.signer}"`);
|
|
54
|
+
}
|
|
55
|
+
if (!opts.transcript.members.includes(opts.signer)) {
|
|
56
|
+
throw new Error(`anchorEncryptedTranscript: signer "${opts.signer}" is not in transcript.members`);
|
|
57
|
+
}
|
|
58
|
+
const signerAddress = demosAddressFromClaim(opts.signer);
|
|
59
|
+
const connected = normalizeDemosAddress(await opts.demos.getEd25519Address());
|
|
60
|
+
if (signerAddress !== connected) {
|
|
61
|
+
throw new Error(`anchorEncryptedTranscript: signer "${opts.signer}" does not match connected wallet ${connected}`);
|
|
62
|
+
}
|
|
63
|
+
const unsignedView = stripTranscriptSignatures(opts.transcript);
|
|
64
|
+
const plaintextBytes = new TextEncoder().encode(canonicalJSONStringify(unsignedView));
|
|
65
|
+
const encrypted = await opts.l2ps.encryptBytes(plaintextBytes);
|
|
66
|
+
const contentHash = bytesToHex(sha256(base64ToBytes(encrypted.ciphertext)));
|
|
67
|
+
const sigBytes = await signWithPrimaryClaim(opts.signer, transcriptSigningBytes(unsignedView), opts.demos);
|
|
68
|
+
const payload = {
|
|
69
|
+
transcriptVersion: "1",
|
|
70
|
+
channelId: opts.transcript.channelId,
|
|
71
|
+
encrypted,
|
|
72
|
+
contentHash,
|
|
73
|
+
signature: {
|
|
74
|
+
sigVersion: "1",
|
|
75
|
+
signer: opts.signer,
|
|
76
|
+
signature: signatureToHex(sigBytes),
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
const storageAddress = await deployAnchorSP(opts.demos, anchorProgramName(opts.transcript.channelId), payload);
|
|
80
|
+
return { anchor: storageAddress, contentHash };
|
|
81
|
+
}
|
|
82
|
+
async function deployAnchorSP(demos, programName, payload) {
|
|
83
|
+
const deployer = normalizeDemosAddress(await demos.getEd25519Address());
|
|
84
|
+
const nonce = (await demos.getAddressNonce(deployer)) + 1;
|
|
85
|
+
const spPayload = StorageProgram.createStorageProgram(deployer, programName, payload, "json", { mode: "public" }, { nonce });
|
|
86
|
+
const tx = DemosTransactions.empty();
|
|
87
|
+
tx.content.to = spPayload.storageAddress;
|
|
88
|
+
tx.content.nonce = nonce;
|
|
89
|
+
tx.content.amount = 0;
|
|
90
|
+
tx.content.type = "storageProgram";
|
|
91
|
+
tx.content.timestamp = Date.now();
|
|
92
|
+
tx.content.data = ["storageProgram", spPayload];
|
|
93
|
+
const signed = await demos.sign(tx);
|
|
94
|
+
const validity = await demos.confirm(signed);
|
|
95
|
+
await demos.broadcast(validity);
|
|
96
|
+
return spPayload.storageAddress;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Fetch + decrypt + fully verify the original transcript.
|
|
100
|
+
*
|
|
101
|
+
* @param opts - RPC URL + Storage Program address + L2PS instance (caller
|
|
102
|
+
* must be a subnet member; otherwise AES-GCM auth fails).
|
|
103
|
+
* @returns A `ChannelTranscript` whose every component has been
|
|
104
|
+
* validated end-to-end — callers do not need to run
|
|
105
|
+
* `verifyTranscript` again.
|
|
106
|
+
* @throws {Error} On the first of:
|
|
107
|
+
* - SP not found / wrong shape / unknown transcriptVersion
|
|
108
|
+
* - ciphertext hash does not match the stored `contentHash` (tamper)
|
|
109
|
+
* - AES-GCM auth fails (= caller's L2PS instance is not a subnet member)
|
|
110
|
+
* - decrypted `channelId` does not match the anchor's `channelId`
|
|
111
|
+
* - anchor-party transcript signature is invalid
|
|
112
|
+
* - any embedded ChannelMessage signature, sequence, sender ∈ members,
|
|
113
|
+
* or per-message channelId check fails (delegated to `verifyTranscript`).
|
|
114
|
+
*/
|
|
115
|
+
export async function decryptAnchoredTranscript(opts) {
|
|
116
|
+
const sp = await StorageProgram.getByAddress(opts.rpcUrl, opts.storageAddress);
|
|
117
|
+
if (!sp?.data || typeof sp.data !== "object")
|
|
118
|
+
throw new Error(`decryptAnchoredTranscript: no storage program at ${opts.storageAddress}`);
|
|
119
|
+
const payload = sp.data;
|
|
120
|
+
if (payload.transcriptVersion !== "1")
|
|
121
|
+
throw new Error("decryptAnchoredTranscript: unknown transcriptVersion");
|
|
122
|
+
const ctHash = bytesToHex(sha256(base64ToBytes(payload.encrypted.ciphertext)));
|
|
123
|
+
if (ctHash !== payload.contentHash)
|
|
124
|
+
throw new Error("decryptAnchoredTranscript: ciphertext hash does not match stored contentHash (tamper)");
|
|
125
|
+
const plaintext = await opts.l2ps.decryptBytes(payload.encrypted);
|
|
126
|
+
const transcript = JSON.parse(new TextDecoder().decode(plaintext));
|
|
127
|
+
if (transcript.channelId !== payload.channelId)
|
|
128
|
+
throw new Error("decryptAnchoredTranscript: decrypted channelId does not match anchor channelId");
|
|
129
|
+
const sigOk = verifyPrimaryClaimSignature(payload.signature.signer, transcriptSigningBytes(transcript), signatureFromHex(payload.signature.signature));
|
|
130
|
+
if (!sigOk)
|
|
131
|
+
throw new Error("decryptAnchoredTranscript: transcript signature verification failed");
|
|
132
|
+
const fullTranscript = {
|
|
133
|
+
...transcript,
|
|
134
|
+
signatures: [payload.signature],
|
|
135
|
+
};
|
|
136
|
+
// Run the audit verifier on the assembled transcript so the caller's
|
|
137
|
+
// return value is end-to-end validated, not just the anchor wrapper.
|
|
138
|
+
const audit = verifyTranscript(fullTranscript);
|
|
139
|
+
if (!audit.ok)
|
|
140
|
+
throw new Error(`decryptAnchoredTranscript: transcript audit failed — ${audit.errors.join("; ")}`);
|
|
141
|
+
return fullTranscript;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Public tamper-check that needs no decryption keys. Hashes the on-chain
|
|
145
|
+
* ciphertext and compares with the stored `contentHash`; also asserts the
|
|
146
|
+
* SP owner address matches the embedded signer's CCI claim. Useful for
|
|
147
|
+
* auditors and non-members verifying an anchor without subnet access.
|
|
148
|
+
*
|
|
149
|
+
* @param opts - RPC URL + Storage Program address.
|
|
150
|
+
* @returns `{ ok, errors }` — `ok` only when every public check passes;
|
|
151
|
+
* `errors` contains every failure (collected in one pass so
|
|
152
|
+
* auditors see the full picture, not the first failure).
|
|
153
|
+
*/
|
|
154
|
+
export async function verifyAnchorIntegrity(opts) {
|
|
155
|
+
const errors = [];
|
|
156
|
+
const sp = await StorageProgram.getByAddress(opts.rpcUrl, opts.storageAddress);
|
|
157
|
+
if (!sp?.data || typeof sp.data !== "object") {
|
|
158
|
+
errors.push(`no storage program at ${opts.storageAddress}`);
|
|
159
|
+
return { ok: false, errors };
|
|
160
|
+
}
|
|
161
|
+
const payload = sp.data;
|
|
162
|
+
if (payload.transcriptVersion !== "1")
|
|
163
|
+
errors.push(`unknown transcriptVersion ${payload.transcriptVersion}`);
|
|
164
|
+
try {
|
|
165
|
+
const hash = bytesToHex(sha256(base64ToBytes(payload.encrypted.ciphertext)));
|
|
166
|
+
if (hash !== payload.contentHash)
|
|
167
|
+
errors.push("ciphertext hash does not match stored contentHash");
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
errors.push(`hash check failed: ${e.message}`);
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const signerAddress = demosAddressFromClaim(payload.signature.signer);
|
|
174
|
+
if (normalizeDemosAddress(sp.owner) !== signerAddress)
|
|
175
|
+
errors.push(`storage program owner ${sp.owner} does not match signer claim ${payload.signature.signer}`);
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
errors.push(`signer claim address check failed: ${e.message}`);
|
|
179
|
+
}
|
|
180
|
+
return { ok: errors.length === 0, errors };
|
|
181
|
+
}
|
|
182
|
+
function base64ToBytes(b64) {
|
|
183
|
+
const bin = Buffer.from(b64, "base64");
|
|
184
|
+
return new Uint8Array(bin.buffer, bin.byteOffset, bin.byteLength);
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=anchor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anchor.js","sourceRoot":"","sources":["../../../../src/l2ps/anchor/anchor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DACS-3 §8.7 — encrypted transcript anchoring (SR-4 WI-3).
|
|
3
|
+
*
|
|
4
|
+
* Encrypts a `ChannelTranscript` to the subnet member set via the
|
|
5
|
+
* existing L2PS AES-GCM key, anchors the ciphertext + public content hash
|
|
6
|
+
* via SR-2 Storage Program, and signs the transcript-plaintext hash with
|
|
7
|
+
* the caller's Demos key.
|
|
8
|
+
*/
|
|
9
|
+
export { anchorEncryptedTranscript, anchorProgramName, decryptAnchoredTranscript, verifyAnchorIntegrity, type AnchorEncryptedTranscriptOpts, type DecryptAnchoredTranscriptOpts, type VerifyAnchorIntegrityResult, } from "./anchor";
|
|
10
|
+
export type { AnchoredTranscriptPayload, AnchoredTranscriptSignature, AttestationRef, TranscriptDisclosurePolicy, } from "./types";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DACS-3 §8.7 — encrypted transcript anchoring (SR-4 WI-3).
|
|
3
|
+
*
|
|
4
|
+
* Encrypts a `ChannelTranscript` to the subnet member set via the
|
|
5
|
+
* existing L2PS AES-GCM key, anchors the ciphertext + public content hash
|
|
6
|
+
* via SR-2 Storage Program, and signs the transcript-plaintext hash with
|
|
7
|
+
* the caller's Demos key.
|
|
8
|
+
*/
|
|
9
|
+
export { anchorEncryptedTranscript, anchorProgramName, decryptAnchoredTranscript, verifyAnchorIntegrity, } from "./anchor.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/l2ps/anchor/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACH,yBAAyB,EACzB,iBAAiB,EACjB,yBAAyB,EACzB,qBAAqB,GAIxB,MAAM,UAAU,CAAA"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ClaimReference } from "../../identity/cci";
|
|
2
|
+
import type { L2PSEncryptedBytes } from "../l2ps";
|
|
3
|
+
/**
|
|
4
|
+
* §8.7 disclosure policy values. Naming verbatim from the brief.
|
|
5
|
+
*/
|
|
6
|
+
export type TranscriptDisclosurePolicy = "none" | "encrypted-anchored-recommended" | "encrypted-anchored-required";
|
|
7
|
+
/**
|
|
8
|
+
* The reference the negotiate-* phase output carries (brief §2 WI-3 step 4):
|
|
9
|
+
* a Storage Program address + a hex content hash that anyone can re-derive
|
|
10
|
+
* by hashing the on-chain ciphertext.
|
|
11
|
+
*/
|
|
12
|
+
export interface AttestationRef {
|
|
13
|
+
anchor: string;
|
|
14
|
+
contentHash: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Signature over the transcript-plaintext hash, written verbatim into the
|
|
18
|
+
* Storage Program so members can re-verify after decryption.
|
|
19
|
+
*/
|
|
20
|
+
export interface AnchoredTranscriptSignature {
|
|
21
|
+
sigVersion: "1";
|
|
22
|
+
signer: ClaimReference;
|
|
23
|
+
signature: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Wire shape stored inside the Storage Program. Self-contained so a
|
|
27
|
+
* verifier can run `verifyAnchorIntegrity` without prior context.
|
|
28
|
+
*/
|
|
29
|
+
export interface AnchoredTranscriptPayload {
|
|
30
|
+
transcriptVersion: "1";
|
|
31
|
+
channelId: string;
|
|
32
|
+
encrypted: L2PSEncryptedBytes;
|
|
33
|
+
/** Hex sha256 of the decoded ciphertext bytes — public tamper-evidence. */
|
|
34
|
+
contentHash: string;
|
|
35
|
+
signature: AnchoredTranscriptSignature;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/l2ps/anchor/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type ClaimReference } from "../../identity/cci";
|
|
2
|
+
import { Demos } from "../../websdk/demosclass";
|
|
3
|
+
import type { L2PSMembershipBinding } from "./types";
|
|
4
|
+
import L2PS from "../l2ps";
|
|
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 declare function bindingProgramName(channelId: string, subnetMemberId: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Brief calls subnetMemberId "the member's L2PS/RSA identity within the
|
|
17
|
+
* subnet". L2PS already publishes a stable fingerprint of the RSA key —
|
|
18
|
+
* we reuse that so the value is derivable on both sides without a separate
|
|
19
|
+
* registry.
|
|
20
|
+
*/
|
|
21
|
+
export declare function subnetMemberIdFromL2PS(l2ps: L2PS): Promise<string>;
|
|
22
|
+
export interface CreateMembershipBindingOpts {
|
|
23
|
+
channelId: string;
|
|
24
|
+
subnetMemberId: string;
|
|
25
|
+
claim: ClaimReference;
|
|
26
|
+
demos: Demos;
|
|
27
|
+
/** Defaults to Date.now(). Useful for deterministic test fixtures. */
|
|
28
|
+
boundAt?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build and sign a binding. The signature is produced by the key
|
|
32
|
+
* controlling `claim`. Throws if `claim`'s scheme is not `demos:` or if
|
|
33
|
+
* the controlling address does not match the connected wallet — the
|
|
34
|
+
* invariant that ties channel signatures to on-chain identity.
|
|
35
|
+
*/
|
|
36
|
+
export declare function createMembershipBinding(opts: CreateMembershipBindingOpts): Promise<L2PSMembershipBinding>;
|
|
37
|
+
/**
|
|
38
|
+
* Pure signature check — no chain access. Verifies the embedded signature
|
|
39
|
+
* against the Demos public key encoded in `cciPrimaryClaim`.
|
|
40
|
+
*
|
|
41
|
+
* Returns `false` (not throws) on any structural problem so callers can
|
|
42
|
+
* use it as a filter when iterating SP candidates in `resolveMember`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function verifyMembershipBinding(binding: L2PSMembershipBinding): boolean;
|
|
45
|
+
export interface AnchorMembershipBindingResult {
|
|
46
|
+
storageAddress: string;
|
|
47
|
+
txHash: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Anchor a signed binding to chain via SR-2 (Storage Program). The SP is
|
|
51
|
+
* deployed under `bindingProgramName(...)` so `resolveMember` can find it
|
|
52
|
+
* by name. The deployer (= connected Demos wallet) becomes the SP owner;
|
|
53
|
+
* that owner check is what makes the resolver safe against impostor SPs
|
|
54
|
+
* published under the same name.
|
|
55
|
+
*
|
|
56
|
+
* Returns once the broadcast succeeds. Callers that need on-chain finality
|
|
57
|
+
* should poll the returned tx hash before opening the channel.
|
|
58
|
+
*/
|
|
59
|
+
export declare function anchorMembershipBinding(binding: L2PSMembershipBinding, demos: Demos): Promise<AnchorMembershipBindingResult>;
|
|
60
|
+
/**
|
|
61
|
+
* Find the bound `ClaimReference` for `(channelId, subnetMemberId)`.
|
|
62
|
+
*
|
|
63
|
+
* Two-stage check on every candidate Storage Program:
|
|
64
|
+
* 1. Embedded signature verifies under the embedded claim's key.
|
|
65
|
+
* 2. SP owner's Demos address matches that claim's address — so only
|
|
66
|
+
* the actual key-holder could have deployed this SP under this name.
|
|
67
|
+
*
|
|
68
|
+
* Both checks must pass. Returns `null` when no candidate qualifies.
|
|
69
|
+
* Membership is fixed for channel lifetime (CH-1) so callers SHOULD cache
|
|
70
|
+
* the result for the session.
|
|
71
|
+
*/
|
|
72
|
+
export declare function resolveMember(channelId: string, subnetMemberId: string, rpcUrl: string): Promise<ClaimReference | null>;
|