sello 0.1.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/LICENSE +200 -0
- package/README.md +195 -0
- package/SPEC.md +738 -0
- package/docs/assets/sello-banner.png +0 -0
- package/docs/assets/sello-social-preview.png +0 -0
- package/docs/decisions.md +79 -0
- package/docs/paper/notarized-agents.md +523 -0
- package/docs/paper/notarized-agents.pdf +0 -0
- package/docs/paper/notarized-agents.tex +1387 -0
- package/docs/paper/refs.bib +245 -0
- package/docs/performance.md +24 -0
- package/docs/release-checklist.md +56 -0
- package/docs/sdk-build-plan.md +214 -0
- package/docs/sdk-quickstart.md +115 -0
- package/docs/sdk-security-audit.md +53 -0
- package/docs/security-review.md +54 -0
- package/examples/mcp-tool-server.ts +250 -0
- package/examples/quickstart-tool.ts +178 -0
- package/fixtures/vectors/.gitkeep +1 -0
- package/fixtures/vectors/sello-v0.1.json +101 -0
- package/package.json +52 -0
- package/src/cbor.ts +337 -0
- package/src/cli/bench.ts +390 -0
- package/src/cli/demo.ts +114 -0
- package/src/cli/sello.ts +514 -0
- package/src/cose/protected-header.ts +210 -0
- package/src/cose/sign1.ts +124 -0
- package/src/crypto/ed25519.ts +117 -0
- package/src/crypto/identifiers.ts +64 -0
- package/src/hpke/base.ts +349 -0
- package/src/hpke/receipt.ts +79 -0
- package/src/index.ts +15 -0
- package/src/log/canonical-url.ts +168 -0
- package/src/log/mock-log.ts +170 -0
- package/src/log/rekor.ts +147 -0
- package/src/log/types.ts +27 -0
- package/src/mcp/middleware.ts +198 -0
- package/src/owner/verify.ts +276 -0
- package/src/receipt/body.ts +210 -0
- package/src/registry/json-registry.ts +233 -0
- package/src/sdk/index.ts +22 -0
- package/src/sdk/keys.ts +191 -0
- package/src/sdk/logs.ts +200 -0
- package/src/sdk/publisher.ts +145 -0
- package/src/sdk/service.ts +562 -0
- package/src/service/create-receipt.ts +178 -0
- package/src/token/jws-profile.ts +174 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# SDK Security Audit Notes
|
|
2
|
+
|
|
3
|
+
These notes cover the first Stripe-style SDK implementation pass. They are written for reviewers who need the full plan context: production Rekor proof verification, hosted dashboard key delegation, managed remote signing, and durable on-disk queues remain deferred.
|
|
4
|
+
|
|
5
|
+
## Phase 0: Contract And Docs
|
|
6
|
+
|
|
7
|
+
- Service emission and owner viewing are documented as separate environments.
|
|
8
|
+
- The service process is not required to hold `SELLO_OWNER_KEY`.
|
|
9
|
+
- Hosted `sello.build` is described as optional convenience, not as a protocol dependency.
|
|
10
|
+
- Deferred production features are named in `docs/sdk-build-plan.md`.
|
|
11
|
+
|
|
12
|
+
## Phase 1: Env-First Facade
|
|
13
|
+
|
|
14
|
+
- `sello.service()` accepts env config, service-id override, or explicit config.
|
|
15
|
+
- Missing-config errors are actionable and do not print key material.
|
|
16
|
+
- `sello inspect-env` redacts key and secret values.
|
|
17
|
+
- Hosted `SELLO_SECRET_KEY` mode fetches config, but local receipt signing remains the default trust model.
|
|
18
|
+
|
|
19
|
+
## Phase 2: Build/Submit Split
|
|
20
|
+
|
|
21
|
+
- Receipt cryptography remains unchanged: the same protected headers, HPKE payload, and COSE_Sign1 envelope are produced before append.
|
|
22
|
+
- Existing `createReceipt()` behavior is preserved by composing build plus append.
|
|
23
|
+
- Background submission is bounded and exposes `flush()`, `onSubmitError`, and `onDrop`.
|
|
24
|
+
- Background mode is low-latency, not a strict durability guarantee; `submit.mode: "await"` remains available.
|
|
25
|
+
|
|
26
|
+
## Phase 3: Tool Wrapper
|
|
27
|
+
|
|
28
|
+
- Token verification happens before handler execution.
|
|
29
|
+
- Invalid tokens prevent the handler from running and emit no receipt.
|
|
30
|
+
- Success, error, and denied paths emit receipts without including plaintext request or response bodies.
|
|
31
|
+
- The wrapper uses the configured service identity and key for every receipt.
|
|
32
|
+
|
|
33
|
+
## Phase 4: Logs And Action Viewing
|
|
34
|
+
|
|
35
|
+
- Local `/actions` and `sello actions` use owner-side verification and HPKE decryption.
|
|
36
|
+
- Public log entries remain encrypted.
|
|
37
|
+
- Viewing details requires `SELLO_OWNER_KEY` or local dev state created by `sello dev`.
|
|
38
|
+
- Production registry URLs require a registry signature and trust root before `sello actions` will use them.
|
|
39
|
+
|
|
40
|
+
## Phase 5: Docs And First-Run Flow
|
|
41
|
+
|
|
42
|
+
- The quickstart tool reads ignored local dev state from `.sello/dev.json`; it does not print the service key, owner key, or agent token.
|
|
43
|
+
- The example uses `submit: { mode: "await" }` so the command succeeds only after the receipt append completes.
|
|
44
|
+
- The example canonicalizes only tool input fields and excludes the authorization token wrapper from the action input hash.
|
|
45
|
+
- The MCP-style example reads the bearer token from the transport header, but hashes only the `tools/call` method and params.
|
|
46
|
+
- README and quickstart docs keep self-hosting first-class and describe `sello.build` as optional convenience.
|
|
47
|
+
|
|
48
|
+
## Residual Risks
|
|
49
|
+
|
|
50
|
+
- Background submission can drop receipts under sustained pressure if the bounded queue fills. This is surfaced through `onDrop`; durable queues are deferred.
|
|
51
|
+
- The HTTP log adapter defines a minimal Sello-compatible JSON transport. Production transparency-log proof formats need dedicated adapters.
|
|
52
|
+
- JWKS support currently selects the first Ed25519 OKP key. Key selection by token `kid` should be added before relying on multi-key issuers.
|
|
53
|
+
- Hosted dashboard decryption is not implemented; any hosted viewer must use client-side decryption or explicit delegated viewer keys.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Security Review Notes
|
|
2
|
+
|
|
3
|
+
Date: 2026-05-28
|
|
4
|
+
|
|
5
|
+
Scope: implementation-level review of the TypeScript reference implementation's crypto API usage. This is not an external cryptographic audit.
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
The reference implementation keeps the cryptographic surface deliberately small:
|
|
10
|
+
|
|
11
|
+
- SHA-256 uses Node's `createHash`.
|
|
12
|
+
- Ed25519 signing and verification use Node's built-in Ed25519 support.
|
|
13
|
+
- X25519 key agreement uses Node's built-in X25519 support through `diffieHellman`.
|
|
14
|
+
- HPKE v0.1 support is limited to the single specified suite: DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, ChaCha20-Poly1305.
|
|
15
|
+
- COSE_Sign1 support is limited to the Sello profile: protected header bytes, empty unprotected map, embedded payload, and Ed25519 signature.
|
|
16
|
+
- JWS support is limited to compact JWS with `alg: "EdDSA"` and no `crit` header.
|
|
17
|
+
|
|
18
|
+
The implementation passes the current end-to-end test suite and pins HPKE behavior against RFC 9180 Appendix A.2.1.
|
|
19
|
+
|
|
20
|
+
## Findings
|
|
21
|
+
|
|
22
|
+
No release-blocking issues were found in this pass.
|
|
23
|
+
|
|
24
|
+
One small hardening change was made during review: base64url decoding now rejects impossible unpadded lengths (`length % 4 === 1`) before handing data to Node's decoder. Tests cover this for JWS token claims and registry signatures.
|
|
25
|
+
|
|
26
|
+
## Positive Checks
|
|
27
|
+
|
|
28
|
+
- JWS payload claims are parsed only after signature verification.
|
|
29
|
+
- COSE signature verification uses the exact protected-header bytes from the envelope, not a reserialized header map.
|
|
30
|
+
- HPKE `aad` is the exact protected-header byte string.
|
|
31
|
+
- HPKE `info` binds the Sello suite label, registry-resolved service identifier, and `sello_token_ref`.
|
|
32
|
+
- X25519 all-zero shared secrets are rejected.
|
|
33
|
+
- Revocation checks use log integrated time, not service-asserted receipt timestamps.
|
|
34
|
+
- Log identity is checked by canonical URL equality between the signed header and returning log.
|
|
35
|
+
- Decrypted receipt contents are not surfaced for entries that fail before HPKE succeeds.
|
|
36
|
+
|
|
37
|
+
## Residual Risks
|
|
38
|
+
|
|
39
|
+
- The HPKE and COSE implementations are intentionally narrow local implementations. They should be replaced with well-maintained libraries or externally audited before production use.
|
|
40
|
+
- Rekor support is currently discovery-only. Live Rekor inclusion-proof verification and witnessed-root validation remain future work.
|
|
41
|
+
- The mock transparency log proof is not a Merkle proof. It exists to exercise owner-side data flow in tests.
|
|
42
|
+
- Key lifecycle operations are not specified beyond registry lookup and revocation checks.
|
|
43
|
+
- Token authorization semantics such as expiry, audience, and scope remain service policy outside Sello.
|
|
44
|
+
- No constant-time comparison is used for non-secret public values such as token references and log URLs. This is acceptable for the current data model, but should be revisited if secret-bearing comparisons are added.
|
|
45
|
+
|
|
46
|
+
## Recommended Production Gates
|
|
47
|
+
|
|
48
|
+
Before production use:
|
|
49
|
+
|
|
50
|
+
1. Replace or audit the local HPKE implementation.
|
|
51
|
+
2. Replace or audit the local COSE_Sign1 implementation.
|
|
52
|
+
3. Add live Rekor proof verification against witnessed log roots.
|
|
53
|
+
4. Add fuzz/property tests for CBOR decoding, COSE envelope parsing, and JWS parsing.
|
|
54
|
+
5. Review key storage, rotation, and revocation operations for the deployment environment.
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --experimental-strip-types
|
|
2
|
+
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
canonicalJsonBytes,
|
|
7
|
+
sello,
|
|
8
|
+
type SdkSubmissionLog,
|
|
9
|
+
type SelloReceipts,
|
|
10
|
+
} from "../src/index.ts";
|
|
11
|
+
import {
|
|
12
|
+
loadQuickstartDevState,
|
|
13
|
+
type QuickstartDevState,
|
|
14
|
+
} from "./quickstart-tool.ts";
|
|
15
|
+
|
|
16
|
+
export type JsonRpcId = string | number | null;
|
|
17
|
+
|
|
18
|
+
export type McpToolCallBody = {
|
|
19
|
+
jsonrpc: "2.0";
|
|
20
|
+
id: JsonRpcId;
|
|
21
|
+
method: "tools/call";
|
|
22
|
+
params: {
|
|
23
|
+
name: string;
|
|
24
|
+
arguments: Record<string, unknown>;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type McpHttpToolCall = {
|
|
29
|
+
headers: {
|
|
30
|
+
authorization: string;
|
|
31
|
+
};
|
|
32
|
+
body: McpToolCallBody;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type McpToolResult = {
|
|
36
|
+
content: readonly {
|
|
37
|
+
type: "text";
|
|
38
|
+
text: string;
|
|
39
|
+
}[];
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type McpHttpToolResponse = {
|
|
43
|
+
status: number;
|
|
44
|
+
body:
|
|
45
|
+
| {
|
|
46
|
+
jsonrpc: "2.0";
|
|
47
|
+
id: JsonRpcId;
|
|
48
|
+
result: McpToolResult;
|
|
49
|
+
}
|
|
50
|
+
| {
|
|
51
|
+
jsonrpc: "2.0";
|
|
52
|
+
id: JsonRpcId;
|
|
53
|
+
error: {
|
|
54
|
+
code: number;
|
|
55
|
+
message: string;
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type SelloMcpToolServer = {
|
|
61
|
+
tool(
|
|
62
|
+
name: string,
|
|
63
|
+
handler: (args: Record<string, unknown>) => McpToolResult | Promise<McpToolResult>,
|
|
64
|
+
): void;
|
|
65
|
+
handle(request: McpHttpToolCall): Promise<McpHttpToolResponse>;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export type McpToolServerExampleOptions = {
|
|
69
|
+
state?: QuickstartDevState;
|
|
70
|
+
statePath?: string;
|
|
71
|
+
log?: SdkSubmissionLog;
|
|
72
|
+
now?: () => string;
|
|
73
|
+
toolArguments?: Record<string, unknown>;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export function createSelloMcpToolServer(receipts: SelloReceipts): SelloMcpToolServer {
|
|
77
|
+
const tools = new Map<
|
|
78
|
+
string,
|
|
79
|
+
(request: McpHttpToolCall) => Promise<McpHttpToolResponse>
|
|
80
|
+
>();
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
tool(name, handler) {
|
|
84
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
85
|
+
throw new TypeError("MCP tool name must be a non-empty string");
|
|
86
|
+
}
|
|
87
|
+
if (tools.has(name)) {
|
|
88
|
+
throw new TypeError(`MCP tool ${name} is already registered`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const wrapped = receipts.tool<McpHttpToolCall, McpHttpToolResponse>(
|
|
92
|
+
`mcp.tools/call.${name}`,
|
|
93
|
+
async (request) => ({
|
|
94
|
+
status: 200,
|
|
95
|
+
body: {
|
|
96
|
+
jsonrpc: "2.0",
|
|
97
|
+
id: request.body.id,
|
|
98
|
+
result: await handler(request.body.params.arguments),
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
{
|
|
102
|
+
canonicalizeInput: (request) => canonicalJsonBytes({
|
|
103
|
+
method: request.body.method,
|
|
104
|
+
params: request.body.params,
|
|
105
|
+
}),
|
|
106
|
+
canonicalizeOutput: (response) => canonicalJsonBytes(response.body),
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
tools.set(name, wrapped);
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
async handle(request) {
|
|
114
|
+
if (request.body.method !== "tools/call") {
|
|
115
|
+
return jsonRpcError(request.body.id, -32601, "method not found");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const tool = tools.get(request.body.params.name);
|
|
119
|
+
if (!tool) {
|
|
120
|
+
return jsonRpcError(request.body.id, -32601, "tool not found");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return await tool(request);
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function runMcpToolServerExample(
|
|
129
|
+
options: McpToolServerExampleOptions = {},
|
|
130
|
+
): Promise<{
|
|
131
|
+
request: McpHttpToolCall;
|
|
132
|
+
response: McpHttpToolResponse;
|
|
133
|
+
actionsUrl: string;
|
|
134
|
+
}> {
|
|
135
|
+
const state = options.state ?? loadMcpDevState(options.statePath);
|
|
136
|
+
const receipts = sello.service({
|
|
137
|
+
service: state.serviceId,
|
|
138
|
+
serviceKey: state.serviceKey,
|
|
139
|
+
tokenIssuer: state.tokenIssuerPublicKey,
|
|
140
|
+
log: options.log ?? sello.logs.http(state.logUrl, {
|
|
141
|
+
endpoint: state.logEndpoint,
|
|
142
|
+
}),
|
|
143
|
+
submit: { mode: "await" },
|
|
144
|
+
now: options.now,
|
|
145
|
+
});
|
|
146
|
+
const server = createSelloMcpToolServer(receipts);
|
|
147
|
+
|
|
148
|
+
server.tool("calendar.create_event", async (args) => {
|
|
149
|
+
const title = readString(args.title, "title");
|
|
150
|
+
return {
|
|
151
|
+
content: [
|
|
152
|
+
{
|
|
153
|
+
type: "text",
|
|
154
|
+
text: `created ${title}`,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const request = {
|
|
161
|
+
headers: {
|
|
162
|
+
authorization: `Bearer ${state.agentToken}`,
|
|
163
|
+
},
|
|
164
|
+
body: {
|
|
165
|
+
jsonrpc: "2.0",
|
|
166
|
+
id: "demo-call-1",
|
|
167
|
+
method: "tools/call",
|
|
168
|
+
params: {
|
|
169
|
+
name: "calendar.create_event",
|
|
170
|
+
arguments: {
|
|
171
|
+
calendarId: "demo-calendar",
|
|
172
|
+
title: "Review launch plan",
|
|
173
|
+
start: "2026-06-05T17:00:00Z",
|
|
174
|
+
...(options.toolArguments ?? {}),
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
} satisfies McpHttpToolCall;
|
|
179
|
+
|
|
180
|
+
const response = await server.handle(request);
|
|
181
|
+
await receipts.flush();
|
|
182
|
+
|
|
183
|
+
return { request, response, actionsUrl: actionViewerUrl(state) };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
187
|
+
try {
|
|
188
|
+
const { response, actionsUrl } = await runMcpToolServerExample();
|
|
189
|
+
console.log("Handled MCP tools/call and emitted a Sello receipt.");
|
|
190
|
+
console.log(JSON.stringify(response.body, null, 2));
|
|
191
|
+
console.log("");
|
|
192
|
+
console.log("View verified actions with:");
|
|
193
|
+
console.log(" node --run actions");
|
|
194
|
+
console.log("");
|
|
195
|
+
console.log("Or open:");
|
|
196
|
+
console.log(` ${actionsUrl}`);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error(`sello mcp example: ${error instanceof Error ? error.message : String(error)}`);
|
|
199
|
+
if (
|
|
200
|
+
error instanceof Error &&
|
|
201
|
+
(error.message.includes("fetch failed") ||
|
|
202
|
+
error.message.includes("Sello log append failed"))
|
|
203
|
+
) {
|
|
204
|
+
console.error("Is the local dev log running? Start it with `node --run dev`.");
|
|
205
|
+
}
|
|
206
|
+
process.exitCode = 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function jsonRpcError(
|
|
211
|
+
id: JsonRpcId,
|
|
212
|
+
code: number,
|
|
213
|
+
message: string,
|
|
214
|
+
): McpHttpToolResponse {
|
|
215
|
+
return {
|
|
216
|
+
status: 200,
|
|
217
|
+
body: {
|
|
218
|
+
jsonrpc: "2.0",
|
|
219
|
+
id,
|
|
220
|
+
error: { code, message },
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function actionViewerUrl(state: QuickstartDevState): string {
|
|
226
|
+
const endpoint = new URL(state.logEndpoint);
|
|
227
|
+
return `${endpoint.origin}/actions`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function loadMcpDevState(statePath?: string): QuickstartDevState {
|
|
231
|
+
try {
|
|
232
|
+
return loadQuickstartDevState(statePath);
|
|
233
|
+
} catch (error) {
|
|
234
|
+
if (error instanceof Error) {
|
|
235
|
+
throw new TypeError(
|
|
236
|
+
error.message.replace("node --run example:tool", "node --run example:mcp"),
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
throw error;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function readString(value: unknown, name: string): string {
|
|
245
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
246
|
+
throw new TypeError(`${name} must be a non-empty string`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return value;
|
|
250
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --experimental-strip-types
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
canonicalJsonBytes,
|
|
9
|
+
sello,
|
|
10
|
+
type SdkSubmissionLog,
|
|
11
|
+
} from "../src/index.ts";
|
|
12
|
+
|
|
13
|
+
export type QuickstartDevState = {
|
|
14
|
+
serviceId: string;
|
|
15
|
+
serviceKey: string;
|
|
16
|
+
tokenIssuerPublicKey: string;
|
|
17
|
+
agentToken: string;
|
|
18
|
+
logUrl: string;
|
|
19
|
+
logEndpoint: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type QuickstartEventRequest = {
|
|
23
|
+
authorizationToken: string;
|
|
24
|
+
calendarId: string;
|
|
25
|
+
title: string;
|
|
26
|
+
start: string;
|
|
27
|
+
attendees: string[];
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type QuickstartEventResponse = {
|
|
31
|
+
id: string;
|
|
32
|
+
calendarId: string;
|
|
33
|
+
title: string;
|
|
34
|
+
status: "created";
|
|
35
|
+
createdAt: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type QuickstartToolOptions = {
|
|
39
|
+
state?: QuickstartDevState;
|
|
40
|
+
statePath?: string;
|
|
41
|
+
log?: SdkSubmissionLog;
|
|
42
|
+
now?: () => string;
|
|
43
|
+
request?: Partial<Omit<QuickstartEventRequest, "authorizationToken">>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const defaultRequest = {
|
|
47
|
+
calendarId: "demo-calendar",
|
|
48
|
+
title: "Review launch plan",
|
|
49
|
+
start: "2026-06-05T17:00:00Z",
|
|
50
|
+
attendees: ["ada@example.com", "grace@example.com"],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export async function runQuickstartTool(
|
|
54
|
+
options: QuickstartToolOptions = {},
|
|
55
|
+
): Promise<{
|
|
56
|
+
request: QuickstartEventRequest;
|
|
57
|
+
response: QuickstartEventResponse;
|
|
58
|
+
actionsUrl: string;
|
|
59
|
+
}> {
|
|
60
|
+
const state = options.state ?? loadQuickstartDevState(options.statePath);
|
|
61
|
+
const receipts = sello.service({
|
|
62
|
+
service: state.serviceId,
|
|
63
|
+
serviceKey: state.serviceKey,
|
|
64
|
+
tokenIssuer: state.tokenIssuerPublicKey,
|
|
65
|
+
log: options.log ?? sello.logs.http(state.logUrl, {
|
|
66
|
+
endpoint: state.logEndpoint,
|
|
67
|
+
}),
|
|
68
|
+
submit: { mode: "await" },
|
|
69
|
+
now: options.now,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const createEvent = receipts.tool<QuickstartEventRequest, QuickstartEventResponse>(
|
|
73
|
+
"calendar.create_event",
|
|
74
|
+
async (request) => ({
|
|
75
|
+
id: `evt_${slug(request.title)}`,
|
|
76
|
+
calendarId: request.calendarId,
|
|
77
|
+
title: request.title,
|
|
78
|
+
status: "created",
|
|
79
|
+
createdAt: new Date().toISOString(),
|
|
80
|
+
}),
|
|
81
|
+
{
|
|
82
|
+
canonicalizeInput: (request) => canonicalJsonBytes({
|
|
83
|
+
calendarId: request.calendarId,
|
|
84
|
+
title: request.title,
|
|
85
|
+
start: request.start,
|
|
86
|
+
attendees: request.attendees,
|
|
87
|
+
}),
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const request = {
|
|
92
|
+
...defaultRequest,
|
|
93
|
+
...options.request,
|
|
94
|
+
authorizationToken: state.agentToken,
|
|
95
|
+
};
|
|
96
|
+
const response = await createEvent(request);
|
|
97
|
+
await receipts.flush();
|
|
98
|
+
|
|
99
|
+
return { request, response, actionsUrl: actionViewerUrl(state) };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function loadQuickstartDevState(
|
|
103
|
+
statePath = join(process.cwd(), ".sello", "dev.json"),
|
|
104
|
+
): QuickstartDevState {
|
|
105
|
+
let parsed: unknown;
|
|
106
|
+
try {
|
|
107
|
+
parsed = JSON.parse(readFileSync(statePath, "utf8"));
|
|
108
|
+
} catch {
|
|
109
|
+
throw new TypeError(
|
|
110
|
+
"missing local Sello dev state. Start the local log with `node --run dev` from this repo, or `npx sello dev` after install, then run `node --run example:tool` in another terminal.",
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!isRecord(parsed)) {
|
|
115
|
+
throw new TypeError("local Sello dev state must be a JSON object");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
serviceId: readString(parsed.serviceId, "dev state serviceId"),
|
|
120
|
+
serviceKey: readString(parsed.serviceKey, "dev state serviceKey"),
|
|
121
|
+
tokenIssuerPublicKey: readString(
|
|
122
|
+
parsed.tokenIssuerPublicKey,
|
|
123
|
+
"dev state tokenIssuerPublicKey",
|
|
124
|
+
),
|
|
125
|
+
agentToken: readString(parsed.agentToken, "dev state agentToken"),
|
|
126
|
+
logUrl: readString(parsed.logUrl, "dev state logUrl"),
|
|
127
|
+
logEndpoint: readString(parsed.logEndpoint, "dev state logEndpoint"),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
132
|
+
try {
|
|
133
|
+
const { response, actionsUrl } = await runQuickstartTool();
|
|
134
|
+
console.log("Created example event and emitted a Sello receipt.");
|
|
135
|
+
console.log(JSON.stringify(response, null, 2));
|
|
136
|
+
console.log("");
|
|
137
|
+
console.log("View verified actions with:");
|
|
138
|
+
console.log(" node --run actions");
|
|
139
|
+
console.log("");
|
|
140
|
+
console.log("Or open:");
|
|
141
|
+
console.log(` ${actionsUrl}`);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error(`sello quickstart: ${error instanceof Error ? error.message : String(error)}`);
|
|
144
|
+
if (
|
|
145
|
+
error instanceof Error &&
|
|
146
|
+
(error.message.includes("fetch failed") ||
|
|
147
|
+
error.message.includes("Sello log append failed"))
|
|
148
|
+
) {
|
|
149
|
+
console.error("Is the local dev log running? Start it with `node --run dev`.");
|
|
150
|
+
}
|
|
151
|
+
process.exitCode = 1;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function slug(value: string): string {
|
|
156
|
+
return value
|
|
157
|
+
.toLowerCase()
|
|
158
|
+
.replace(/[^a-z0-9]+/g, "_")
|
|
159
|
+
.replace(/^_+|_+$/g, "")
|
|
160
|
+
.slice(0, 40);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function actionViewerUrl(state: QuickstartDevState): string {
|
|
164
|
+
const endpoint = new URL(state.logEndpoint);
|
|
165
|
+
return `${endpoint.origin}/actions`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function readString(value: unknown, name: string): string {
|
|
169
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
170
|
+
throw new TypeError(`${name} must be a non-empty string`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
177
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
178
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.0",
|
|
3
|
+
"description": "Implementation-backed Sello v0.1 vectors for success, error, and denied receipts.",
|
|
4
|
+
"common": {
|
|
5
|
+
"service_identifier": "github.com/mcp/v1",
|
|
6
|
+
"log_url": "https://rekor.example.com/api",
|
|
7
|
+
"kid_hex": "6769746875622d6d63702d76312d323032362d7132",
|
|
8
|
+
"keys": {
|
|
9
|
+
"issuer_public_key_ed25519": "03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8",
|
|
10
|
+
"issuer_private_key_ed25519": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
|
|
11
|
+
"service_public_key_ed25519": "29acbae141bccaf0b22e1a94d34d0bc7361e526d0bfe12c89794bc9322966dd7",
|
|
12
|
+
"service_private_key_ed25519": "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f",
|
|
13
|
+
"trust_root_public_key_ed25519": "2543b92ff1095511476adc8369db6ddc933665a11978dda1404ee1066ca9559d",
|
|
14
|
+
"trust_root_private_key_ed25519": "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f",
|
|
15
|
+
"owner_public_key_x25519": "4310ee97d88cc1f088a5576c77ab0cf5c3ac797f3d95139c6c84b5429c59662a",
|
|
16
|
+
"owner_private_key_x25519": "8057991eef8f1f1af18f4a9491d16a1ce333f695d4db8e38da75975c4478e0fb"
|
|
17
|
+
},
|
|
18
|
+
"registry_json": "{\"6769746875622d6d63702d76312d323032362d7132\":{\"service_identifier\":\"github.com/mcp/v1\",\"public_key_ed25519\":\"Kay64UG8yvCyLhqU000LxzYeUm0L_hLIl5S8kyKWbdc\"}}",
|
|
19
|
+
"registry_signature_base64url": "Zj8TNsKLAyfg2vM6Q15f675DZNAwS_qv-24S2t0EmdMJZq-hYeJDzrwJPJcSGXE-NUZMbCaY1lS0rM5IHebDBw"
|
|
20
|
+
},
|
|
21
|
+
"vectors": [
|
|
22
|
+
{
|
|
23
|
+
"name": "success",
|
|
24
|
+
"integrated_time": "2026-05-28T10:00:00Z",
|
|
25
|
+
"authorization_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlLmNvbSIsInN1YiI6ImRlbW8tYWdlbnQiLCJqdGkiOiJ2ZWN0b3Itc3VjY2VzcyIsIm93bmVyX2hwa2VfcGsiOiJReER1bDlpTXdmQ0lwVmRzZDZzTTljT3NlWDg5bFJPY2JJUzFRcHhaWmlvIiwic2VsbG9fbG9ncyI6WyJodHRwczovL3Jla29yLmV4YW1wbGUuY29tL2FwaSJdfQ.tY4WWhhNmEUCBGypL8mAgMs2rqntuSHwSpyS7x03SUardAq1x-fVDsclbmEwQ_nLeNLdbF9Y7eehcci6g3VmAQ",
|
|
26
|
+
"sello_token_ref": "6cdfd05fb7402d9c6930af5e742ccbb6d366f76e3a6823ef2880271b88fc35c3",
|
|
27
|
+
"agent_identifier": "6cdfd05fb7402d9c6930af5e742ccbb6",
|
|
28
|
+
"receipt_body": {
|
|
29
|
+
"agent-identifier": "6cdfd05fb7402d9c6930af5e742ccbb6",
|
|
30
|
+
"action-type": "tools/call",
|
|
31
|
+
"action-input-hash": "5a28bf8eabc7811c3c267c211a34eca8e7e29a464271d5cf5072f4e046407525",
|
|
32
|
+
"action-output-hash": "8b544119e3a09a4ad8d06d976b0f8c8f35f7abfe52d21a60bf26c9ae4abad2ff",
|
|
33
|
+
"result-status": "success",
|
|
34
|
+
"timestamp": "2026-05-28T10:00:00Z"
|
|
35
|
+
},
|
|
36
|
+
"receipt_body_cbor_hex": "a66974696d657374616d70c074323032362d30352d32385431303a30303a30305a6b616374696f6e2d747970656a746f6f6c732f63616c6c6d726573756c742d7374617475736773756363657373706167656e742d6964656e7469666965727820366364666430356662373430326439633639333061663565373432636362623671616374696f6e2d696e7075742d6861736858205a28bf8eabc7811c3c267c211a34eca8e7e29a464271d5cf5072f4e04640752572616374696f6e2d6f75747075742d6861736858208b544119e3a09a4ad8d06d976b0f8c8f35f7abfe52d21a60bf26c9ae4abad2ff",
|
|
37
|
+
"protected_header_hex": "a5012704556769746875622d6d63702d76312d323032362d71323a0001000065302e312e303a0001000158206cdfd05fb7402d9c6930af5e742ccbb6d366f76e3a6823ef2880271b88fc35c33a00010002781d68747470733a2f2f72656b6f722e6578616d706c652e636f6d2f617069",
|
|
38
|
+
"hpke_payload_hex": "1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a27de8f652a49af996c4c0e9605194b9b157f623103624de38d781d11ed4f30e51e1bfe14a5def8f41aab80cb7d24fd8f69e3d56507435af0e133bba6cdcd6bea608c2660e253c5834047a0ec54ecf16f5739dab5963e8c9a5d4b254329caed98fd95fab4c0928310673649938a8db4e85d7ce91861daadc9d7ed651e74fa648d7f2da0b397832c65715c601270af794608d71c659bf9acb64e41ab398cefef0677fd01f746bd8b9d1ab5c14f4b1527f68958905cc4eb1c9bb0f55f217f1c383a6af20588b0de98d24c01213606ee3ad7d1b6101d00fbcdeeabbf756b393bd1e9a37b1254eeac13153acd9fc93aa7448abeabee1ad68d03dc3e32",
|
|
39
|
+
"cose_sign1_envelope_hex": "845870a5012704556769746875622d6d63702d76312d323032362d71323a0001000065302e312e303a0001000158206cdfd05fb7402d9c6930af5e742ccbb6d366f76e3a6823ef2880271b88fc35c33a00010002781d68747470733a2f2f72656b6f722e6578616d706c652e636f6d2f617069a059011a1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a27de8f652a49af996c4c0e9605194b9b157f623103624de38d781d11ed4f30e51e1bfe14a5def8f41aab80cb7d24fd8f69e3d56507435af0e133bba6cdcd6bea608c2660e253c5834047a0ec54ecf16f5739dab5963e8c9a5d4b254329caed98fd95fab4c0928310673649938a8db4e85d7ce91861daadc9d7ed651e74fa648d7f2da0b397832c65715c601270af794608d71c659bf9acb64e41ab398cefef0677fd01f746bd8b9d1ab5c14f4b1527f68958905cc4eb1c9bb0f55f217f1c383a6af20588b0de98d24c01213606ee3ad7d1b6101d00fbcdeeabbf756b393bd1e9a37b1254eeac13153acd9fc93aa7448abeabee1ad68d03dc3e325840376a642595ad0c25aaf34228ef1e82eaa1e0fb753220cd7b11c9f80ebab5c789084264fe04292cce45670455f679decdc22ac86a95eed6fed66f0141f3c4760b",
|
|
40
|
+
"mock_log_proof": {
|
|
41
|
+
"logUrl": "https://rekor.example.com/api",
|
|
42
|
+
"index": 0,
|
|
43
|
+
"integratedTime": "2026-05-28T10:00:00Z",
|
|
44
|
+
"envelopeHash": "05b2b0b12cb35a5c29541e2064a29612654b3b55441cae93dc25e9525a962685",
|
|
45
|
+
"proofHash": "ffada841f19b1c7e952dae12184ce025e013907edb2220d2b7c2d8e4026a9214"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "error",
|
|
50
|
+
"integrated_time": "2026-05-28T10:00:01Z",
|
|
51
|
+
"authorization_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlLmNvbSIsInN1YiI6ImRlbW8tYWdlbnQiLCJqdGkiOiJ2ZWN0b3ItZXJyb3IiLCJvd25lcl9ocGtlX3BrIjoiUXhEdWw5aU13ZkNJcFZkc2Q2c005Y09zZVg4OWxST2NiSVMxUXB4WlppbyIsInNlbGxvX2xvZ3MiOlsiaHR0cHM6Ly9yZWtvci5leGFtcGxlLmNvbS9hcGkiXX0.KSeW4aFRCz9XsbvaTf3jvvilN95H852pNW98Yimfal9hqSG9IBgN6pByM4Qlrjm4Cts7dk1VfIHtoN6SkM2fCw",
|
|
52
|
+
"sello_token_ref": "2e70f600fd8f3367e235a6eb404247d211bdb6497743099da2397740829ae34f",
|
|
53
|
+
"agent_identifier": "2e70f600fd8f3367e235a6eb404247d2",
|
|
54
|
+
"receipt_body": {
|
|
55
|
+
"agent-identifier": "2e70f600fd8f3367e235a6eb404247d2",
|
|
56
|
+
"action-type": "issues.create",
|
|
57
|
+
"action-input-hash": "877f6aadc489ab930b9036c85b8d4f34d69f8fc020b8c9a73e321867ae04938e",
|
|
58
|
+
"action-output-hash": "23814260e61d0767a9eac1214bd3c68c229969782a113ee8df2c6e5595d87220",
|
|
59
|
+
"result-status": "error",
|
|
60
|
+
"timestamp": "2026-05-28T10:00:01Z"
|
|
61
|
+
},
|
|
62
|
+
"receipt_body_cbor_hex": "a66974696d657374616d70c074323032362d30352d32385431303a30303a30315a6b616374696f6e2d747970656d6973737565732e6372656174656d726573756c742d737461747573656572726f72706167656e742d6964656e7469666965727820326537306636303066643866333336376532333561366562343034323437643271616374696f6e2d696e7075742d686173685820877f6aadc489ab930b9036c85b8d4f34d69f8fc020b8c9a73e321867ae04938e72616374696f6e2d6f75747075742d68617368582023814260e61d0767a9eac1214bd3c68c229969782a113ee8df2c6e5595d87220",
|
|
63
|
+
"protected_header_hex": "a5012704556769746875622d6d63702d76312d323032362d71323a0001000065302e312e303a0001000158202e70f600fd8f3367e235a6eb404247d211bdb6497743099da2397740829ae34f3a00010002781d68747470733a2f2f72656b6f722e6578616d706c652e636f6d2f617069",
|
|
64
|
+
"hpke_payload_hex": "675dd574ed7789310b3d2e7681f3790b466c773b1521fecf36577958371ea52f92f28933bef9a7d0c9037164e536db97ea90b177ce0b4b5afb92baafa3c21df37ca1afaed212b6f140c9ecea2c12bb1f99c520251d0c3b0ca6840168bee5d7e5efb142e044328db1e8c74094bfd4146999b8c3c38ba41e82fd9d173fda05142f87110be3fb5ac1b4398033b295577c29862b7a82d068064a6bb5df799c0c021b42d8c245898dc0775b1832d06734d72aca293cbbceefa8efadb0f11716f0e0abf7bf05601e28ee82a0fbb7c36c273e197c6e57725339a2bc26a41e9174a063e9be56032fd27f470729a70ea7bf593c81b764249730f14280ec29df6a039c92b36c271331d5a64d916e39e5f55ec047ef3fe517ae2c3465018d73e4",
|
|
65
|
+
"cose_sign1_envelope_hex": "845870a5012704556769746875622d6d63702d76312d323032362d71323a0001000065302e312e303a0001000158202e70f600fd8f3367e235a6eb404247d211bdb6497743099da2397740829ae34f3a00010002781d68747470733a2f2f72656b6f722e6578616d706c652e636f6d2f617069a059011b675dd574ed7789310b3d2e7681f3790b466c773b1521fecf36577958371ea52f92f28933bef9a7d0c9037164e536db97ea90b177ce0b4b5afb92baafa3c21df37ca1afaed212b6f140c9ecea2c12bb1f99c520251d0c3b0ca6840168bee5d7e5efb142e044328db1e8c74094bfd4146999b8c3c38ba41e82fd9d173fda05142f87110be3fb5ac1b4398033b295577c29862b7a82d068064a6bb5df799c0c021b42d8c245898dc0775b1832d06734d72aca293cbbceefa8efadb0f11716f0e0abf7bf05601e28ee82a0fbb7c36c273e197c6e57725339a2bc26a41e9174a063e9be56032fd27f470729a70ea7bf593c81b764249730f14280ec29df6a039c92b36c271331d5a64d916e39e5f55ec047ef3fe517ae2c3465018d73e45840b48f077fb8069f721aaee2beff19a035e0d02d42e33f9e47ac3fe657ed7f9b66a22fdb4023eeb49feaeb95ce1d31ebbfdc9d19146537e0d5c47a15efb7f6010e",
|
|
66
|
+
"mock_log_proof": {
|
|
67
|
+
"logUrl": "https://rekor.example.com/api",
|
|
68
|
+
"index": 0,
|
|
69
|
+
"integratedTime": "2026-05-28T10:00:01Z",
|
|
70
|
+
"envelopeHash": "daaa599016f737b6e49fcff71711c81c8edf495e3cdaf8bef22dbc9979836a54",
|
|
71
|
+
"proofHash": "4afd17bac82a3931177b948f5b3a4ba9f87ba295b138937b3ce21a86eb4e948b"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "denied",
|
|
76
|
+
"integrated_time": "2026-05-28T10:00:02Z",
|
|
77
|
+
"authorization_token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci5leGFtcGxlLmNvbSIsInN1YiI6ImRlbW8tYWdlbnQiLCJqdGkiOiJ2ZWN0b3ItZGVuaWVkIiwib3duZXJfaHBrZV9wayI6IlF4RHVsOWlNd2ZDSXBWZHNkNnNNOWNPc2VYODlsUk9jYklTMVFweFpaaW8iLCJzZWxsb19sb2dzIjpbImh0dHBzOi8vcmVrb3IuZXhhbXBsZS5jb20vYXBpIl19.CpckhcTBCsVUkPhmQrDvT6PjmRtqWmkVtQiTBm-JORfVkVfBePeBBYHHLtCIgh2R_d30CJhg1XDyqWFKgrKWAA",
|
|
78
|
+
"sello_token_ref": "3b07ee0c80e9d380374221168c937e2ce688776062d88a3c3cbcaf318dcaec42",
|
|
79
|
+
"agent_identifier": "3b07ee0c80e9d380374221168c937e2c",
|
|
80
|
+
"receipt_body": {
|
|
81
|
+
"agent-identifier": "3b07ee0c80e9d380374221168c937e2c",
|
|
82
|
+
"action-type": "repo.delete",
|
|
83
|
+
"action-input-hash": "8068b4fb35823247cd82036105b4bffd8d093307ae80089d399f63854a8313cf",
|
|
84
|
+
"action-output-hash": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
85
|
+
"result-status": "denied",
|
|
86
|
+
"timestamp": "2026-05-28T10:00:02Z"
|
|
87
|
+
},
|
|
88
|
+
"receipt_body_cbor_hex": "a66974696d657374616d70c074323032362d30352d32385431303a30303a30325a6b616374696f6e2d747970656b7265706f2e64656c6574656d726573756c742d7374617475736664656e696564706167656e742d6964656e7469666965727820336230376565306338306539643338303337343232313136386339333765326371616374696f6e2d696e7075742d6861736858208068b4fb35823247cd82036105b4bffd8d093307ae80089d399f63854a8313cf72616374696f6e2d6f75747075742d6861736858200000000000000000000000000000000000000000000000000000000000000000",
|
|
89
|
+
"protected_header_hex": "a5012704556769746875622d6d63702d76312d323032362d71323a0001000065302e312e303a0001000158203b07ee0c80e9d380374221168c937e2ce688776062d88a3c3cbcaf318dcaec423a00010002781d68747470733a2f2f72656b6f722e6578616d706c652e636f6d2f617069",
|
|
90
|
+
"hpke_payload_hex": "493e82fc74464a59268817623d2053c5eb8e2cc4a988b4fee179ec6b010d531d0df430d57303f6df11d46cb252e457455b763c3e1273e797996eaf6ef4cc579e04812393dfcb4ece94363ab809bf3396f81dfce38a3adc179e5a199e76c8d62b106bfcec44017505c639e05099b54071115abd1464a7d09bd6861d82be03733dc3c3654be36676e80a01affae950523d8b0bcd330d8ff31929e05d952d841e880db8475a177c405d4ce07c4a9a12e78549cd163f180b567d9a89257026dd03671bf688f1607b54a25cc46ed9f6159bfbe11f147ea77a8a0ea9f38e85910537786e6c0faa2ead75f2cb4913020ccc6f7ecc1fccb633f2ce43e17615df72ce4e66fa1c92145402118c08f03539e76a22f4cb935200055676d84be1",
|
|
91
|
+
"cose_sign1_envelope_hex": "845870a5012704556769746875622d6d63702d76312d323032362d71323a0001000065302e312e303a0001000158203b07ee0c80e9d380374221168c937e2ce688776062d88a3c3cbcaf318dcaec423a00010002781d68747470733a2f2f72656b6f722e6578616d706c652e636f6d2f617069a059011a493e82fc74464a59268817623d2053c5eb8e2cc4a988b4fee179ec6b010d531d0df430d57303f6df11d46cb252e457455b763c3e1273e797996eaf6ef4cc579e04812393dfcb4ece94363ab809bf3396f81dfce38a3adc179e5a199e76c8d62b106bfcec44017505c639e05099b54071115abd1464a7d09bd6861d82be03733dc3c3654be36676e80a01affae950523d8b0bcd330d8ff31929e05d952d841e880db8475a177c405d4ce07c4a9a12e78549cd163f180b567d9a89257026dd03671bf688f1607b54a25cc46ed9f6159bfbe11f147ea77a8a0ea9f38e85910537786e6c0faa2ead75f2cb4913020ccc6f7ecc1fccb633f2ce43e17615df72ce4e66fa1c92145402118c08f03539e76a22f4cb935200055676d84be158409aaed5eb90fdc5c94349fb5c417f52c66e097f07aa304e8a0afa39256a5de59f9ba8781bbee10dc7be81e142963eaaa482822080e5879d956262b5dc5a76d905",
|
|
92
|
+
"mock_log_proof": {
|
|
93
|
+
"logUrl": "https://rekor.example.com/api",
|
|
94
|
+
"index": 0,
|
|
95
|
+
"integratedTime": "2026-05-28T10:00:02Z",
|
|
96
|
+
"envelopeHash": "9d2d05b01183a2582541d121159db7f071e81fece04e3cb63c2acfb105c94138",
|
|
97
|
+
"proofHash": "14ee32619ccd1ac9a9db6df38a8ea1eab43b5136f8e76635069df715d4a516d8"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
}
|