tersign 0.6.1 → 0.6.3
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 +1 -1
- package/dist/intercept-bin.js +5 -3
- package/dist/keystore.d.ts +14 -2
- package/dist/keystore.js +59 -12
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +20 -13
- package/dist/receipt/binding.d.ts +1 -1
- package/dist/receipt/binding.js +2 -2
- package/dist/receipt/eip712.js +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -178,7 +178,7 @@ Full URLs, readable without auth. If you are an agent, start here.
|
|
|
178
178
|
| Envelope API | `GET https://tersign.ai/v1/receipts/{digest}/envelope?venue={internet-court\|kleros\|uma\|generic}` |
|
|
179
179
|
| Ledger stats | `GET https://tersign.ai/v1/stats` |
|
|
180
180
|
| Ledger signer | `GET https://tersign.ai/v1/ledger` |
|
|
181
|
-
| Bundle verifier, out-of-band | https://tersign.ai/verify/v1/
|
|
181
|
+
| Bundle verifier, out-of-band | Fetch the checker from the address the archive's `VERIFY.md` section 0 names: the content address of the release the archive was built with, `https://tersign.ai/verify/sha256/<sha256 of its SHA256SUMS>/` (archives whose VERIFY.md names https://tersign.ai/verify/v1/ use that). Each release serves `verify_bundle.py` · `keccak.py` · `secp256k1.py` · `SHA256SUMS`, and its files never change; https://tersign.ai/verify/releases.json lists every release. A bundle ships its own checker; for evidence from an interested party fetch that release and diff the two. |
|
|
182
182
|
| llms.txt | https://raw.githubusercontent.com/tersignhq/tersign-js/main/llms.txt |
|
|
183
183
|
| Conformance vectors (RFC 8785 + keccak256, two-sided) | https://github.com/tersignhq/evidence-record-conformance |
|
|
184
184
|
| Sample compliance-fields record + digests | https://github.com/tersignhq/tersign-js/blob/main/test/fixtures/compliance-record.json |
|
package/dist/intercept-bin.js
CHANGED
|
@@ -25,7 +25,7 @@ import { DEFAULT_EVENTS, McpCapture } from './intercept/capture.js';
|
|
|
25
25
|
import { parseInterceptFlags } from './intercept/flags.js';
|
|
26
26
|
import { startIntercept } from './intercept/proxy.js';
|
|
27
27
|
import { EvidenceSink } from './intercept/sink.js';
|
|
28
|
-
import { resolveSignerKey } from './keystore.js';
|
|
28
|
+
import { ledgerApiKeyUnlessPlaceholder, resolveSignerKey } from './keystore.js';
|
|
29
29
|
const USAGE = 'usage: tersign intercept [--events tools/call,prompts/get] [--agent-id id] [--ledger url] -- <mcp server command> [args…]\n' +
|
|
30
30
|
'hosted ledger mode (TERSIGN_LEDGER_API_KEY + TERSIGN_LEDGER_SELLER_ID) requires the signer key\n' +
|
|
31
31
|
'registered for that sellerId — set TERSIGN_SELLER_KEY; rejected records fall back to ~/.tersign.\n' +
|
|
@@ -53,10 +53,12 @@ for (const e of (flags.events ?? '').split(',')) {
|
|
|
53
53
|
const envAgentId = process.env.TERSIGN_AGENT_ID;
|
|
54
54
|
const agentId = flags.agentId ?? (envAgentId !== undefined && envAgentId !== '' ? envAgentId : 'mcp-intercept');
|
|
55
55
|
const ledgerUrl = flags.ledger ?? process.env.TERSIGN_LEDGER_URL ?? 'https://tersign.ai';
|
|
56
|
-
|
|
56
|
+
// An MCP client launches this process, so an unsubstituted placeholder such as
|
|
57
|
+
// `${TERSIGN_LEDGER_API_KEY}` counts as unset here, as it does for the MCP server.
|
|
58
|
+
const apiKey = ledgerApiKeyUnlessPlaceholder(process.env.TERSIGN_LEDGER_API_KEY);
|
|
57
59
|
const sellerId = process.env.TERSIGN_LEDGER_SELLER_ID;
|
|
58
60
|
try {
|
|
59
|
-
const { key, source } = resolveSignerKey({ create: true });
|
|
61
|
+
const { key, source } = resolveSignerKey({ create: true, placeholderAsUnset: true });
|
|
60
62
|
const account = privateKeyToAccount(key);
|
|
61
63
|
console.error(`signing key: ${account.address} (${source})`);
|
|
62
64
|
const sink = new EvidenceSink({
|
package/dist/keystore.d.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* deployer by construction.
|
|
5
5
|
*
|
|
6
6
|
* Priority (first hit wins):
|
|
7
|
-
* 1. TERSIGN_SELLER_KEY env — explicit override; headless/CI/agent contract.
|
|
7
|
+
* 1. TERSIGN_SELLER_KEY env — explicit override; headless/CI/agent contract. Empty counts as
|
|
8
|
+
* unset; so does an unsubstituted placeholder such as `${TERSIGN_SELLER_KEY}`, but only for a
|
|
9
|
+
* caller that passes `placeholderAsUnset` (a process an MCP client launches).
|
|
8
10
|
* 2. macOS keychain, service `tersign-signer` — the at-rest default on darwin.
|
|
9
11
|
* 3. keyfile ~/.tersign/signer.key (0600) — portable fallback; created with a warning
|
|
10
12
|
* recommending the env/keychain paths.
|
|
@@ -17,7 +19,17 @@ export interface ResolvedSignerKey {
|
|
|
17
19
|
}
|
|
18
20
|
/** Resolve the deployer signing key. With `create: true`, a missing key is generated and
|
|
19
21
|
* persisted (keychain on darwin, else a 0600 keyfile); without it, resolution failure throws
|
|
20
|
-
* with the wiring instructions.
|
|
22
|
+
* with the wiring instructions. `env` is where TERSIGN_SELLER_KEY is read (default
|
|
23
|
+
* process.env). With `placeholderAsUnset: true` (passed only where an MCP client launches the
|
|
24
|
+
* process: the MCP server and `tersign intercept`), an unsubstituted placeholder there is treated
|
|
25
|
+
* as unset, and one stderr line says which key was used instead (stderr, never stdout: stdout is
|
|
26
|
+
* the MCP stdio channel). Without it, a placeholder gets the clear error like any malformed key. */
|
|
21
27
|
export declare function resolveSignerKey(opts?: {
|
|
22
28
|
create?: boolean;
|
|
29
|
+
env?: Record<string, string | undefined>;
|
|
30
|
+
placeholderAsUnset?: boolean;
|
|
23
31
|
}): ResolvedSignerKey;
|
|
32
|
+
/** TERSIGN_LEDGER_API_KEY as given, except that an unsubstituted placeholder is treated as unset,
|
|
33
|
+
* with one stderr line, so it is never sent as a credential. Only for processes an MCP client
|
|
34
|
+
* launches (the MCP server and `tersign intercept`); `""` and every other value pass through. */
|
|
35
|
+
export declare function ledgerApiKeyUnlessPlaceholder(value: string | undefined): string | undefined;
|
package/dist/keystore.js
CHANGED
|
@@ -2,9 +2,25 @@ import { execFileSync } from 'node:child_process';
|
|
|
2
2
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { homedir, userInfo } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
import { generatePrivateKey } from 'viem/accounts';
|
|
5
|
+
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
|
|
6
6
|
const KEY_PATTERN = /^0x[0-9a-fA-F]{64}$/;
|
|
7
7
|
const KEYCHAIN_SERVICE = 'tersign-signer';
|
|
8
|
+
// A client config can carry `"TERSIGN_SELLER_KEY": "${TERSIGN_SELLER_KEY}"` for every secret a
|
|
9
|
+
// server declares. When that variable is not set where the client runs, a client can pass the
|
|
10
|
+
// text through unexpanded, so the server receives the placeholder itself.
|
|
11
|
+
// Only a WHOLE value of one of these shapes counts: `${NAME}`, `${NAME:-default}`,
|
|
12
|
+
// `${NAME-default}` or `$NAME`.
|
|
13
|
+
const PLACEHOLDER_PATTERN = /^\$\{[A-Za-z_][A-Za-z0-9_]*(:?-[^}]*)?\}$|^\$[A-Za-z_][A-Za-z0-9_]*$/;
|
|
14
|
+
/** True when an environment value is an unexpanded variable reference rather than a value. */
|
|
15
|
+
function isUnexpandedPlaceholder(value) {
|
|
16
|
+
return PLACEHOLDER_PATTERN.test(value);
|
|
17
|
+
}
|
|
18
|
+
/** The placeholder as it may be printed: a `:-default` / `-default` part is elided, since a
|
|
19
|
+
* default can itself be a secret. */
|
|
20
|
+
function shownPlaceholder(value) {
|
|
21
|
+
const withDefault = /^\$\{([A-Za-z_][A-Za-z0-9_]*)(:?-)/.exec(value);
|
|
22
|
+
return withDefault ? `\${${withDefault[1]}${withDefault[2]}…}` : value;
|
|
23
|
+
}
|
|
8
24
|
function keyfilePath() {
|
|
9
25
|
return join(homedir(), '.tersign', 'signer.key');
|
|
10
26
|
}
|
|
@@ -48,35 +64,66 @@ function writeKeychain(key) {
|
|
|
48
64
|
}
|
|
49
65
|
/** Resolve the deployer signing key. With `create: true`, a missing key is generated and
|
|
50
66
|
* persisted (keychain on darwin, else a 0600 keyfile); without it, resolution failure throws
|
|
51
|
-
* with the wiring instructions.
|
|
67
|
+
* with the wiring instructions. `env` is where TERSIGN_SELLER_KEY is read (default
|
|
68
|
+
* process.env). With `placeholderAsUnset: true` (passed only where an MCP client launches the
|
|
69
|
+
* process: the MCP server and `tersign intercept`), an unsubstituted placeholder there is treated
|
|
70
|
+
* as unset, and one stderr line says which key was used instead (stderr, never stdout: stdout is
|
|
71
|
+
* the MCP stdio channel). Without it, a placeholder gets the clear error like any malformed key. */
|
|
52
72
|
export function resolveSignerKey(opts = {}) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
const value = (opts.env ?? process.env).TERSIGN_SELLER_KEY;
|
|
74
|
+
const placeholder = opts.placeholderAsUnset === true && value !== undefined && isUnexpandedPlaceholder(value) ? value : undefined;
|
|
75
|
+
if (value !== undefined && value !== '' && placeholder === undefined) {
|
|
76
|
+
if (!KEY_PATTERN.test(value))
|
|
56
77
|
throw new Error('TERSIGN_SELLER_KEY must be a 0x-prefixed 32-byte hex key');
|
|
57
|
-
return { key:
|
|
78
|
+
return { key: value, source: 'env' };
|
|
79
|
+
}
|
|
80
|
+
const resolved = resolveStoredOrNew(opts.create === true, placeholder);
|
|
81
|
+
if (placeholder !== undefined) {
|
|
82
|
+
const where = resolved.generated
|
|
83
|
+
? `a newly generated key (kept in the ${resolved.source === 'keychain' ? 'OS keychain' : `keyfile ${keyfilePath()}`})`
|
|
84
|
+
: resolved.source === 'keychain'
|
|
85
|
+
? `the key from the OS keychain (service ${KEYCHAIN_SERVICE})`
|
|
86
|
+
: `the key from the keyfile ${keyfilePath()}`;
|
|
87
|
+
console.error(`tersign: TERSIGN_SELLER_KEY held the literal, unsubstituted text ${shownPlaceholder(placeholder)} and was ` +
|
|
88
|
+
`treated as unset; signing with ${where}, address ${privateKeyToAccount(resolved.key).address}.`);
|
|
58
89
|
}
|
|
90
|
+
return { key: resolved.key, source: resolved.source };
|
|
91
|
+
}
|
|
92
|
+
/** TERSIGN_LEDGER_API_KEY as given, except that an unsubstituted placeholder is treated as unset,
|
|
93
|
+
* with one stderr line, so it is never sent as a credential. Only for processes an MCP client
|
|
94
|
+
* launches (the MCP server and `tersign intercept`); `""` and every other value pass through. */
|
|
95
|
+
export function ledgerApiKeyUnlessPlaceholder(value) {
|
|
96
|
+
if (value === undefined || !isUnexpandedPlaceholder(value))
|
|
97
|
+
return value;
|
|
98
|
+
console.error(`tersign: TERSIGN_LEDGER_API_KEY held the literal, unsubstituted text ${shownPlaceholder(value)} and was ` +
|
|
99
|
+
'treated as unset; no API key is sent to the ledger.');
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
function resolveStoredOrNew(create, placeholder) {
|
|
59
103
|
const fromKeychain = readKeychain();
|
|
60
104
|
if (fromKeychain)
|
|
61
|
-
return { key: fromKeychain, source: 'keychain' };
|
|
105
|
+
return { key: fromKeychain, source: 'keychain', generated: false };
|
|
62
106
|
const file = keyfilePath();
|
|
63
107
|
if (existsSync(file)) {
|
|
64
108
|
const raw = readFileSync(file, 'utf8').trim();
|
|
65
109
|
if (!KEY_PATTERN.test(raw))
|
|
66
110
|
throw new Error(`${file} does not contain a 0x-prefixed 32-byte hex key`);
|
|
67
|
-
return { key: raw, source: 'keyfile' };
|
|
111
|
+
return { key: raw, source: 'keyfile', generated: false };
|
|
68
112
|
}
|
|
69
|
-
if (!
|
|
70
|
-
throw new Error(
|
|
113
|
+
if (!create) {
|
|
114
|
+
throw new Error((placeholder !== undefined
|
|
115
|
+
? `TERSIGN_SELLER_KEY held the unexpanded placeholder ${shownPlaceholder(placeholder)} and was treated as unset; `
|
|
116
|
+
: '') +
|
|
117
|
+
'no signing key found — set TERSIGN_SELLER_KEY, store one in the macOS keychain ' +
|
|
71
118
|
`(security add-generic-password -s ${KEYCHAIN_SERVICE} -a $USER -w 0x…), or rerun with key creation enabled`);
|
|
72
119
|
}
|
|
73
120
|
const key = generatePrivateKey();
|
|
74
121
|
if (writeKeychain(key))
|
|
75
|
-
return { key, source: 'keychain' };
|
|
122
|
+
return { key, source: 'keychain', generated: true };
|
|
76
123
|
mkdirSync(join(homedir(), '.tersign'), { recursive: true, mode: 0o700 });
|
|
77
124
|
writeFileSync(file, `${key}\n`, { mode: 0o600 });
|
|
78
125
|
chmodSync(file, 0o600);
|
|
79
126
|
console.error(`tersign: generated a new signing key at ${file} (0600). This key IS your evidence identity — ` +
|
|
80
127
|
'back it up, and prefer TERSIGN_SELLER_KEY or the OS keychain on shared machines.');
|
|
81
|
-
return { key, source: 'keyfile' };
|
|
128
|
+
return { key, source: 'keyfile', generated: true };
|
|
82
129
|
}
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -8,6 +8,6 @@ export declare function envDeps(env?: Record<string, string | undefined>): McpDe
|
|
|
8
8
|
* every client; mcp.test.ts pins it against package.json so a release bump can't drift it. */
|
|
9
9
|
export declare const MCP_SERVER_IDENTITY: {
|
|
10
10
|
readonly name: "tersign";
|
|
11
|
-
readonly version: "0.6.
|
|
11
|
+
readonly version: "0.6.3";
|
|
12
12
|
};
|
|
13
13
|
export declare function buildServer(deps: McpDeps): McpServer;
|
package/dist/mcp/server.js
CHANGED
|
@@ -3,7 +3,7 @@ import { z } from 'zod';
|
|
|
3
3
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
4
|
import { Assure } from '../assure.js';
|
|
5
5
|
import { LedgerClient } from '../ledgerClient.js';
|
|
6
|
-
import { resolveSignerKey } from '../keystore.js';
|
|
6
|
+
import { ledgerApiKeyUnlessPlaceholder, resolveSignerKey } from '../keystore.js';
|
|
7
7
|
import { adjudicateDisputeTool, getDisputeTool, issueReceiptTool, openDisputeTool, recordDisclosureTool, recordRefundTool, submitEvidenceTool, verifyReceiptTool, verifyRecordTool, } from './tools.js';
|
|
8
8
|
/** MCP packaging: exposes assure as tools any MCP-speaking agent can call, so an agent
|
|
9
9
|
* (or its framework) can issue, verify, and chain receipts without importing the SDK.
|
|
@@ -14,12 +14,19 @@ export function envDeps(env = process.env) {
|
|
|
14
14
|
// that first call self-provisions a signer-keyed account; before this the MCP entry point threw
|
|
15
15
|
// instead, so `npx tersign` died on first run for anyone who had not already exported a key —
|
|
16
16
|
// and no directory or sandbox could introspect the server at all.
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
// Key precedence as in 0.6.2: `env`'s TERSIGN_SELLER_KEY when non-empty, else process.env's
|
|
18
|
+
// (`||`: an EMPTY value means unset — a client may fill a blank optional secret with ""; fixed
|
|
19
|
+
// 2026-09-27). The value is then read by the keystore itself, never handed raw to
|
|
20
|
+
// privateKeyToAccount, so a malformed value gets the keystore's clear error instead of a crypto
|
|
21
|
+
// library's. An MCP client launches this process, so an unsubstituted placeholder such as
|
|
22
|
+
// `${TERSIGN_SELLER_KEY}` also counts as unset (until 0.6.3 that text went straight to
|
|
23
|
+
// privateKeyToAccount and the server died at startup). test/listing.test.ts starts the registry
|
|
24
|
+
// command with the key set to "" and to the placeholder.
|
|
25
|
+
const sellerKey = env.TERSIGN_SELLER_KEY || process.env.TERSIGN_SELLER_KEY;
|
|
26
|
+
const account = privateKeyToAccount(resolveSignerKey({ create: true, env: { TERSIGN_SELLER_KEY: sellerKey }, placeholderAsUnset: true }).key);
|
|
27
|
+
// The listing marks the ledger API key secret too, so the same placeholder can reach it: treat
|
|
28
|
+
// it as unset rather than send it as a credential. "" keeps its old meaning.
|
|
29
|
+
const apiKey = ledgerApiKeyUnlessPlaceholder(env.TERSIGN_LEDGER_API_KEY);
|
|
23
30
|
const assure = new Assure({
|
|
24
31
|
signer: account,
|
|
25
32
|
issuer: {
|
|
@@ -27,12 +34,12 @@ export function envDeps(env = process.env) {
|
|
|
27
34
|
jurisdiction: env.TERSIGN_ISSUER_JURISDICTION ?? 'unknown',
|
|
28
35
|
...(env.TERSIGN_ISSUER_TAX_ID !== undefined ? { taxId: env.TERSIGN_ISSUER_TAX_ID } : {}),
|
|
29
36
|
},
|
|
30
|
-
...(env.TERSIGN_LEDGER_URL &&
|
|
31
|
-
? { ledger: { url: env.TERSIGN_LEDGER_URL, apiKey
|
|
37
|
+
...(env.TERSIGN_LEDGER_URL && apiKey && env.TERSIGN_LEDGER_SELLER_ID
|
|
38
|
+
? { ledger: { url: env.TERSIGN_LEDGER_URL, apiKey, sellerId: env.TERSIGN_LEDGER_SELLER_ID } }
|
|
32
39
|
: {}),
|
|
33
40
|
});
|
|
34
|
-
const ledger = env.TERSIGN_LEDGER_URL &&
|
|
35
|
-
? new LedgerClient({ url: env.TERSIGN_LEDGER_URL, apiKey
|
|
41
|
+
const ledger = env.TERSIGN_LEDGER_URL && apiKey && env.TERSIGN_LEDGER_SELLER_ID
|
|
42
|
+
? new LedgerClient({ url: env.TERSIGN_LEDGER_URL, apiKey, sellerId: env.TERSIGN_LEDGER_SELLER_ID })
|
|
36
43
|
: undefined;
|
|
37
44
|
return {
|
|
38
45
|
assure,
|
|
@@ -42,7 +49,7 @@ export function envDeps(env = process.env) {
|
|
|
42
49
|
? {
|
|
43
50
|
ledgerHttp: {
|
|
44
51
|
url: env.TERSIGN_LEDGER_URL,
|
|
45
|
-
...(
|
|
52
|
+
...(apiKey !== undefined ? { apiKey } : {}),
|
|
46
53
|
},
|
|
47
54
|
}
|
|
48
55
|
: {}),
|
|
@@ -53,7 +60,7 @@ function json(value) {
|
|
|
53
60
|
}
|
|
54
61
|
/** MUST match package.json name/version — the MCP handshake self-reports this identity to
|
|
55
62
|
* every client; mcp.test.ts pins it against package.json so a release bump can't drift it. */
|
|
56
|
-
export const MCP_SERVER_IDENTITY = { name: 'tersign', version: '0.6.
|
|
63
|
+
export const MCP_SERVER_IDENTITY = { name: 'tersign', version: '0.6.3' };
|
|
57
64
|
export function buildServer(deps) {
|
|
58
65
|
const server = new McpServer(MCP_SERVER_IDENTITY);
|
|
59
66
|
server.registerTool('issue_receipt', {
|
|
@@ -51,7 +51,7 @@ export declare function bindSigner(signer: `0x${string}`, expected: string | und
|
|
|
51
51
|
*
|
|
52
52
|
* Scope: verifyReceipt, verifyComplianceRecord and the CLI and MCP tools built on them. The
|
|
53
53
|
* action-record, dispute and evidence verifiers (evidence/action.ts, dispute/sign.ts) do NOT call
|
|
54
|
-
* this and still accept what viem accepts
|
|
54
|
+
* this and still accept what viem accepts. Neither does the ledger's ingest
|
|
55
55
|
* (SECURITY-AUDIT A8), so a receipt the ledger counter-signed with a non-canonical signature is
|
|
56
56
|
* refused here. The reasons are package text only: nothing from the file is echoed. */
|
|
57
57
|
export declare function signatureError(sig: unknown): string | undefined;
|
package/dist/receipt/binding.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* verifyComplianceRecord, the `tersign verify` CLI and the MCP verify tools, so those four
|
|
3
3
|
* surfaces cannot drift apart on the one question a verify result is read for. verifyActionRecord,
|
|
4
4
|
* verifyDispute and verifyEvidence do NOT use it: they have no signer binding and no canonical
|
|
5
|
-
* signature check
|
|
5
|
+
* signature check.
|
|
6
6
|
*
|
|
7
7
|
* ECDSA recovery yields an address for ANY payload and ANY well-formed signature. An edited
|
|
8
8
|
* receipt therefore still "verifies" — it recovers a different address. A recovered signer is
|
|
@@ -67,7 +67,7 @@ const SECP256K1_N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0
|
|
|
67
67
|
*
|
|
68
68
|
* Scope: verifyReceipt, verifyComplianceRecord and the CLI and MCP tools built on them. The
|
|
69
69
|
* action-record, dispute and evidence verifiers (evidence/action.ts, dispute/sign.ts) do NOT call
|
|
70
|
-
* this and still accept what viem accepts
|
|
70
|
+
* this and still accept what viem accepts. Neither does the ledger's ingest
|
|
71
71
|
* (SECURITY-AUDIT A8), so a receipt the ledger counter-signed with a non-canonical signature is
|
|
72
72
|
* refused here. The reasons are package text only: nothing from the file is echoed. */
|
|
73
73
|
export function signatureError(sig) {
|
package/dist/receipt/eip712.js
CHANGED
|
@@ -118,6 +118,17 @@ function signedFieldError(p) {
|
|
|
118
118
|
}
|
|
119
119
|
return undefined;
|
|
120
120
|
}
|
|
121
|
+
/** x402 offer-and-receipt §5.5 step 2: payload.version selects the EIP-712 types, and "currently
|
|
122
|
+
* only version 1 is defined". Without this a version-2 payload recovered under the version-1
|
|
123
|
+
* types and verified valid. Runs after signedFieldError, so version is already a JSON integer
|
|
124
|
+
* (a string "1" is refused there with its own reason); the comparison is strict anyway, so it
|
|
125
|
+
* does not lean on that order. The Python twin raises the same text. */
|
|
126
|
+
function receiptVersionError(p) {
|
|
127
|
+
const v = p.version;
|
|
128
|
+
if (v === 1 || v === 1n)
|
|
129
|
+
return undefined;
|
|
130
|
+
return `payload.version ${String(v)} is not supported: version 1 is the only receipt version the x402 offer-and-receipt extension defines`;
|
|
131
|
+
}
|
|
121
132
|
/** Verify an EIP-712 receipt. `expectedSigner` implements the spec's payTo-key authorization
|
|
122
133
|
* model; pass the seller's payTo address (or a registry-resolved key), obtained out-of-band,
|
|
123
134
|
* to enforce it. Without it the recovered signer is UNAUTHENTICATED (signerBound:false). */
|
|
@@ -136,6 +147,9 @@ export async function verifyReceipt(artifact, expectedSigner) {
|
|
|
136
147
|
const badField = signedFieldError(artifact.payload);
|
|
137
148
|
if (badField)
|
|
138
149
|
return { valid: false, signerBound: false, reason: badField };
|
|
150
|
+
const badVersion = receiptVersionError(artifact.payload);
|
|
151
|
+
if (badVersion)
|
|
152
|
+
return { valid: false, signerBound: false, reason: badVersion };
|
|
139
153
|
const badSig = signatureError(artifact.signature);
|
|
140
154
|
if (badSig)
|
|
141
155
|
return { valid: false, signerBound: false, reason: badSig };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tersign",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Tersign \u2014 the evidence layer for the agent economy. Counter-signed receipts, agent action records, idempotency enforcement, refunds, disputes, and jury-ready evidence envelopes for x402/agent-commerce sellers.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|