@stasho/vf-records 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,5 +9,13 @@ what a prepared publish or transfer message should say and compares it
9
9
  against what the signer actually asked for, before that signer's key ever
10
10
  touches the message.
11
11
 
12
+ The format it checks is public: the Verified Frontends signing format page
13
+ (https://docs.stasho.xyz/reference/vf-signing-format.html) specifies the
14
+ request envelope, the prepare/submit endpoints, the transaction and every
15
+ check, for clients that do not want to run this package.
16
+ `examples/reference-client.ts` (in the repository) is a complete publish client that imports no
17
+ `@stasho/*` code; `tests/reference-client.test.ts` holds its check to the
18
+ same verdicts as `checkPreparedMessage`.
19
+
12
20
  Zero runtime dependencies, no Node-only imports — safe to import from a
13
21
  browser or from `@stasho/vf`.
@@ -34,7 +34,15 @@ export function checkPreparedMessage(args) {
34
34
  return refuse("instruction program is not the memo program");
35
35
  if (ix.accounts.length !== 1 || ix.accounts[0] !== 1)
36
36
  return refuse("your key must be the instruction's only account");
37
- const payload = new TextDecoder().decode(ix.data);
37
+ // Fatal + ignoreBOM: a leading BOM or an invalid byte is refused, never repaired
38
+ // into a string that compares equal while the on-chain bytes do not parse.
39
+ let payload;
40
+ try {
41
+ payload = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(ix.data);
42
+ }
43
+ catch {
44
+ return refuse("instruction data is not valid UTF-8");
45
+ }
38
46
  const record = parsePayload(payload);
39
47
  if (record === null)
40
48
  return refuse("instruction data is not a stasho-vf record");
@@ -56,8 +64,13 @@ export function checkPreparedMessage(args) {
56
64
  if (record.kind === "accept" && args.expected.kind === "accept" && record.transfer !== args.expected.transfer) {
57
65
  return refuse(`accept names transfer ${record.transfer}, you asked for ${args.expected.transfer}`);
58
66
  }
67
+ // `Date.parse` also accepts legacy formats and ignores parenthesised text, so
68
+ // only the exact `toISOString()` form the server emits is admitted: `t` must
69
+ // not carry anything but a time onto the customer's signed record.
59
70
  const t = Date.parse(record.ts);
60
- if (!Number.isFinite(t) || Math.abs(t - args.now) > CLIENT_CHECK_SKEW_MS)
71
+ if (!Number.isFinite(t) || new Date(t).toISOString() !== record.ts)
72
+ return refuse("record timestamp is not an ISO 8601 UTC time");
73
+ if (Math.abs(t - args.now) > CLIENT_CHECK_SKEW_MS)
61
74
  return refuse("record timestamp is more than 10 minutes from this clock");
62
75
  return { ok: true, payload, record };
63
76
  }
@@ -16,6 +16,10 @@ class Reader {
16
16
  let value = 0;
17
17
  for (let shift = 0; shift <= 14; shift += 7) {
18
18
  const b = this.u8();
19
+ // A trailing zero group re-encodes the same length (an "alias"), which
20
+ // Solana's own short_vec decoder refuses too.
21
+ if (b === 0 && shift > 0)
22
+ throw new RangeError("compact-u16 is not minimally encoded");
19
23
  value |= (b & 0x7f) << shift;
20
24
  if ((b & 0x80) === 0)
21
25
  return value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stasho/vf-records",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Stasho Verified Frontends record codec + the client-side check of a prepared publish message. Zero dependencies, browser-safe.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  "build": "tsc -p tsconfig.build.json",
20
20
  "typecheck": "tsc --noEmit",
21
21
  "test": "vitest run",
22
- "lint": "oxlint src tests",
22
+ "lint": "oxlint src tests examples",
23
23
  "prepublishOnly": "npm run build"
24
24
  },
25
25
  "devDependencies": {