@t2000/sdk 5.17.0 → 5.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -97,6 +97,9 @@ interface VerifyOptions {
97
97
  apiBase?: string;
98
98
  /** Sui network for the trustless anchor read (default `mainnet`). */
99
99
  network?: 'mainnet' | 'testnet';
100
+ /** Confidential model id for fetching the attested receipt-signing key
101
+ * (default `phala/glm-5.2`; the gateway key is workload-wide). */
102
+ model?: string;
100
103
  }
101
104
  /**
102
105
  * Verify a confidential response by its receipt id. Reads the signed receipt
package/dist/index.d.ts CHANGED
@@ -97,6 +97,9 @@ interface VerifyOptions {
97
97
  apiBase?: string;
98
98
  /** Sui network for the trustless anchor read (default `mainnet`). */
99
99
  network?: 'mainnet' | 'testnet';
100
+ /** Confidential model id for fetching the attested receipt-signing key
101
+ * (default `phala/glm-5.2`; the gateway key is workload-wide). */
102
+ model?: string;
100
103
  }
101
104
  /**
102
105
  * Verify a confidential response by its receipt id. Reads the signed receipt
package/dist/index.js CHANGED
@@ -11,6 +11,9 @@ import { decodeSuiPrivateKey } from '@mysten/sui/cryptography';
11
11
  import { access, mkdir, writeFile, readFile } from 'fs/promises';
12
12
  import { join, dirname, resolve } from 'path';
13
13
  import { homedir } from 'os';
14
+ import { secp256k1 } from '@noble/curves/secp256k1';
15
+ import { sha256 } from '@noble/hashes/sha256';
16
+ import { hexToBytes, bytesToHex } from '@noble/hashes/utils';
14
17
  import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
15
18
  import { SuinsTransaction } from '@mysten/suins';
16
19
 
@@ -2028,6 +2031,65 @@ var RECEIPT_ANCHORED_SUFFIX = "::anchor::ReceiptAnchored";
2028
2031
  function fullnodeUrl(network) {
2029
2032
  return network === "testnet" ? "https://fullnode.testnet.sui.io" : "https://fullnode.mainnet.sui.io";
2030
2033
  }
2034
+ function jcs(value) {
2035
+ if (value === null) {
2036
+ return "null";
2037
+ }
2038
+ if (typeof value === "boolean") {
2039
+ return value ? "true" : "false";
2040
+ }
2041
+ if (typeof value === "number") {
2042
+ if (!Number.isInteger(value)) {
2043
+ throw new Error("JCS: non-integer number");
2044
+ }
2045
+ return String(value);
2046
+ }
2047
+ if (typeof value === "string") {
2048
+ return JSON.stringify(value);
2049
+ }
2050
+ if (Array.isArray(value)) {
2051
+ return `[${value.map(jcs).join(",")}]`;
2052
+ }
2053
+ const keys = Object.keys(value).sort();
2054
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${jcs(value[k])}`).join(",")}}`;
2055
+ }
2056
+ function verifyReceiptSignature(receipt, signingKeyHex) {
2057
+ try {
2058
+ const sig = receipt.signature;
2059
+ if (sig?.algo !== "ecdsa-secp256k1" || !sig.value) {
2060
+ return false;
2061
+ }
2062
+ const canonical = {
2063
+ api_version: receipt.api_version ?? "",
2064
+ receipt_id: receipt.receipt_id ?? "",
2065
+ chat_id: receipt.chat_id ?? null,
2066
+ workload_id: receipt.workload_id ?? "",
2067
+ workload_keyset_digest: receipt.workload_keyset_digest ?? "",
2068
+ endpoint: receipt.endpoint ?? "",
2069
+ method: receipt.method ?? "",
2070
+ served_at: receipt.served_at ?? 0,
2071
+ event_log: receipt.event_log ?? [],
2072
+ signature: { algo: sig.algo, key_id: sig.key_id ?? "" }
2073
+ };
2074
+ const prehash = sha256(new TextEncoder().encode(jcs(canonical)));
2075
+ const sigBytes = hexToBytes(sig.value);
2076
+ if (sigBytes.length !== 65) {
2077
+ return false;
2078
+ }
2079
+ let v = sigBytes[64];
2080
+ if (v >= 27 && v <= 30) {
2081
+ v -= 27;
2082
+ }
2083
+ if (v > 3) {
2084
+ return false;
2085
+ }
2086
+ const recovered = secp256k1.Signature.fromCompact(sigBytes.slice(0, 64)).addRecoveryBit(v).recoverPublicKey(prehash).toHex(false);
2087
+ const endorsed = bytesToHex(hexToBytes(signingKeyHex.replace(/^0x/, "")));
2088
+ return recovered.toLowerCase() === endorsed.toLowerCase();
2089
+ } catch {
2090
+ return false;
2091
+ }
2092
+ }
2031
2093
  async function verifyReceipt(receiptId, opts = {}) {
2032
2094
  const base = opts.apiBase ?? DEFAULT_API_BASE;
2033
2095
  const network = opts.network ?? "mainnet";
@@ -2121,15 +2183,39 @@ async function verifyReceipt(receiptId, opts = {}) {
2121
2183
  });
2122
2184
  }
2123
2185
  }
2186
+ let signatureForged = false;
2187
+ let sigStatus = "skip";
2188
+ let sigDetail = "no signature on receipt";
2189
+ if (receipt.signature?.value) {
2190
+ try {
2191
+ const model = opts.model ?? "phala/glm-5.2";
2192
+ const res = await fetch(
2193
+ `${base}/aci/attestation?model=${encodeURIComponent(model)}`
2194
+ );
2195
+ const att = res.ok ? await res.json() : null;
2196
+ if (!att?.signingKey) {
2197
+ sigDetail = "could not fetch the attested keyset to check the signature";
2198
+ } else if (att.workloadId && att.workloadId !== workloadId) {
2199
+ sigDetail = `attested keyset is for a different workload \u2014 pass --model for ${workloadId}`;
2200
+ } else {
2201
+ const ok = verifyReceiptSignature(receipt, att.signingKey);
2202
+ sigStatus = ok ? "pass" : "fail";
2203
+ signatureForged = !ok;
2204
+ sigDetail = ok ? `signed by the attested receipt key (${receipt.signature.key_id ?? "key"})` : "signature does NOT recover the attested receipt key \u2014 forged/altered";
2205
+ }
2206
+ } catch {
2207
+ sigDetail = "signature check errored";
2208
+ }
2209
+ }
2124
2210
  checks.push({
2125
2211
  name: "Receipt signature",
2126
- status: "skip",
2127
- detail: receipt.signature ? "present (local recompute via RedPill keyset canonicalization = roadmap)" : "no signature on receipt",
2128
- trust: "roadmap"
2212
+ status: sigStatus,
2213
+ detail: sigDetail,
2214
+ trust: sigStatus === "skip" ? "roadmap" : "trustless"
2129
2215
  });
2130
2216
  return {
2131
2217
  receiptId,
2132
- verified: Boolean(wireHash && workloadId) && anchorVerified,
2218
+ verified: Boolean(wireHash && workloadId) && anchorVerified && !signatureForged,
2133
2219
  anchorVerified,
2134
2220
  checks,
2135
2221
  wireHash,