@elgora/cli 11.0.0 → 12.0.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.
Files changed (3) hide show
  1. package/README.md +23 -5
  2. package/dist/cli.js +1486 -747
  3. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import path5 from "path";
4
+ import path6 from "path";
5
5
  import { config as loadDotenv } from "dotenv";
6
6
  import { ZodError as ZodError2 } from "zod";
7
7
 
@@ -40,8 +40,8 @@ function bytesToBase64Url(bytes) {
40
40
  return chunks.join("");
41
41
  }
42
42
  function base64UrlToBytes(value) {
43
- const padded = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
44
- return base64ToBytes(padded);
43
+ const padded2 = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(value.length / 4) * 4, "=");
44
+ return base64ToBytes(padded2);
45
45
  }
46
46
  function base64ToBytes(value) {
47
47
  const binary = atob(value);
@@ -87,12 +87,24 @@ function safeDecodeUtf8(bytes) {
87
87
  import { Aes256Gcm, CipherSuite, HkdfSha256 } from "@hpke/core";
88
88
  import { DhkemX25519HkdfSha256 } from "@hpke/dhkem-x25519";
89
89
  import { bytesToHex, keccak256, toBytes } from "viem";
90
- import { z } from "zod";
90
+ import { z as z2 } from "zod";
91
91
 
92
92
  // ../../packages/core/src/constants.ts
93
93
  var BOUNTY_CHALLENGE_PROFILE = "elgora_markdown_bounty_challenge_v0";
94
+ var BOUNTY_CHALLENGE_PROFILE_V1 = "elgora_markdown_bounty_challenge_v1";
94
95
  var WINNER_TAKE_ALL_PAYOUT_POLICY = "winner_take_all";
96
+ var MAX_BOUNTY_CHALLENGE_MARKDOWN_CHARACTERS = 1e6;
97
+ var BOUNTY_CHALLENGE_FRONTMATTER_KEYS = [
98
+ "profile",
99
+ "escrow_amount",
100
+ "submission_deadline",
101
+ "payout_policy"
102
+ ];
103
+ var BOUNTY_CHALLENGE_OPTIONAL_FRONTMATTER_KEYS = [
104
+ "constraints"
105
+ ];
95
106
  var SUBMISSION_ENCRYPTION_TYPE = "guardian_timelock_hpke_aes_256_gcm_v0";
107
+ var SUBMISSION_ENCRYPTION_TYPE_V1 = "guardian_timelock_hpke_aes_256_gcm_v1";
96
108
  var ZERO_BYTES32 = "0x0000000000000000000000000000000000000000000000000000000000000000";
97
109
  var READINESS_APPROVAL_VALIDITY_SECONDS = 7 * 24 * 60 * 60;
98
110
  var READINESS_TIMEOUT_MS = 18e4;
@@ -128,63 +140,127 @@ function canonicalizeJson(value) {
128
140
  throw new Error("Unsupported JSON value");
129
141
  }
130
142
 
143
+ // ../../packages/core/src/submission-path.ts
144
+ import { z } from "zod";
145
+ var SOLVER_SUBMISSION_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
146
+ var MAX_SOLVER_SUBMISSION_NAME_CHARACTERS = 255;
147
+ var MAX_SOLVER_SUBMISSION_PATH_CHARACTERS = 768;
148
+ var MAX_SOLVER_SUBMISSION_FILES_V1 = 1e4;
149
+ var SUBMISSION_ENCRYPTED_BUNDLE_V1_KIND = "elgora_private_artifact_encrypted_bundle_v1";
150
+ var NAME_TOO_LONG_MESSAGE = `Artifact filename exceeds ${MAX_SOLVER_SUBMISSION_NAME_CHARACTERS} ASCII characters (including extension). Solvers may shorten it only if the published challenge permits, before the deadline.`;
151
+ var solverSubmissionNameSchema = z.string().max(MAX_SOLVER_SUBMISSION_NAME_CHARACTERS, NAME_TOO_LONG_MESSAGE).regex(SOLVER_SUBMISSION_NAME_RE);
152
+ function describeSolverSubmissionPathIssue(value) {
153
+ if (value.length > MAX_SOLVER_SUBMISSION_PATH_CHARACTERS) {
154
+ return `"${value.slice(0, 64)}\u2026" is ${value.length} characters; a Submission path is at most ${MAX_SOLVER_SUBMISSION_PATH_CHARACTERS}.`;
155
+ }
156
+ for (const segment of value.split("/")) {
157
+ if (segment.length > MAX_SOLVER_SUBMISSION_NAME_CHARACTERS) {
158
+ return `"${value}" has a name longer than ${MAX_SOLVER_SUBMISSION_NAME_CHARACTERS} ASCII characters. Solvers may shorten it only if the published challenge permits, before the deadline.`;
159
+ }
160
+ if (!SOLVER_SUBMISSION_NAME_RE.test(segment)) {
161
+ return `"${value}" is not a valid Submission path: it is "/"-separated names, each starting with a letter or digit and using only letters, digits, ".", "_" and "-".`;
162
+ }
163
+ }
164
+ return null;
165
+ }
166
+ function findSolverSubmissionPathConflict(paths) {
167
+ const files = /* @__PURE__ */ new Map();
168
+ const folders = /* @__PURE__ */ new Map();
169
+ for (const path7 of paths) {
170
+ const folded = path7.toLowerCase();
171
+ const earlier = files.get(folded);
172
+ if (earlier !== void 0) {
173
+ return {
174
+ path: path7,
175
+ conflictsWith: earlier,
176
+ reason: earlier === path7 ? "duplicate" : "case"
177
+ };
178
+ }
179
+ files.set(folded, path7);
180
+ const segments = path7.split("/");
181
+ for (let depth = 1; depth < segments.length; depth += 1) {
182
+ const spelling = segments.slice(0, depth).join("/");
183
+ const known = folders.get(spelling.toLowerCase());
184
+ if (known === void 0) {
185
+ folders.set(spelling.toLowerCase(), { spelling, path: path7 });
186
+ } else if (known.spelling !== spelling) {
187
+ return { path: path7, conflictsWith: known.path, reason: "folder_case" };
188
+ }
189
+ }
190
+ }
191
+ for (const [folded, path7] of files) {
192
+ const nested = folders.get(folded);
193
+ if (nested !== void 0) {
194
+ return { path: path7, conflictsWith: nested.path, reason: "folder" };
195
+ }
196
+ }
197
+ return null;
198
+ }
199
+ function describeSolverSubmissionPathConflict(conflict) {
200
+ switch (conflict.reason) {
201
+ case "duplicate":
202
+ return `"${conflict.path}" appears twice`;
203
+ case "case":
204
+ return `"${conflict.path}" and "${conflict.conflictsWith}" differ only in case, so they would collide on a case-insensitive filesystem`;
205
+ case "folder_case":
206
+ return `"${conflict.path}" and "${conflict.conflictsWith}" spell the same folder in different case, so filesystems would disagree on whether they share it`;
207
+ case "folder":
208
+ return `"${conflict.path}" is a file, but "${conflict.conflictsWith}" needs it to be a folder`;
209
+ }
210
+ }
211
+
131
212
  // ../../packages/core/src/submission-encryption.ts
132
- var PRIVATE_ARTIFACT_ENVELOPE_KIND = "elgora_private_artifact_envelope_v0";
133
- var PRIVATE_ARTIFACT_PUBLIC_MANIFEST_KIND = "elgora_private_artifact_public_manifest_v0";
134
- var PRIVATE_ARTIFACT_ENCRYPTED_BUNDLE_KIND = "elgora_private_artifact_encrypted_bundle_v0";
135
- var PRIVATE_ARTIFACT_WRAPPED_KEY_BAG_KIND = "elgora_private_artifact_wrapped_key_bag_v0";
136
- var PRIVATE_ARTIFACT_REVEAL_POLICY_KIND = "drand_timelock_reveal_policy_v0";
137
- var PRIVATE_ARTIFACT_HPKE_SUITE = "hpke-dhkem-x25519-hkdf-sha256+hkdf-sha256+aes-256-gcm@1";
213
+ var SUBMISSION_ENVELOPE_KIND = "elgora_private_artifact_envelope_v0";
214
+ var SUBMISSION_PUBLIC_MANIFEST_KIND = "elgora_private_artifact_public_manifest_v0";
215
+ var SUBMISSION_ENCRYPTED_BUNDLE_KIND = "elgora_private_artifact_encrypted_bundle_v0";
216
+ var SUBMISSION_WRAPPED_KEY_BAG_KIND = "elgora_private_artifact_wrapped_key_bag_v0";
217
+ var SUBMISSION_REVEAL_POLICY_KIND = "drand_timelock_reveal_policy_v0";
218
+ var SUBMISSION_HPKE_SUITE = "hpke-dhkem-x25519-hkdf-sha256+hkdf-sha256+aes-256-gcm@1";
138
219
  var MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES = 50 * 1024 * 1024;
139
220
  var MAX_SOLVER_SUBMISSION_FILES = 500;
140
- var MAX_SOLVER_SUBMISSION_NAME_CHARACTERS = 255;
141
- var MAX_SOLVER_SUBMISSION_ARTIFACT_BYTES = 37 * 1024 * 1024;
142
221
  var MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES = 250 * 1e3 * 1e3;
143
- var PRIVATE_ARTIFACT_HPKE_INFO_PREFIX = "elgora/private-artifact-envelope/hpke-aes-key/v0";
222
+ var SUBMISSION_HPKE_INFO_PREFIX = "elgora/private-artifact-envelope/hpke-aes-key/v0";
144
223
  var AES_256_KEY_BYTES = 32;
145
224
  var AES_GCM_IV_BYTES = 12;
146
- var PRIVATE_ARTIFACT_NONCE_BYTES = 16;
147
- var AES_GCM_TAG_BITS = 128;
225
+ var SUBMISSION_NONCE_BYTES = 16;
226
+ var AES_GCM_TAG_BYTES = 16;
227
+ var AES_GCM_TAG_BITS = AES_GCM_TAG_BYTES * 8;
148
228
  var BASE64URL_RE = /^[A-Za-z0-9_-]+$/;
149
- var SOLVER_SUBMISSION_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
150
229
  var MEDIA_TYPE_RE = /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/;
151
230
  var KEY_ID_RE = /^[A-Za-z0-9][ -~]*$/;
152
- var base64UrlSchema = z.string().regex(BASE64URL_RE);
153
- var lowerAddressSchema = z.string().refine(isLowercaseHexAddress);
154
- var bytes32Schema = z.string().refine(isLowercaseBytes32);
155
- var sha256HexSchema = z.string().refine(isSha256Hex);
156
- var keyIdSchema = z.string().min(1).max(128).regex(KEY_ID_RE);
157
- var artifactNameSchema = z.string().max(
158
- MAX_SOLVER_SUBMISSION_NAME_CHARACTERS,
159
- `Artifact filename exceeds ${MAX_SOLVER_SUBMISSION_NAME_CHARACTERS} ASCII characters (including extension). Solvers may shorten it only if the published challenge permits, before the deadline.`
160
- ).regex(SOLVER_SUBMISSION_NAME_RE);
161
- var mediaTypeSchema = z.string().regex(MEDIA_TYPE_RE);
162
- var bountyIdSchema = z.string().refine(isBountyId);
163
- var chainIdSchema = z.number().int().positive().lte(Number.MAX_SAFE_INTEGER);
164
- var privateArtifactContextSchema = z.strictObject({
165
- type: z.literal("solver_submission"),
231
+ var base64UrlSchema = z2.string().regex(BASE64URL_RE);
232
+ var lowerAddressSchema = z2.string().refine(isLowercaseHexAddress);
233
+ var bytes32Schema = z2.string().refine(isLowercaseBytes32);
234
+ var sha256HexSchema = z2.string().refine(isSha256Hex);
235
+ var keyIdSchema = z2.string().min(1).max(128).regex(KEY_ID_RE);
236
+ var fileNameSchema = solverSubmissionNameSchema;
237
+ var mediaTypeSchema = z2.string().regex(MEDIA_TYPE_RE);
238
+ var bountyIdSchema = z2.string().refine(isBountyId);
239
+ var chainIdSchema = z2.number().int().positive().lte(Number.MAX_SAFE_INTEGER);
240
+ var submissionContextSchema = z2.strictObject({
241
+ type: z2.literal("solver_submission"),
166
242
  chain_id: chainIdSchema,
167
243
  hub_address: lowerAddressSchema,
168
244
  bounty_id: bountyIdSchema,
169
245
  actor_address: lowerAddressSchema
170
246
  });
171
- var guardianRecipientSchema = z.strictObject({
247
+ var guardianRecipientSchema = z2.strictObject({
172
248
  key_id: keyIdSchema,
173
- suite: z.literal(PRIVATE_ARTIFACT_HPKE_SUITE),
249
+ suite: z2.literal(SUBMISSION_HPKE_SUITE),
174
250
  public_key: base64UrlSchema.length(
175
251
  43,
176
252
  "public_key must encode a 32-byte X25519 public key"
177
253
  )
178
254
  });
179
- var guardianRecipientPrivateKeySchema = z.strictObject({
255
+ var guardianRecipientPrivateKeySchema = z2.strictObject({
180
256
  key_id: keyIdSchema,
181
- suite: z.literal(PRIVATE_ARTIFACT_HPKE_SUITE),
257
+ suite: z2.literal(SUBMISSION_HPKE_SUITE),
182
258
  private_key: base64UrlSchema.length(
183
259
  43,
184
260
  "private_key must encode a 32-byte X25519 private key"
185
261
  )
186
262
  });
187
- var guardianRecipientsSchema = z.array(guardianRecipientSchema).min(1).superRefine((recipients, ctx) => {
263
+ var guardianRecipientsSchema = z2.array(guardianRecipientSchema).min(1).superRefine((recipients, ctx) => {
188
264
  const seen = /* @__PURE__ */ new Set();
189
265
  for (const recipient of recipients) {
190
266
  if (seen.has(recipient.key_id)) {
@@ -197,84 +273,84 @@ var guardianRecipientsSchema = z.array(guardianRecipientSchema).min(1).superRefi
197
273
  seen.add(recipient.key_id);
198
274
  }
199
275
  });
200
- var guardianRecipientPrivateKeysSchema = z.array(guardianRecipientPrivateKeySchema).min(1);
201
- var artifactManifestEntrySchema = z.strictObject({
202
- name: artifactNameSchema,
276
+ var guardianRecipientPrivateKeysSchema = z2.array(guardianRecipientPrivateKeySchema).min(1);
277
+ var bundleFileEntrySchema = z2.strictObject({
278
+ name: fileNameSchema,
203
279
  media_type: mediaTypeSchema,
204
- size_bytes: z.number().int().nonnegative().lte(Number.MAX_SAFE_INTEGER),
280
+ size_bytes: z2.number().int().nonnegative().lte(Number.MAX_SAFE_INTEGER),
205
281
  sha256: sha256HexSchema
206
282
  });
207
- var privateArtifactPublicManifestSchema = z.strictObject({
208
- kind: z.literal(PRIVATE_ARTIFACT_PUBLIC_MANIFEST_KIND),
209
- context: privateArtifactContextSchema
283
+ var submissionPublicManifestSchema = z2.strictObject({
284
+ kind: z2.literal(SUBMISSION_PUBLIC_MANIFEST_KIND),
285
+ context: submissionContextSchema
210
286
  });
211
- var encryptedBundleArtifactSchema = artifactManifestEntrySchema.extend({
287
+ var encryptedBundleFileSchema = bundleFileEntrySchema.extend({
212
288
  bytes: base64UrlSchema
213
289
  });
214
- var privateArtifactEncryptedBundleSchema = z.strictObject({
215
- kind: z.literal(PRIVATE_ARTIFACT_ENCRYPTED_BUNDLE_KIND),
216
- context: privateArtifactContextSchema,
217
- artifacts: z.array(encryptedBundleArtifactSchema).min(1).max(
290
+ var submissionEncryptedBundleSchema = z2.strictObject({
291
+ kind: z2.literal(SUBMISSION_ENCRYPTED_BUNDLE_KIND),
292
+ context: submissionContextSchema,
293
+ artifacts: z2.array(encryptedBundleFileSchema).min(1).max(
218
294
  MAX_SOLVER_SUBMISSION_FILES,
219
295
  `Submission exceeds the maximum of ${MAX_SOLVER_SUBMISSION_FILES} files.`
220
296
  ).refine(
221
- (artifacts) => new Set(artifacts.map((artifact) => artifact.name.toLowerCase())).size === artifacts.length,
222
- "artifact names must be unique case-insensitively"
297
+ (files) => new Set(files.map((file) => file.name.toLowerCase())).size === files.length,
298
+ "file names must be unique case-insensitively"
223
299
  )
224
300
  });
225
- var privateArtifactRevealPolicySchema = z.strictObject({
226
- kind: z.literal(PRIVATE_ARTIFACT_REVEAL_POLICY_KIND),
227
- network: z.string().min(1),
228
- chain_hash: z.string().min(1),
229
- round: z.string().refine(isPositiveInteger),
230
- not_before: z.number().int().nonnegative().lte(Number.MAX_SAFE_INTEGER)
301
+ var submissionRevealPolicySchema = z2.strictObject({
302
+ kind: z2.literal(SUBMISSION_REVEAL_POLICY_KIND),
303
+ network: z2.string().min(1),
304
+ chain_hash: z2.string().min(1),
305
+ round: z2.string().refine(isPositiveInteger),
306
+ not_before: z2.number().int().nonnegative().lte(Number.MAX_SAFE_INTEGER)
231
307
  });
232
- var wrappedRecipientKeySchema = z.strictObject({
308
+ var wrappedRecipientKeySchema = z2.strictObject({
233
309
  key_id: keyIdSchema,
234
- suite: z.literal(PRIVATE_ARTIFACT_HPKE_SUITE),
310
+ suite: z2.literal(SUBMISSION_HPKE_SUITE),
235
311
  enc: base64UrlSchema,
236
312
  ciphertext: base64UrlSchema
237
313
  });
238
- var wrappedKeyBagSchema = z.strictObject({
239
- kind: z.literal(PRIVATE_ARTIFACT_WRAPPED_KEY_BAG_KIND),
240
- keys: z.array(wrappedRecipientKeySchema).min(1)
314
+ var wrappedKeyBagSchema = z2.strictObject({
315
+ kind: z2.literal(SUBMISSION_WRAPPED_KEY_BAG_KIND),
316
+ keys: z2.array(wrappedRecipientKeySchema).min(1)
241
317
  });
242
- var privateArtifactEnvelopeSchema = z.strictObject({
243
- kind: z.literal(PRIVATE_ARTIFACT_ENVELOPE_KIND),
244
- alg: z.literal(SUBMISSION_ENCRYPTION_TYPE),
245
- context: privateArtifactContextSchema,
246
- public_manifest: privateArtifactPublicManifestSchema,
318
+ var submissionEnvelopeSchema = z2.strictObject({
319
+ kind: z2.literal(SUBMISSION_ENVELOPE_KIND),
320
+ alg: z2.enum([SUBMISSION_ENCRYPTION_TYPE, SUBMISSION_ENCRYPTION_TYPE_V1]),
321
+ context: submissionContextSchema,
322
+ public_manifest: submissionPublicManifestSchema,
247
323
  public_manifest_sha256: sha256HexSchema,
248
324
  encrypted_bundle_sha256: sha256HexSchema,
249
325
  ciphertext_sha256: sha256HexSchema,
250
326
  guardian_recipients: guardianRecipientsSchema,
251
327
  guardian_recipients_commitment: bytes32Schema,
252
- reveal_policy: privateArtifactRevealPolicySchema,
328
+ reveal_policy: submissionRevealPolicySchema,
253
329
  envelope_nonce: base64UrlSchema.length(
254
330
  22,
255
331
  "envelope_nonce must encode 16 bytes"
256
332
  ),
257
333
  iv: base64UrlSchema.length(16, "iv must encode 12 bytes"),
258
334
  wrapped_key_bag: base64UrlSchema,
259
- ciphertext_locator: z.string().min(1)
335
+ ciphertext_locator: z2.string().min(1)
260
336
  });
261
- function parsePrivateArtifactEnvelope(input) {
262
- return privateArtifactEnvelopeSchema.parse(input);
337
+ function parseSubmissionEnvelope(input) {
338
+ return submissionEnvelopeSchema.parse(input);
263
339
  }
264
- function serializePrivateArtifactEnvelope(envelope) {
340
+ function serializeSubmissionEnvelope(envelope) {
265
341
  return canonicalizeJson(
266
- privateArtifactEnvelopeSchema.parse(envelope)
342
+ submissionEnvelopeSchema.parse(envelope)
267
343
  );
268
344
  }
269
- function derivePrivateArtifactEnvelopeCommitment(envelope) {
270
- return keccak256(toBytes(serializePrivateArtifactEnvelope(envelope)));
345
+ function deriveSubmissionEnvelopeCommitment(envelope) {
346
+ return keccak256(toBytes(serializeSubmissionEnvelope(envelope)));
271
347
  }
272
348
  function deriveSolverSubmissionCiphertextLocator(input) {
273
349
  const { chain_id, hub_address, bounty_id, actor_address } = input.context;
274
350
  return `${chain_id}/${hub_address}/${bounty_id}/${actor_address}/${input.ciphertextSha256}.bin`;
275
351
  }
276
- async function verifyPrivateArtifactEnvelope(input) {
277
- const envelope = parsePrivateArtifactEnvelope(input);
352
+ async function verifySubmissionEnvelope(input) {
353
+ const envelope = parseSubmissionEnvelope(input);
278
354
  if (canonicalizeJson(envelope.public_manifest.context) !== canonicalizeJson(envelope.context)) {
279
355
  throw new Error("public_manifest context does not match envelope context");
280
356
  }
@@ -314,7 +390,7 @@ var x25519PrivateKeySchema = base64UrlSchema.length(
314
390
  43,
315
391
  "must encode a 32-byte X25519 private key"
316
392
  );
317
- var winningSolutionDeliveryContextSchema = z.strictObject({
393
+ var winningSolutionDeliveryContextSchema = z2.strictObject({
318
394
  chain_id: chainIdSchema,
319
395
  hub_address: lowerAddressSchema,
320
396
  bounty_id: bountyIdSchema,
@@ -395,7 +471,7 @@ function buildWinningSolutionDeliveryInfo(contextCommitment) {
395
471
  var GUARDIAN_ROSTER_KEY_PROOF_INFO = "elgora/guardian-roster-key-proof/hpke-fixture/v0";
396
472
  var GUARDIAN_ROSTER_KEY_PROOF_MESSAGE_TYPE = "elgora_guardian_roster_key_proof_v0";
397
473
  var GUARDIAN_ROSTER_KEY_PROOF_FIXTURE_BYTES = 32;
398
- var guardianRosterKeyProofContextSchema = z.strictObject({
474
+ var guardianRosterKeyProofContextSchema = z2.strictObject({
399
475
  chain_id: chainIdSchema,
400
476
  hub_address: lowerAddressSchema,
401
477
  proposed_account: lowerAddressSchema,
@@ -465,42 +541,30 @@ function buildGuardianRosterKeyProofSignedPayload(message, fixture) {
465
541
  return toBytes(`${message}
466
542
  ${bytesToHex(fixture).slice(2)}`);
467
543
  }
468
- async function sealPrivateArtifactEnvelope(input) {
469
- const context = privateArtifactContextSchema.parse(
470
- normalizeContext(input.context)
471
- );
472
- const revealPolicy = privateArtifactRevealPolicySchema.parse(
473
- input.reveal_policy
544
+ async function sealSubmissionEnvelope(input) {
545
+ const context = submissionContextSchema.parse(
546
+ normalizeSubmissionContext(input.context)
474
547
  );
548
+ const revealPolicy = submissionRevealPolicySchema.parse(input.reveal_policy);
475
549
  const recipients = canonicalizeGuardianRecipients(input.guardian_recipients);
476
550
  const guardianRecipientsCommitment = deriveGuardianRecipientsCommitment(recipients);
477
- const artifacts = await buildBundleArtifacts(input.artifacts);
478
- const publicManifest = privateArtifactPublicManifestSchema.parse({
479
- kind: PRIVATE_ARTIFACT_PUBLIC_MANIFEST_KIND,
551
+ const publicManifest = submissionPublicManifestSchema.parse({
552
+ kind: SUBMISSION_PUBLIC_MANIFEST_KIND,
480
553
  context
481
554
  });
482
- const encryptedBundle = privateArtifactEncryptedBundleSchema.parse({
483
- kind: PRIVATE_ARTIFACT_ENCRYPTED_BUNDLE_KIND,
484
- context,
485
- artifacts: artifacts.map((artifact) => ({
486
- ...artifact,
487
- bytes: bytesToBase64Url(artifact.bytes)
488
- }))
489
- });
490
- const encryptedBundleBytes = toBytes(
491
- canonicalizeJson(encryptedBundle)
492
- );
555
+ const alg = input.encryption_type;
556
+ const encryptedBundleBytes = input.encryption_type === SUBMISSION_ENCRYPTION_TYPE_V1 ? input.archive : input.bundle;
493
557
  const publicManifestSha256 = await computeSha256Hex(
494
558
  toBytes(canonicalizeJson(publicManifest))
495
559
  );
496
560
  const encryptedBundleSha256 = await computeSha256Hex(encryptedBundleBytes);
497
561
  const randomBytes = input.random_bytes ?? secureRandomBytes;
498
562
  const rawAesKey = randomBytes(AES_256_KEY_BYTES);
499
- const envelopeNonce = randomBytes(PRIVATE_ARTIFACT_NONCE_BYTES);
563
+ const envelopeNonce = randomBytes(SUBMISSION_NONCE_BYTES);
500
564
  const iv = randomBytes(AES_GCM_IV_BYTES);
501
565
  const aadInput = {
502
- kind: PRIVATE_ARTIFACT_ENVELOPE_KIND,
503
- alg: SUBMISSION_ENCRYPTION_TYPE,
566
+ kind: SUBMISSION_ENVELOPE_KIND,
567
+ alg,
504
568
  context,
505
569
  public_manifest_sha256: publicManifestSha256,
506
570
  encrypted_bundle_sha256: encryptedBundleSha256,
@@ -512,7 +576,7 @@ async function sealPrivateArtifactEnvelope(input) {
512
576
  rawAesKey,
513
577
  iv,
514
578
  plaintext: encryptedBundleBytes,
515
- aad: buildPrivateArtifactAad(aadInput)
579
+ aad: buildSubmissionAad(aadInput)
516
580
  });
517
581
  const wrappedKeys = await wrapAesKeyForRecipients({
518
582
  rawAesKey,
@@ -520,7 +584,7 @@ async function sealPrivateArtifactEnvelope(input) {
520
584
  guardianRecipientsCommitment
521
585
  });
522
586
  const wrappedKeyBag = wrappedKeyBagSchema.parse({
523
- kind: PRIVATE_ARTIFACT_WRAPPED_KEY_BAG_KIND,
587
+ kind: SUBMISSION_WRAPPED_KEY_BAG_KIND,
524
588
  keys: wrappedKeys
525
589
  });
526
590
  const timelockedKeyBag = await input.timelock_encrypt({
@@ -532,9 +596,9 @@ async function sealPrivateArtifactEnvelope(input) {
532
596
  context,
533
597
  ciphertextSha256
534
598
  });
535
- const envelope = privateArtifactEnvelopeSchema.parse({
536
- kind: PRIVATE_ARTIFACT_ENVELOPE_KIND,
537
- alg: SUBMISSION_ENCRYPTION_TYPE,
599
+ const envelope = submissionEnvelopeSchema.parse({
600
+ kind: SUBMISSION_ENVELOPE_KIND,
601
+ alg,
538
602
  context,
539
603
  public_manifest: publicManifest,
540
604
  public_manifest_sha256: publicManifestSha256,
@@ -550,67 +614,65 @@ async function sealPrivateArtifactEnvelope(input) {
550
614
  });
551
615
  return { envelope, ciphertext };
552
616
  }
553
- async function openPrivateArtifactEnvelope(input) {
554
- const opened = await openPrivateArtifactEnvelopeWithRawKey(input);
555
- return {
556
- envelope: opened.envelope,
557
- recipient_key_id: opened.recipient_key_id,
558
- artifacts: opened.artifacts
559
- };
617
+ async function openSubmissionEnvelope(input) {
618
+ const { rawAesKey: _rawAesKey, ...opened } = await openSubmissionEnvelopeWithRawKey(input);
619
+ return opened;
560
620
  }
561
- async function openPrivateArtifactEnvelopeForDelivery(input) {
562
- const opened = await openPrivateArtifactEnvelopeWithRawKey(input);
621
+ async function openSubmissionEnvelopeForDelivery(input) {
622
+ const envelope = await verifySubmissionEnvelope(input.envelope);
623
+ const { rawAesKey, recipientKeyId } = await openEnvelopeAesKey(
624
+ envelope,
625
+ input
626
+ );
563
627
  const wrapped_key = await wrapWinningSolutionAesKey({
564
- rawAesKey: opened.rawAesKey,
628
+ rawAesKey,
565
629
  recipientPublicKey: input.delivery_recipient_public_key,
566
630
  context: input.context
567
631
  });
568
- return {
569
- envelope: opened.envelope,
570
- recipient_key_id: opened.recipient_key_id,
571
- wrapped_key
572
- };
632
+ return { envelope, recipient_key_id: recipientKeyId, wrapped_key };
573
633
  }
574
- async function openPrivateArtifactEnvelopeWithRawKey(input) {
575
- const envelope = await verifyPrivateArtifactEnvelope(input.envelope);
576
- await assertCiphertextIntegrity(input.ciphertext, envelope.ciphertext_sha256);
577
- const timelockedKeyBag = base64UrlToBytes(envelope.wrapped_key_bag);
634
+ async function openEnvelopeAesKey(envelope, input) {
578
635
  const wrappedKeyBagBytes = await input.timelock_decrypt({
579
- ciphertext: timelockedKeyBag,
636
+ ciphertext: base64UrlToBytes(envelope.wrapped_key_bag),
580
637
  reveal_policy: envelope.reveal_policy
581
638
  });
582
639
  const wrappedKeyBag = wrappedKeyBagSchema.parse(
583
640
  JSON.parse(new TextDecoder().decode(wrappedKeyBagBytes))
584
641
  );
585
- const openResult = await openAesKeyForRecipient({
642
+ return openAesKeyForRecipient({
586
643
  wrappedKeys: wrappedKeyBag.keys,
587
644
  privateKeys: input.guardian_private_keys,
588
645
  guardianRecipientsCommitment: envelope.guardian_recipients_commitment
589
646
  });
590
- const artifacts = await decryptAndVerifyBundle(envelope, input.ciphertext, {
647
+ }
648
+ async function openSubmissionEnvelopeWithRawKey(input) {
649
+ const envelope = await verifySubmissionEnvelope(input.envelope);
650
+ await assertCiphertextIntegrity(input.ciphertext, envelope.ciphertext_sha256);
651
+ const openResult = await openEnvelopeAesKey(envelope, input);
652
+ const bundle = await decryptAndVerifyBundle(envelope, input.ciphertext, {
591
653
  rawAesKey: openResult.rawAesKey
592
654
  });
593
655
  return {
594
656
  envelope,
595
657
  recipient_key_id: openResult.recipientKeyId,
596
- artifacts,
658
+ ...bundle,
597
659
  rawAesKey: openResult.rawAesKey
598
660
  };
599
661
  }
600
- async function openPrivateArtifactEnvelopeWithKnownAesKey(input) {
601
- const envelope = await verifyPrivateArtifactEnvelope(input.envelope);
662
+ async function openSubmissionEnvelopeWithKnownAesKey(input) {
663
+ const envelope = await verifySubmissionEnvelope(input.envelope);
602
664
  await assertCiphertextIntegrity(input.ciphertext, envelope.ciphertext_sha256);
603
- const artifacts = await decryptAndVerifyBundle(envelope, input.ciphertext, {
665
+ const bundle = await decryptAndVerifyBundle(envelope, input.ciphertext, {
604
666
  rawAesKey: input.rawAesKey
605
667
  });
606
- return { envelope, artifacts };
668
+ return { envelope, ...bundle };
607
669
  }
608
670
  async function decryptAndVerifyBundle(envelope, ciphertext, key) {
609
671
  const decryptedBytes = await decryptAes256Gcm({
610
672
  rawAesKey: key.rawAesKey,
611
673
  iv: base64UrlToBytes(envelope.iv),
612
674
  ciphertext,
613
- aad: buildPrivateArtifactAad({
675
+ aad: buildSubmissionAad({
614
676
  kind: envelope.kind,
615
677
  alg: envelope.alg,
616
678
  context: envelope.context,
@@ -621,63 +683,20 @@ async function decryptAndVerifyBundle(envelope, ciphertext, key) {
621
683
  envelope_nonce: envelope.envelope_nonce
622
684
  })
623
685
  });
624
- const encryptedBundle = privateArtifactEncryptedBundleSchema.parse(
625
- JSON.parse(new TextDecoder().decode(decryptedBytes))
626
- );
627
686
  if (await computeSha256Hex(decryptedBytes) !== envelope.encrypted_bundle_sha256) {
628
687
  throw new Error("encrypted_bundle_sha256 does not match decrypted bundle");
629
688
  }
630
- const rebuiltManifest = privateArtifactPublicManifestSchema.parse({
631
- kind: PRIVATE_ARTIFACT_PUBLIC_MANIFEST_KIND,
632
- context: encryptedBundle.context
633
- });
634
- if (canonicalizeJson(rebuiltManifest) !== canonicalizeJson(envelope.public_manifest)) {
635
- throw new Error("decrypted bundle does not match public_manifest");
636
- }
637
- const artifacts = encryptedBundle.artifacts.map((artifact) => ({
638
- name: artifact.name,
639
- media_type: artifact.media_type,
640
- size_bytes: artifact.size_bytes,
641
- sha256: artifact.sha256,
642
- bytes: base64UrlToBytes(artifact.bytes)
643
- }));
644
- for (const artifact of artifacts) {
645
- if (artifact.bytes.byteLength !== artifact.size_bytes) {
646
- throw new Error(`artifact ${artifact.name} size_bytes mismatch`);
647
- }
648
- if (await computeSha256Hex(artifact.bytes) !== artifact.sha256) {
649
- throw new Error(`artifact ${artifact.name} sha256 mismatch`);
650
- }
651
- }
652
- return artifacts;
653
- }
654
- async function buildBundleArtifacts(artifacts) {
655
- const seen = /* @__PURE__ */ new Set();
656
- const parsed = artifacts.map((artifact) => ({
657
- name: artifactNameSchema.parse(artifact.name),
658
- media_type: mediaTypeSchema.parse(artifact.media_type),
659
- bytes: Uint8Array.from(artifact.bytes)
660
- })).sort((left, right) => compareCanonicalStrings(left.name, right.name));
661
- if (parsed.length === 0) {
662
- throw new Error(
663
- "private artifact envelope must include at least one artifact"
664
- );
689
+ if (envelope.alg === SUBMISSION_ENCRYPTION_TYPE_V1) {
690
+ return { alg: envelope.alg, archive: decryptedBytes };
665
691
  }
666
- return Promise.all(
667
- parsed.map(async (artifact) => {
668
- if (seen.has(artifact.name)) {
669
- throw new Error(`artifact name ${artifact.name} is duplicated`);
670
- }
671
- seen.add(artifact.name);
672
- return {
673
- name: artifact.name,
674
- media_type: artifact.media_type,
675
- size_bytes: artifact.bytes.byteLength,
676
- sha256: await computeSha256Hex(artifact.bytes),
677
- bytes: artifact.bytes
678
- };
679
- })
692
+ const encryptedBundle = submissionEncryptedBundleSchema.parse(
693
+ JSON.parse(new TextDecoder().decode(decryptedBytes))
680
694
  );
695
+ const files = encryptedBundle.artifacts.map((file) => ({
696
+ ...file,
697
+ bytes: base64UrlToBytes(file.bytes)
698
+ }));
699
+ return { alg: envelope.alg, files };
681
700
  }
682
701
  async function assertEnvelopeDigests(envelope) {
683
702
  const manifestSha256 = await computeSha256Hex(
@@ -687,7 +706,7 @@ async function assertEnvelopeDigests(envelope) {
687
706
  throw new Error("public_manifest_sha256 does not match public_manifest");
688
707
  }
689
708
  }
690
- function buildPrivateArtifactAad(input) {
709
+ function buildSubmissionAad(input) {
691
710
  return toBytes(
692
711
  canonicalizeJson({
693
712
  kind: input.kind,
@@ -724,7 +743,7 @@ async function wrapAesKeyForRecipients(input) {
724
743
  }
725
744
  async function openAesKeyForRecipient(input) {
726
745
  const suite = createHpkeSuite();
727
- const wrappedKeys = z.array(wrappedRecipientKeySchema).parse(input.wrappedKeys);
746
+ const wrappedKeys = z2.array(wrappedRecipientKeySchema).parse(input.wrappedKeys);
728
747
  const privateKeys = guardianRecipientPrivateKeysSchema.parse(
729
748
  input.privateKeys
730
749
  );
@@ -764,7 +783,7 @@ async function openAesKeyForRecipient(input) {
764
783
  ...new Set(privateKeys.map((key) => key.key_id))
765
784
  ].sort();
766
785
  throw new Error(
767
- `no matching Guardian private key for private artifact envelope: this envelope was sealed for key_id ${JSON.stringify(wanted)}, but ELGORA_GUARDIAN_PRIVATE_KEYS_JSON configures key_id ${JSON.stringify(configured)}. key_id must be this Guardian's own registered roster name (the name passed to roster:add-guardian), not its account address.`
786
+ `no matching Guardian private key for the Submission envelope: this envelope was sealed for key_id ${JSON.stringify(wanted)}, but ELGORA_GUARDIAN_PRIVATE_KEYS_JSON configures key_id ${JSON.stringify(configured)}. key_id must be this Guardian's own registered roster name (the name passed to roster:add-guardian), not its account address.`
768
787
  );
769
788
  }
770
789
  throw new Error("matching Guardian private key could not open wrapped key", {
@@ -816,7 +835,7 @@ async function decryptAes256Gcm(input) {
816
835
  )
817
836
  );
818
837
  } catch (error) {
819
- throw new Error("private artifact ciphertext authentication failed", {
838
+ throw new Error("Submission ciphertext authentication failed", {
820
839
  cause: error
821
840
  });
822
841
  }
@@ -833,7 +852,7 @@ async function computeSha256Hex(bytes) {
833
852
  }
834
853
  function buildRecipientKeyInfo(guardianRecipientsCommitment) {
835
854
  return toBytes(
836
- `${PRIVATE_ARTIFACT_HPKE_INFO_PREFIX}:${guardianRecipientsCommitment}`
855
+ `${SUBMISSION_HPKE_INFO_PREFIX}:${guardianRecipientsCommitment}`
837
856
  );
838
857
  }
839
858
  function createHpkeSuite() {
@@ -845,7 +864,7 @@ function createHpkeSuite() {
845
864
  }
846
865
  function getWebCrypto() {
847
866
  if (!globalThis.crypto?.subtle) {
848
- throw new Error("WebCrypto is required for private artifact encryption");
867
+ throw new Error("WebCrypto is required for Submission encryption");
849
868
  }
850
869
  return globalThis.crypto;
851
870
  }
@@ -868,7 +887,7 @@ function toArrayBufferView(bytes) {
868
887
  function normalizeBountyId(value) {
869
888
  return bountyIdSchema.parse(value.toString());
870
889
  }
871
- function normalizeContext(context) {
890
+ function normalizeSubmissionContext(context) {
872
891
  return {
873
892
  ...context,
874
893
  hub_address: context.hub_address.toLowerCase(),
@@ -892,29 +911,29 @@ var AGE_ARMOR_END = "-----END AGE ENCRYPTED FILE-----";
892
911
  var AGE_VERSION = "age-encryption.org/v1";
893
912
  var TLOCK_STANZA_PREFIX = "-> tlock ";
894
913
  var AGE_HEADER_SEPARATOR_BYTES = new Uint8Array([10, 45, 45, 45, 32]);
895
- var PrivateArtifactTimelockOpenError = class extends Error {
914
+ var SubmissionTimelockOpenError = class extends Error {
896
915
  code;
897
916
  constructor(code, message, options) {
898
917
  super(message, options);
899
- this.name = "PrivateArtifactTimelockOpenError";
918
+ this.name = "SubmissionTimelockOpenError";
900
919
  this.code = code;
901
920
  }
902
921
  };
903
- function buildPrivateArtifactRevealPolicyForDeadline(input) {
922
+ function buildSubmissionRevealPolicyForDeadline(input) {
904
923
  const revealDelaySeconds = input.revealDelaySeconds ?? DEFAULT_TIMELOCK_REVEAL_DELAY_SECONDS;
905
924
  const notBefore = addUnixSeconds(
906
925
  input.submissionDeadline,
907
926
  revealDelaySeconds
908
927
  );
909
928
  return {
910
- kind: PRIVATE_ARTIFACT_REVEAL_POLICY_KIND,
929
+ kind: SUBMISSION_REVEAL_POLICY_KIND,
911
930
  network: TIMELOCK_DRAND_NETWORK,
912
931
  chain_hash: DEFAULT_TIMELOCK_DRAND_CHAIN_HASH,
913
932
  round: deriveFirstRoundAtOrAfter(notBefore).toString(),
914
933
  not_before: notBefore
915
934
  };
916
935
  }
917
- function getPrivateArtifactTimelockReleaseState(input) {
936
+ function getSubmissionTimelockReleaseState(input) {
918
937
  const releaseTime = Math.max(
919
938
  input.revealPolicy.not_before,
920
939
  roundReleaseUnixSeconds(input.revealPolicy.round)
@@ -924,7 +943,7 @@ function getPrivateArtifactTimelockReleaseState(input) {
924
943
  release_time: releaseTime
925
944
  };
926
945
  }
927
- function createPrivateArtifactTimelockEncrypt(input) {
946
+ function createSubmissionTimelockEncrypt(input) {
928
947
  const chainClient = input?.chainClient ?? mainnetClient();
929
948
  return async ({ plaintext, reveal_policy }) => {
930
949
  assertSupportedRevealPolicy(reveal_policy);
@@ -936,7 +955,7 @@ function createPrivateArtifactTimelockEncrypt(input) {
936
955
  return new TextEncoder().encode(ciphertext);
937
956
  };
938
957
  }
939
- function createPrivateArtifactTimelockDecrypt(input) {
958
+ function createSubmissionTimelockDecrypt(input) {
940
959
  const chainClient = classifyBeaconUnavailableErrors(
941
960
  input?.chainClient ?? mainnetClient()
942
961
  );
@@ -944,18 +963,18 @@ function createPrivateArtifactTimelockDecrypt(input) {
944
963
  return async ({ ciphertext, reveal_policy }) => {
945
964
  try {
946
965
  assertSupportedRevealPolicy(reveal_policy);
947
- assertPrivateArtifactTimelockCiphertextMatchesPolicy({
966
+ assertSubmissionTimelockCiphertextMatchesPolicy({
948
967
  ciphertext,
949
968
  revealPolicy: reveal_policy
950
969
  });
951
- const releaseState = getPrivateArtifactTimelockReleaseState({
970
+ const releaseState = getSubmissionTimelockReleaseState({
952
971
  revealPolicy: reveal_policy,
953
972
  nowSeconds: nowSeconds()
954
973
  });
955
974
  if (releaseState.status !== "released") {
956
- throw new PrivateArtifactTimelockOpenError(
975
+ throw new SubmissionTimelockOpenError(
957
976
  "timelock_not_released",
958
- `private artifact timelock is not released until ${releaseState.release_time}`
977
+ `Submission timelock is not released until ${releaseState.release_time}`
959
978
  );
960
979
  }
961
980
  const plaintext = await timelockDecrypt(
@@ -964,18 +983,18 @@ function createPrivateArtifactTimelockDecrypt(input) {
964
983
  );
965
984
  return Uint8Array.from(plaintext);
966
985
  } catch (error) {
967
- if (error instanceof PrivateArtifactTimelockOpenError) {
986
+ if (error instanceof SubmissionTimelockOpenError) {
968
987
  throw error;
969
988
  }
970
- throw new PrivateArtifactTimelockOpenError(
989
+ throw new SubmissionTimelockOpenError(
971
990
  "timelock_open_failed",
972
- "private artifact timelock ciphertext could not be opened",
991
+ "Submission timelock ciphertext could not be opened",
973
992
  { cause: error }
974
993
  );
975
994
  }
976
995
  };
977
996
  }
978
- function parsePrivateArtifactTimelockHeader(ciphertext) {
997
+ function parseSubmissionTimelockHeader(ciphertext) {
979
998
  const ageText = decodeAgePayload(safeDecodeUtf8(ciphertext));
980
999
  const [version, ...lines] = ageText.split("\n");
981
1000
  if (version !== AGE_VERSION) {
@@ -1001,12 +1020,12 @@ function parsePrivateArtifactTimelockHeader(ciphertext) {
1001
1020
  chain_hash: chainHash
1002
1021
  };
1003
1022
  }
1004
- function assertPrivateArtifactTimelockCiphertextMatchesPolicy(input) {
1005
- const header = parsePrivateArtifactTimelockHeader(input.ciphertext);
1006
- if (header.round !== input.revealPolicy.round) {
1023
+ function assertSubmissionTimelockCiphertextMatchesPolicy(input) {
1024
+ const header2 = parseSubmissionTimelockHeader(input.ciphertext);
1025
+ if (header2.round !== input.revealPolicy.round) {
1007
1026
  throw new Error("timelock ciphertext round does not match reveal_policy");
1008
1027
  }
1009
- if (header.chain_hash !== input.revealPolicy.chain_hash) {
1028
+ if (header2.chain_hash !== input.revealPolicy.chain_hash) {
1010
1029
  throw new Error(
1011
1030
  "timelock ciphertext chain_hash does not match reveal_policy"
1012
1031
  );
@@ -1049,14 +1068,14 @@ function assertUnixSeconds(value, label) {
1049
1068
  }
1050
1069
  }
1051
1070
  function assertSupportedRevealPolicy(revealPolicy) {
1052
- if (revealPolicy.kind !== PRIVATE_ARTIFACT_REVEAL_POLICY_KIND) {
1053
- throw new Error("unsupported private artifact reveal_policy kind");
1071
+ if (revealPolicy.kind !== SUBMISSION_REVEAL_POLICY_KIND) {
1072
+ throw new Error("unsupported Submission reveal_policy kind");
1054
1073
  }
1055
1074
  if (revealPolicy.network !== TIMELOCK_DRAND_NETWORK) {
1056
- throw new Error("unsupported private artifact timelock network");
1075
+ throw new Error("unsupported Submission timelock network");
1057
1076
  }
1058
1077
  if (revealPolicy.chain_hash !== DEFAULT_TIMELOCK_DRAND_CHAIN_HASH) {
1059
- throw new Error("unsupported private artifact timelock chain hash");
1078
+ throw new Error("unsupported Submission timelock chain hash");
1060
1079
  }
1061
1080
  }
1062
1081
  function decodeAgePayload(value) {
@@ -1077,8 +1096,8 @@ function decodeAgePayload(value) {
1077
1096
  }
1078
1097
  function decodeAgeHeader(bytes) {
1079
1098
  const separatorIndex = indexOfBytes(bytes, AGE_HEADER_SEPARATOR_BYTES);
1080
- const header = separatorIndex >= 0 ? bytes.subarray(0, separatorIndex) : bytes;
1081
- return safeDecodeUtf8(header);
1099
+ const header2 = separatorIndex >= 0 ? bytes.subarray(0, separatorIndex) : bytes;
1100
+ return safeDecodeUtf8(header2);
1082
1101
  }
1083
1102
  function indexOfBytes(haystack, needle) {
1084
1103
  for (let index = 0; index <= haystack.length - needle.length; index += 1) {
@@ -1113,9 +1132,9 @@ async function classifyBeaconFetch(operation) {
1113
1132
  try {
1114
1133
  return await operation();
1115
1134
  } catch (error) {
1116
- throw new PrivateArtifactTimelockOpenError(
1135
+ throw new SubmissionTimelockOpenError(
1117
1136
  "timelock_beacon_unavailable",
1118
- "drand beacon is unavailable for private artifact timelock",
1137
+ "drand beacon is unavailable for Submission timelock",
1119
1138
  { cause: error }
1120
1139
  );
1121
1140
  }
@@ -1185,8 +1204,8 @@ function readRevealDelaySeconds(args) {
1185
1204
  }
1186
1205
  return parsed;
1187
1206
  }
1188
- function readInput(path6) {
1189
- return path6 === "-" ? readStdinText(process.stdin) : readFile(path6, "utf8");
1207
+ function readInput(path7) {
1208
+ return path7 === "-" ? readStdinText(process.stdin) : readFile(path7, "utf8");
1190
1209
  }
1191
1210
  function usageError(usage) {
1192
1211
  return new Error(`Usage: ${usage}`);
@@ -1214,25 +1233,124 @@ function extractFlagValue(argv, flag) {
1214
1233
  return { value, argv: remaining };
1215
1234
  }
1216
1235
 
1236
+ // ../../packages/core/src/bounty-challenge-headings.ts
1237
+ function hasLineBreak(text) {
1238
+ return /[\r\u2028\u2029]/.test(text);
1239
+ }
1240
+
1217
1241
  // ../../packages/core/src/bounty-challenge-markdown.ts
1218
1242
  import { keccak256 as keccak2562, toBytes as toBytes2 } from "viem";
1219
- import { z as z2 } from "zod";
1243
+ import { z as z3 } from "zod";
1244
+
1245
+ // ../../packages/core/src/protocol-profile.ts
1246
+ var PROTOCOL_PROFILE_V0 = Object.freeze({
1247
+ profile: BOUNTY_CHALLENGE_PROFILE,
1248
+ submission: Object.freeze({
1249
+ maxCiphertextBytes: MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES,
1250
+ maxExtractedBytes: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES,
1251
+ maxFiles: MAX_SOLVER_SUBMISSION_FILES,
1252
+ filename: Object.freeze({
1253
+ maxCharacters: MAX_SOLVER_SUBMISSION_NAME_CHARACTERS,
1254
+ firstCharacter: "letter_or_digit",
1255
+ allowedCharacters: "letters_digits_dot_underscore_hyphen",
1256
+ caseInsensitivelyUnique: true
1257
+ }),
1258
+ layout: Object.freeze({
1259
+ flat: true,
1260
+ regularFilesOnly: true,
1261
+ allowsSubdirectories: false,
1262
+ allowsSymlinks: false,
1263
+ allowsEmpty: false,
1264
+ allowsArchivedDirectoryEntries: false,
1265
+ allowsArchivedPathTraversal: false
1266
+ }),
1267
+ externalReferences: false
1268
+ }),
1269
+ challenge: Object.freeze({
1270
+ maxMarkdownCharacters: MAX_BOUNTY_CHALLENGE_MARKDOWN_CHARACTERS,
1271
+ requiredFrontmatterKeys: Object.freeze([
1272
+ ...BOUNTY_CHALLENGE_FRONTMATTER_KEYS
1273
+ ]),
1274
+ optionalFrontmatterKeys: Object.freeze([
1275
+ ...BOUNTY_CHALLENGE_OPTIONAL_FRONTMATTER_KEYS
1276
+ ]),
1277
+ payoutPolicies: Object.freeze([WINNER_TAKE_ALL_PAYOUT_POLICY])
1278
+ }),
1279
+ encryption: Object.freeze({
1280
+ type: SUBMISSION_ENCRYPTION_TYPE
1281
+ }),
1282
+ tightenable: Object.freeze({
1283
+ max_extracted_bytes: Object.freeze({
1284
+ comparison: "at_most",
1285
+ profileValue: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES
1286
+ }),
1287
+ max_files: Object.freeze({
1288
+ comparison: "at_most",
1289
+ profileValue: MAX_SOLVER_SUBMISSION_FILES
1290
+ }),
1291
+ allowed_extensions: Object.freeze({ comparison: "subset" }),
1292
+ required_files: Object.freeze({ comparison: "filename_list" })
1293
+ })
1294
+ });
1295
+ var PROTOCOL_PROFILE_V1 = Object.freeze({
1296
+ profile: BOUNTY_CHALLENGE_PROFILE_V1,
1297
+ submission: Object.freeze({
1298
+ maxCiphertextBytes: MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES,
1299
+ maxExtractedBytes: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES,
1300
+ maxFiles: MAX_SOLVER_SUBMISSION_FILES_V1,
1301
+ filename: PROTOCOL_PROFILE_V0.submission.filename,
1302
+ layout: Object.freeze({
1303
+ regularFilesOnly: true,
1304
+ allowsSubdirectories: true,
1305
+ allowsSymlinks: false,
1306
+ allowsEmpty: false,
1307
+ allowsArchivedDirectoryEntries: true,
1308
+ allowsArchivedPathTraversal: false
1309
+ }),
1310
+ path: Object.freeze({
1311
+ maxCharacters: MAX_SOLVER_SUBMISSION_PATH_CHARACTERS
1312
+ }),
1313
+ externalReferences: false
1314
+ }),
1315
+ challenge: PROTOCOL_PROFILE_V0.challenge,
1316
+ encryption: Object.freeze({
1317
+ type: SUBMISSION_ENCRYPTION_TYPE_V1
1318
+ }),
1319
+ tightenable: Object.freeze({
1320
+ max_extracted_bytes: Object.freeze({
1321
+ comparison: "at_most",
1322
+ profileValue: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES
1323
+ }),
1324
+ max_files: Object.freeze({
1325
+ comparison: "at_most",
1326
+ profileValue: MAX_SOLVER_SUBMISSION_FILES_V1
1327
+ }),
1328
+ allowed_extensions: Object.freeze({ comparison: "subset" }),
1329
+ required_files: Object.freeze({ comparison: "path_list" })
1330
+ })
1331
+ });
1332
+ var PROTOCOL_PROFILES = /* @__PURE__ */ new Map([
1333
+ [PROTOCOL_PROFILE_V0.profile, PROTOCOL_PROFILE_V0],
1334
+ [PROTOCOL_PROFILE_V1.profile, PROTOCOL_PROFILE_V1]
1335
+ ]);
1336
+ var CURRENT_PROTOCOL_PROFILE = PROTOCOL_PROFILE_V1;
1337
+ var PROTOCOL_PROFILE_VERSIONS = Object.freeze([
1338
+ ...PROTOCOL_PROFILES.keys()
1339
+ ]);
1340
+ function getProtocolProfile(version) {
1341
+ return PROTOCOL_PROFILES.get(version) ?? null;
1342
+ }
1343
+ function isKnownProtocolProfile(version) {
1344
+ return PROTOCOL_PROFILES.has(version);
1345
+ }
1346
+
1347
+ // ../../packages/core/src/bounty-challenge-markdown.ts
1220
1348
  var FRONTMATTER_DELIMITER = "---";
1221
- var MAX_BOUNTY_CHALLENGE_MARKDOWN_CHARACTERS = 1e6;
1222
- var BOUNTY_CHALLENGE_FRONTMATTER_KEYS = [
1223
- "profile",
1224
- "escrow_amount",
1225
- "submission_deadline",
1226
- "payout_policy"
1227
- ];
1228
- var BOUNTY_CHALLENGE_OPTIONAL_FRONTMATTER_KEYS = [
1229
- "constraints"
1230
- ];
1231
1349
  var ALLOWED_FRONTMATTER_KEYS = [
1232
1350
  ...BOUNTY_CHALLENGE_FRONTMATTER_KEYS,
1233
1351
  ...BOUNTY_CHALLENGE_OPTIONAL_FRONTMATTER_KEYS
1234
1352
  ].join(", ");
1235
- var ALLOWED_FRONTMATTER_KEYS_MESSAGE = `Use only the v0 template frontmatter keys: ${ALLOWED_FRONTMATTER_KEYS}`;
1353
+ var ALLOWED_FRONTMATTER_KEYS_MESSAGE = `Use only the template frontmatter keys: ${ALLOWED_FRONTMATTER_KEYS}`;
1236
1354
  var DEPLOYMENT_OWNED_FRONTMATTER_KEYS = /* @__PURE__ */ new Set([
1237
1355
  "chain_id",
1238
1356
  "token_address",
@@ -1257,7 +1375,7 @@ var BountyChallengeSpecValidationError = class extends Error {
1257
1375
  this.issues = issues;
1258
1376
  }
1259
1377
  };
1260
- var markdownSchema = z2.string().min(1, "Markdown body must not be empty").max(
1378
+ var markdownSchema = z3.string().min(1, "Markdown body must not be empty").max(
1261
1379
  MAX_BOUNTY_CHALLENGE_MARKDOWN_CHARACTERS,
1262
1380
  `Markdown body must be at most ${MAX_BOUNTY_CHALLENGE_MARKDOWN_CHARACTERS} characters`
1263
1381
  );
@@ -1341,16 +1459,15 @@ function splitConstraintsBlock(lines, issues) {
1341
1459
  if (entry.trim().length === 0) break;
1342
1460
  if (!/^\s+\S/.test(entry)) break;
1343
1461
  index += 1;
1344
- const match = entry.trim().match(/^([a-z][a-z0-9_]*):\s*(.+)$/);
1345
- const key = match?.[1];
1346
- const rawValue = match?.[2];
1347
- if (key === void 0 || rawValue === void 0) {
1462
+ const parsed = frontmatterEntry(entry.trim());
1463
+ if (!parsed || parsed.value === "") {
1348
1464
  issues.push({
1349
1465
  path: `frontmatter.constraints.line_${index + 2}`,
1350
1466
  message: "Constraint entries must use snake_case_key: value"
1351
1467
  });
1352
1468
  continue;
1353
1469
  }
1470
+ const { key, value: rawValue } = parsed;
1354
1471
  if (seenKeys.has(key)) {
1355
1472
  issues.push({
1356
1473
  path: `frontmatter.constraints.${key}`,
@@ -1377,28 +1494,26 @@ function splitConstraintsBlock(lines, issues) {
1377
1494
  }
1378
1495
  return { scalarLines, constraints };
1379
1496
  }
1497
+ function frontmatterEntry(line) {
1498
+ const key = /^[a-z][a-z0-9_]*:/.exec(line)?.[0];
1499
+ if (!key) return null;
1500
+ const value = line.slice(key.length).trimStart();
1501
+ return hasLineBreak(value) ? null : { key: key.slice(0, -1), value };
1502
+ }
1380
1503
  function parsePublicationTermsFrontmatter(lines, issues) {
1381
1504
  const values = /* @__PURE__ */ new Map();
1382
1505
  let foundUnsupportedKey = false;
1383
1506
  for (const { text: line, index } of lines) {
1384
1507
  if (line.trim().length === 0) continue;
1385
- const match = line.match(/^([a-z][a-z0-9_]*):\s*(.*)$/);
1386
- if (!match) {
1387
- issues.push({
1388
- path: `frontmatter.line_${index + 2}`,
1389
- message: "Frontmatter entries must use snake_case_key: value"
1390
- });
1391
- continue;
1392
- }
1393
- const rawKey = match[1];
1394
- const rawValue = match[2];
1395
- if (rawKey === void 0 || rawValue === void 0) {
1508
+ const parsed = frontmatterEntry(line);
1509
+ if (!parsed) {
1396
1510
  issues.push({
1397
1511
  path: `frontmatter.line_${index + 2}`,
1398
1512
  message: "Frontmatter entries must use snake_case_key: value"
1399
1513
  });
1400
1514
  continue;
1401
1515
  }
1516
+ const { key: rawKey, value: rawValue } = parsed;
1402
1517
  if (!isBountyChallengeFrontmatterKey(rawKey)) {
1403
1518
  if (DEPLOYMENT_OWNED_FRONTMATTER_KEYS.has(rawKey)) {
1404
1519
  if (!foundUnsupportedKey) {
@@ -1411,7 +1526,7 @@ function parsePublicationTermsFrontmatter(lines, issues) {
1411
1526
  } else {
1412
1527
  issues.push({
1413
1528
  path: `frontmatter.${rawKey}`,
1414
- message: `Remove frontmatter key "${rawKey}"; use only the v0 template frontmatter keys: ${ALLOWED_FRONTMATTER_KEYS}`
1529
+ message: `Remove frontmatter key "${rawKey}"; use only the template frontmatter keys: ${ALLOWED_FRONTMATTER_KEYS}`
1415
1530
  });
1416
1531
  }
1417
1532
  continue;
@@ -1439,10 +1554,10 @@ function parsePublicationTermsFrontmatter(lines, issues) {
1439
1554
  }
1440
1555
  }
1441
1556
  const profile = values.get("profile");
1442
- if (profile !== void 0 && profile !== BOUNTY_CHALLENGE_PROFILE) {
1557
+ if (profile !== void 0 && !isKnownProtocolProfile(profile)) {
1443
1558
  issues.push({
1444
1559
  path: "frontmatter.profile",
1445
- message: `Must equal ${BOUNTY_CHALLENGE_PROFILE}`
1560
+ message: `Must be a published protocol profile: ${PROTOCOL_PROFILE_VERSIONS.join(", ")}`
1446
1561
  });
1447
1562
  }
1448
1563
  const payoutPolicy = values.get("payout_policy");
@@ -1462,7 +1577,7 @@ function parsePublicationTermsFrontmatter(lines, issues) {
1462
1577
  "frontmatter.escrow_amount",
1463
1578
  issues
1464
1579
  );
1465
- if (profile !== BOUNTY_CHALLENGE_PROFILE || payoutPolicy !== WINNER_TAKE_ALL_PAYOUT_POLICY || escrowAmount === null || submissionDeadline === null) {
1580
+ if (profile === void 0 || !isKnownProtocolProfile(profile) || payoutPolicy !== WINNER_TAKE_ALL_PAYOUT_POLICY || escrowAmount === null || submissionDeadline === null) {
1466
1581
  return null;
1467
1582
  }
1468
1583
  return {
@@ -1472,40 +1587,40 @@ function parsePublicationTermsFrontmatter(lines, issues) {
1472
1587
  payoutPolicy
1473
1588
  };
1474
1589
  }
1475
- function normalizeFrontmatterScalar(rawValue, path6, issues) {
1590
+ function normalizeFrontmatterScalar(rawValue, path7, issues) {
1476
1591
  const value = rawValue.trim();
1477
1592
  if (value.length === 0) {
1478
- issues.push({ path: path6, message: "Frontmatter value must not be empty" });
1593
+ issues.push({ path: path7, message: "Frontmatter value must not be empty" });
1479
1594
  return null;
1480
1595
  }
1481
1596
  const quote = value[0];
1482
1597
  if (quote !== '"' && quote !== "'") return value;
1483
1598
  if (value.length < 2 || value.at(-1) !== quote) {
1484
1599
  issues.push({
1485
- path: path6,
1600
+ path: path7,
1486
1601
  message: "Quoted frontmatter values must close with the same quote"
1487
1602
  });
1488
1603
  return null;
1489
1604
  }
1490
1605
  return value.slice(1, -1);
1491
1606
  }
1492
- function parsePositiveSafeIntegerFrontmatter(value, path6, issues) {
1607
+ function parsePositiveSafeIntegerFrontmatter(value, path7, issues) {
1493
1608
  if (value === void 0) return null;
1494
1609
  if (!isPositiveInteger(value)) {
1495
- issues.push({ path: path6, message: "Must be a positive integer" });
1610
+ issues.push({ path: path7, message: "Must be a positive integer" });
1496
1611
  return null;
1497
1612
  }
1498
1613
  const parsed = Number(value);
1499
1614
  if (!Number.isSafeInteger(parsed) || parsed <= 0) {
1500
- issues.push({ path: path6, message: "Must be a positive safe integer" });
1615
+ issues.push({ path: path7, message: "Must be a positive safe integer" });
1501
1616
  return null;
1502
1617
  }
1503
1618
  return parsed;
1504
1619
  }
1505
- function parsePositiveIntegerStringFrontmatter(value, path6, issues) {
1620
+ function parsePositiveIntegerStringFrontmatter(value, path7, issues) {
1506
1621
  if (value === void 0) return null;
1507
1622
  if (!isPositiveInteger(value)) {
1508
- issues.push({ path: path6, message: "Must be positive token smallest units" });
1623
+ issues.push({ path: path7, message: "Must be positive token smallest units" });
1509
1624
  return null;
1510
1625
  }
1511
1626
  return value;
@@ -1529,36 +1644,36 @@ function isBountyChallengeFrontmatterKey(value) {
1529
1644
 
1530
1645
  // ../../packages/core/src/bounty-publication-response.ts
1531
1646
  import { isHex as isHex2 } from "viem";
1532
- import { z as z3 } from "zod";
1533
- var addressSchema = z3.string().refine(isHexAddress, "Must be a 20-byte EVM address").transform((value) => value);
1534
- var bytes32Schema2 = z3.string().refine(isHexBytes32, "Must be a bytes32 hex string").transform((value) => value);
1535
- var hexBytesSchema = z3.string().refine(isHex2, "Must be hex-encoded calldata").transform((value) => value);
1536
- var bountyChallengeContentRecordSchema = z3.object({
1537
- content_kind: z3.literal("bounty_challenge_spec"),
1647
+ import { z as z4 } from "zod";
1648
+ var addressSchema = z4.string().refine(isHexAddress, "Must be a 20-byte EVM address").transform((value) => value);
1649
+ var bytes32Schema2 = z4.string().refine(isHexBytes32, "Must be a bytes32 hex string").transform((value) => value);
1650
+ var hexBytesSchema = z4.string().refine(isHex2, "Must be hex-encoded calldata").transform((value) => value);
1651
+ var bountyChallengeContentRecordSchema = z4.object({
1652
+ content_kind: z4.literal("bounty_challenge_spec"),
1538
1653
  spec_commitment: bytes32Schema2,
1539
- byte_length: z3.number().int().positive(),
1654
+ byte_length: z4.number().int().positive(),
1540
1655
  token_address: addressSchema,
1541
- created_at: z3.string().datetime({ offset: true })
1656
+ created_at: z4.string().datetime({ offset: true })
1542
1657
  }).strict();
1543
- var bountyPublicationResponseSchema = z3.object({
1658
+ var bountyPublicationResponseSchema = z4.object({
1544
1659
  content_record: bountyChallengeContentRecordSchema,
1545
- create_bounty_transaction: z3.object({
1546
- chain_id: z3.number().int().positive(),
1660
+ create_bounty_transaction: z4.object({
1661
+ chain_id: z4.number().int().positive(),
1547
1662
  to: addressSchema,
1548
- value: z3.literal("0"),
1663
+ value: z4.literal("0"),
1549
1664
  data: hexBytesSchema,
1550
- function_name: z3.literal("create_bounty"),
1551
- args: z3.object({
1665
+ function_name: z4.literal("create_bounty"),
1666
+ args: z4.object({
1552
1667
  spec_commitment: bytes32Schema2,
1553
- escrow_amount: z3.string().refine(isPositiveInteger),
1554
- submission_deadline: z3.number().int().positive()
1668
+ escrow_amount: z4.string().refine(isPositiveInteger),
1669
+ submission_deadline: z4.number().int().positive()
1555
1670
  }).strict()
1556
1671
  }).strict(),
1557
- protocol_fee_policy: z3.object({
1672
+ protocol_fee_policy: z4.object({
1558
1673
  treasury_recipient: addressSchema,
1559
- treasury_fee_bps: z3.number().int().min(0).max(1e4),
1674
+ treasury_fee_bps: z4.number().int().min(0).max(1e4),
1560
1675
  guardian_fee_recipient: addressSchema,
1561
- guardian_fee_bps: z3.number().int().min(0).max(1e4)
1676
+ guardian_fee_bps: z4.number().int().min(0).max(1e4)
1562
1677
  }).strict()
1563
1678
  }).strict().superRefine((response, context) => {
1564
1679
  if (response.content_record.spec_commitment !== response.create_bounty_transaction.args.spec_commitment) {
@@ -1569,31 +1684,31 @@ var bountyPublicationResponseSchema = z3.object({
1569
1684
  });
1570
1685
  }
1571
1686
  });
1572
- var priorIssueSchema = z3.object({
1573
- issue_index: z3.number().int().nonnegative(),
1574
- message: z3.string().trim().min(1).max(1500)
1687
+ var priorIssueSchema = z4.object({
1688
+ issue_index: z4.number().int().nonnegative(),
1689
+ message: z4.string().trim().min(1).max(1500)
1575
1690
  }).strict();
1576
1691
  var priorIssueStatusSchema = priorIssueSchema.extend({
1577
- status: z3.enum(["resolved", "open"])
1692
+ status: z4.enum(["resolved", "open"])
1578
1693
  }).strict();
1579
- var readinessFindingSchema = z3.object({
1580
- path: z3.string().trim().min(1).max(120),
1581
- message: z3.string().trim().min(1).max(1500),
1582
- code: z3.string().min(1).optional(),
1583
- anchor: z3.string().trim().min(1).max(120).optional(),
1584
- hazard: z3.boolean().optional()
1694
+ var readinessFindingSchema = z4.object({
1695
+ path: z4.string().trim().min(1).max(120),
1696
+ message: z4.string().trim().min(1).max(1500),
1697
+ code: z4.string().min(1).optional(),
1698
+ anchor: z4.string().trim().min(1).max(120).optional(),
1699
+ hazard: z4.boolean().optional()
1585
1700
  });
1586
1701
  var MAX_READINESS_FINDINGS = 20;
1587
- var bountyReviewResponseSchema = z3.object({
1702
+ var bountyReviewResponseSchema = z4.object({
1588
1703
  content_record: bountyChallengeContentRecordSchema.omit({ created_at: true }),
1589
- readiness: z3.object({
1590
- decision: z3.literal("ready"),
1704
+ readiness: z4.object({
1705
+ decision: z4.literal("ready"),
1591
1706
  // Publication reuses the approval of these exact bytes until then.
1592
- approved_until: z3.string().datetime({ offset: true }),
1707
+ approved_until: z4.string().datetime({ offset: true }),
1593
1708
  // The dissenting pass's findings on a ready check; never blocking.
1594
- warnings: z3.array(readinessFindingSchema).max(MAX_READINESS_FINDINGS),
1595
- prior_issues_resolved: z3.array(priorIssueSchema),
1596
- prior_issues_open: z3.array(priorIssueSchema)
1709
+ warnings: z4.array(readinessFindingSchema).max(MAX_READINESS_FINDINGS),
1710
+ prior_issues_resolved: z4.array(priorIssueSchema),
1711
+ prior_issues_open: z4.array(priorIssueSchema)
1597
1712
  })
1598
1713
  });
1599
1714
 
@@ -1615,76 +1730,15 @@ function resolveSupportedChain(chainId) {
1615
1730
  }
1616
1731
  var FOUNDRY_CHAIN_ID = foundry.id;
1617
1732
 
1618
- // ../../packages/core/src/protocol-profile.ts
1619
- var PROTOCOL_PROFILE_V0 = Object.freeze({
1620
- profile: BOUNTY_CHALLENGE_PROFILE,
1621
- submission: Object.freeze({
1622
- maxCiphertextBytes: MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES,
1623
- maxExtractedBytes: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES,
1624
- maxFiles: MAX_SOLVER_SUBMISSION_FILES,
1625
- filename: Object.freeze({
1626
- maxCharacters: MAX_SOLVER_SUBMISSION_NAME_CHARACTERS,
1627
- firstCharacter: "letter_or_digit",
1628
- allowedCharacters: "letters_digits_dot_underscore_hyphen",
1629
- caseInsensitivelyUnique: true
1630
- }),
1631
- layout: Object.freeze({
1632
- flat: true,
1633
- regularFilesOnly: true,
1634
- allowsSubdirectories: false,
1635
- allowsSymlinks: false,
1636
- allowsEmpty: false,
1637
- allowsArchivedDirectoryEntries: false,
1638
- allowsArchivedPathTraversal: false
1639
- }),
1640
- externalReferences: false
1641
- }),
1642
- challenge: Object.freeze({
1643
- maxMarkdownCharacters: MAX_BOUNTY_CHALLENGE_MARKDOWN_CHARACTERS,
1644
- requiredFrontmatterKeys: Object.freeze([
1645
- ...BOUNTY_CHALLENGE_FRONTMATTER_KEYS
1646
- ]),
1647
- optionalFrontmatterKeys: Object.freeze([
1648
- ...BOUNTY_CHALLENGE_OPTIONAL_FRONTMATTER_KEYS
1649
- ]),
1650
- payoutPolicies: Object.freeze([WINNER_TAKE_ALL_PAYOUT_POLICY])
1651
- }),
1652
- encryption: Object.freeze({
1653
- type: SUBMISSION_ENCRYPTION_TYPE
1654
- }),
1655
- tightenable: Object.freeze({
1656
- max_extracted_bytes: Object.freeze({
1657
- comparison: "at_most",
1658
- profileValue: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES
1659
- }),
1660
- max_files: Object.freeze({
1661
- comparison: "at_most",
1662
- profileValue: MAX_SOLVER_SUBMISSION_FILES
1663
- }),
1664
- allowed_extensions: Object.freeze({ comparison: "subset" }),
1665
- required_files: Object.freeze({ comparison: "filename_list" })
1666
- })
1667
- });
1668
- var PROTOCOL_PROFILES = /* @__PURE__ */ new Map([
1669
- [PROTOCOL_PROFILE_V0.profile, PROTOCOL_PROFILE_V0]
1670
- ]);
1671
- var CURRENT_PROTOCOL_PROFILE = PROTOCOL_PROFILE_V0;
1672
- var PROTOCOL_PROFILE_VERSIONS = Object.freeze([
1673
- ...PROTOCOL_PROFILES.keys()
1674
- ]);
1675
- function getProtocolProfile(version) {
1676
- return PROTOCOL_PROFILES.get(version) ?? null;
1677
- }
1678
-
1679
1733
  // ../../packages/core/src/challenge-conformance.ts
1680
1734
  function checkChallengeConformance(input, profile = CURRENT_PROTOCOL_PROFILE) {
1681
1735
  const issues = [];
1682
1736
  const parsed = typeof input === "string" ? readFrontmatter(input, issues) : input;
1683
- const pinned = parsed === null ? null : resolveProfile(parsed.publicationTerms.profile, profile, issues);
1684
- if (parsed !== null && pinned !== null) {
1685
- checkTightenings(parsed.constraints, pinned, issues);
1737
+ const declaredProfile = parsed === null ? null : resolveProfile(parsed.publicationTerms.profile, profile, issues);
1738
+ if (parsed !== null && declaredProfile !== null) {
1739
+ checkTightenings(parsed.constraints, declaredProfile, issues);
1686
1740
  }
1687
- return { issues, profile: pinned };
1741
+ return { issues, profile: declaredProfile };
1688
1742
  }
1689
1743
  function readFrontmatter(markdown, issues) {
1690
1744
  try {
@@ -1703,16 +1757,16 @@ function readFrontmatter(markdown, issues) {
1703
1757
  }
1704
1758
  }
1705
1759
  function resolveProfile(declared, fallback2, issues) {
1706
- const pinned = getProtocolProfile(declared) ?? (declared === fallback2.profile ? fallback2 : null);
1707
- if (pinned === null) {
1760
+ const known = getProtocolProfile(declared) ?? (declared === fallback2.profile ? fallback2 : null);
1761
+ if (known === null) {
1708
1762
  issues.push({
1709
1763
  code: "profile_unknown",
1710
1764
  anchor: "frontmatter.profile",
1711
1765
  path: "frontmatter.profile",
1712
- message: `Unknown protocol profile "${declared}". A bounty can only pin a published profile version.`
1766
+ message: `Unknown protocol profile "${declared}". A bounty can only declare a published profile version.`
1713
1767
  });
1714
1768
  }
1715
- return pinned;
1769
+ return known;
1716
1770
  }
1717
1771
  function checkTightenings(constraints, profile, issues) {
1718
1772
  for (const constraint of constraints) {
@@ -1733,6 +1787,30 @@ function checkTightenings(constraints, profile, issues) {
1733
1787
  }
1734
1788
  checkListTightening(constraint, rule, profile, anchor, issues);
1735
1789
  }
1790
+ checkRequiredFilesAllowed(constraints, issues);
1791
+ }
1792
+ function checkRequiredFilesAllowed(constraints, issues) {
1793
+ const required2 = constraints.find((c) => c.key === "required_files");
1794
+ const allowed = constraints.find((c) => c.key === "allowed_extensions");
1795
+ if (required2 === void 0 || allowed === void 0) return;
1796
+ const anchors = [required2, allowed].map(
1797
+ (constraint) => `frontmatter.constraints.${constraint.key}`
1798
+ );
1799
+ if (issues.some((issue) => anchors.includes(issue.anchor))) return;
1800
+ const paths = parseInlineList(required2.value);
1801
+ const extensions = parseInlineList(allowed.value);
1802
+ if (paths === null || extensions === null) return;
1803
+ const excluded = paths.filter(
1804
+ (file) => !hasAllowedExtension(file, extensions)
1805
+ );
1806
+ if (excluded.length > 0) {
1807
+ issues.push({
1808
+ code: "tightening_malformed",
1809
+ anchor: anchors[0],
1810
+ path: anchors[0],
1811
+ message: `"required_files" names ${excluded.join(", ")}, which "allowed_extensions" excludes, so no Solver could satisfy both.`
1812
+ });
1813
+ }
1736
1814
  }
1737
1815
  function checkNumericTightening(constraint, rule, anchor, issues) {
1738
1816
  const ceiling = rule.profileValue;
@@ -1791,6 +1869,10 @@ function checkListTightening(constraint, rule, profile, anchor, issues) {
1791
1869
  return;
1792
1870
  }
1793
1871
  const { filename, maxFiles } = profile.submission;
1872
+ if (rule.comparison === "path_list") {
1873
+ checkRequiredPaths(constraint, members, maxFiles, anchor, issues);
1874
+ return;
1875
+ }
1794
1876
  const malformed = members.filter(
1795
1877
  (member) => member.length > filename.maxCharacters || !SOLVER_SUBMISSION_NAME_RE.test(member)
1796
1878
  );
@@ -1830,6 +1912,57 @@ function checkListTightening(constraint, rule, profile, anchor, issues) {
1830
1912
  });
1831
1913
  }
1832
1914
  }
1915
+ function checkRequiredPaths(constraint, members, maxFiles, anchor, issues) {
1916
+ const malformed = members.filter(
1917
+ (member) => describeSolverSubmissionPathIssue(member) !== null
1918
+ );
1919
+ if (malformed.length > 0) {
1920
+ issues.push({
1921
+ code: "tightening_malformed",
1922
+ anchor,
1923
+ path: anchor,
1924
+ message: `"${constraint.key}" names paths that do not satisfy the profile's path rule: ${malformed.join(", ")}. A path is "/"-separated names, each starting with a letter or digit and using only letters, digits, ".", "_" and "-".`
1925
+ });
1926
+ return;
1927
+ }
1928
+ const conflict = findSolverSubmissionPathConflict(members);
1929
+ if (conflict !== null) {
1930
+ issues.push({
1931
+ code: "tightening_malformed",
1932
+ anchor,
1933
+ path: anchor,
1934
+ message: `"${constraint.key}" cannot be satisfied: ${describeSolverSubmissionPathConflict(conflict)}.`
1935
+ });
1936
+ return;
1937
+ }
1938
+ if (members.length > maxFiles) {
1939
+ issues.push({
1940
+ code: "tightening_exceeds_profile",
1941
+ anchor,
1942
+ path: anchor,
1943
+ message: `"${constraint.key}" requires ${members.length} files, above the profile's ceiling of ${maxFiles}.`
1944
+ });
1945
+ }
1946
+ }
1947
+ function hasAllowedExtension(path7, extensions) {
1948
+ const name = path7.toLowerCase();
1949
+ return extensions.some((extension) => name.endsWith(extension.toLowerCase()));
1950
+ }
1951
+ function readChallengeConstraints(constraints) {
1952
+ const read = {};
1953
+ for (const { key, value } of constraints) {
1954
+ if (key === "max_extracted_bytes" && /^\d+$/.test(value)) {
1955
+ read.maxExtractedBytes = Number(value);
1956
+ } else if (key === "max_files" && /^\d+$/.test(value)) {
1957
+ read.maxFiles = Number(value);
1958
+ } else if (key === "required_files") {
1959
+ read.requiredFiles = parseInlineList(value) ?? [];
1960
+ } else if (key === "allowed_extensions") {
1961
+ read.allowedExtensions = parseInlineList(value) ?? [];
1962
+ }
1963
+ }
1964
+ return read;
1965
+ }
1833
1966
  function parseInlineList(value) {
1834
1967
  if (!value.startsWith("[") || !value.endsWith("]")) return null;
1835
1968
  return value.slice(1, -1).split(",").map((member) => member.trim().replace(/^["']|["']$/g, "")).filter((member) => member.length > 0);
@@ -1845,6 +1978,7 @@ var DEPLOYMENTS = {
1845
1978
  ELGORA_SUBGRAPH_ENDPOINT: "https://api.goldsky.com/api/public/project_cmtu7wslg04s501ovc2je3beh/subgraphs/elgora-elgorahub-base/production/gn",
1846
1979
  ELGORA_API_BASE_URL: "https://elgora.ai",
1847
1980
  agentIdentitySubgraphEndpoint: "https://gateway.thegraph.com/api/subgraphs/id/43s9hQRurMGjuYnC1r2ZwS6xSQktbFyXMPMqGKUFJojb",
1981
+ agentIdentityRegistryAddress: "0x8004a169fb4a3325136eb29fa0ceb6d2e539a432",
1848
1982
  agentExplorerBaseUrl: "https://8004scan.io/agents/base",
1849
1983
  deployBlock: 51752389,
1850
1984
  deployTransaction: "0xd62f407b770c8fca82a9704b30ed0605d9e3ed2b56278dcca152958063f7f469",
@@ -1857,6 +1991,7 @@ var DEPLOYMENTS = {
1857
1991
  ELGORA_SUBGRAPH_ENDPOINT: "https://api.goldsky.com/api/public/project_cmtu7wslg04s501ovc2je3beh/subgraphs/elgora-elgorahub/testnet-production/gn",
1858
1992
  ELGORA_API_BASE_URL: "https://testnet.elgora.ai",
1859
1993
  agentIdentitySubgraphEndpoint: "https://gateway.thegraph.com/api/subgraphs/id/4yYAvQLFjBhBtdRCY7eUWo181VNoTSLLFd5M7FXQAi6u",
1994
+ agentIdentityRegistryAddress: "0x8004a818bfb912233c491871b3d84c89a494bd9e",
1860
1995
  agentExplorerBaseUrl: "https://testnet.8004scan.io/agents/base-sepolia",
1861
1996
  deployBlock: 46353511,
1862
1997
  deployTransaction: "0xed3d3c8af3c69f9f8d4ec47da7179fe4b89bd4a4cd8d8232dbbd3d3f3d38e74b",
@@ -1894,9 +2029,9 @@ async function main(_env = process.env, argv = process.argv.slice(2)) {
1894
2029
  }
1895
2030
  }
1896
2031
  function readBountyChallengeMarkdown(argv) {
1897
- const path6 = argv[0];
1898
- if (!path6 || argv.length !== 1) throw usageError(USAGE);
1899
- return readInput(path6);
2032
+ const path7 = argv[0];
2033
+ if (!path7 || argv.length !== 1) throw usageError(USAGE);
2034
+ return readInput(path7);
1900
2035
  }
1901
2036
  var command = { main, OPTIONS, USAGE };
1902
2037
 
@@ -4168,7 +4303,7 @@ function settlementAmounts(escrowAmount, treasuryFeeBps, guardianFeeBps) {
4168
4303
  }
4169
4304
 
4170
4305
  // ../../packages/core/src/api/bounty-preparation.ts
4171
- import { ZodError, z as z4 } from "zod";
4306
+ import { ZodError, z as z5 } from "zod";
4172
4307
 
4173
4308
  // ../../packages/core/src/api/elgorahub.ts
4174
4309
  import {
@@ -4401,14 +4536,14 @@ var BoundaryValidationError = class extends Error {
4401
4536
  this.issues = issues;
4402
4537
  }
4403
4538
  };
4404
- var artifactUploadUrlRequestSchema = z4.object({
4405
- ciphertext_sha256: z4.string().refine(isSha256Hex),
4406
- ciphertext_byte_length: z4.number().int().positive().max(MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES)
4539
+ var artifactUploadUrlRequestSchema = z5.object({
4540
+ ciphertext_sha256: z5.string().refine(isSha256Hex),
4541
+ ciphertext_byte_length: z5.number().int().positive().max(MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES)
4407
4542
  }).strict();
4408
4543
 
4409
4544
  // ../../packages/core/src/api/config.ts
4410
4545
  import { createHash } from "crypto";
4411
- import { z as z5 } from "zod";
4546
+ import { z as z6 } from "zod";
4412
4547
  function withDeploymentEnvDefaults(env) {
4413
4548
  const merged = { ...env };
4414
4549
  const setIfBlank = (key, value) => {
@@ -4429,11 +4564,11 @@ function withDeploymentEnvDefaults(env) {
4429
4564
  setIfBlank("ELGORA_SUBGRAPH_ENDPOINT", deployment?.ELGORA_SUBGRAPH_ENDPOINT);
4430
4565
  return merged;
4431
4566
  }
4432
- var chainIdField = z5.coerce.number().int().positive().refine((chainId) => SUPPORTED_CHAINS[chainId] !== void 0, {
4567
+ var chainIdField = z6.coerce.number().int().positive().refine((chainId) => SUPPORTED_CHAINS[chainId] !== void 0, {
4433
4568
  message: `ELGORA_CHAIN_ID must be a supported chain: ${Object.keys(SUPPORTED_CHAINS).join(", ")}`
4434
4569
  });
4435
- var hubAddressField = z5.string().refine(isHexAddress, "ELGORA_HUB_ADDRESS must be a 20-byte EVM address").transform((value) => value.toLowerCase());
4436
- var publicDeploymentEnvSchema = z5.object({
4570
+ var hubAddressField = z6.string().refine(isHexAddress, "ELGORA_HUB_ADDRESS must be a 20-byte EVM address").transform((value) => value.toLowerCase());
4571
+ var publicDeploymentEnvSchema = z6.object({
4437
4572
  ELGORA_CHAIN_ID: chainIdField,
4438
4573
  ELGORA_HUB_ADDRESS: hubAddressField
4439
4574
  });
@@ -4449,30 +4584,30 @@ function loadPublicDeploymentConfig(env = process.env) {
4449
4584
  var envShape = {
4450
4585
  ELGORA_CHAIN_ID: chainIdField,
4451
4586
  ELGORA_HUB_ADDRESS: hubAddressField,
4452
- ELGORA_ESCROW_TOKEN_ADDRESS: z5.string().refine(
4587
+ ELGORA_ESCROW_TOKEN_ADDRESS: z6.string().refine(
4453
4588
  isHexAddress,
4454
4589
  "ELGORA_ESCROW_TOKEN_ADDRESS must be a 20-byte EVM address"
4455
4590
  ).transform((value) => value.toLowerCase()),
4456
- ELGORA_TIMELOCK_REVEAL_DELAY_SECONDS: z5.coerce.number().int().nonnegative().default(DEFAULT_TIMELOCK_REVEAL_DELAY_SECONDS),
4457
- ELGORA_RPC_URL: z5.string().min(1, "ELGORA_RPC_URL is required"),
4591
+ ELGORA_TIMELOCK_REVEAL_DELAY_SECONDS: z6.coerce.number().int().nonnegative().default(DEFAULT_TIMELOCK_REVEAL_DELAY_SECONDS),
4592
+ ELGORA_RPC_URL: z6.string().min(1, "ELGORA_RPC_URL is required"),
4458
4593
  /**
4459
4594
  * Only consulted for a chain with no recorded deployment — a local anvil
4460
4595
  * stack, whose addresses change on every deploy. A recorded deployment's
4461
4596
  * scheme always wins, because settlement validation compares against it.
4462
4597
  */
4463
- ELGORA_PAYOUT_SCHEME_ADDRESS: z5.string().refine(
4598
+ ELGORA_PAYOUT_SCHEME_ADDRESS: z6.string().refine(
4464
4599
  isHexAddress,
4465
4600
  "ELGORA_PAYOUT_SCHEME_ADDRESS must be a 20-byte EVM address"
4466
4601
  ).transform((value) => value.toLowerCase()).optional(),
4467
- ELGORA_DATABASE_URL: z5.string().min(1, "ELGORA_DATABASE_URL is required"),
4468
- ELGORA_SUBGRAPH_ENDPOINT: z5.string().min(1, "ELGORA_SUBGRAPH_ENDPOINT is required"),
4469
- ELGORA_ARTIFACT_STORAGE_ENDPOINT: z5.string().min(1, "ELGORA_ARTIFACT_STORAGE_ENDPOINT is required"),
4470
- ELGORA_ARTIFACT_STORAGE_REGION: z5.string().min(1, "ELGORA_ARTIFACT_STORAGE_REGION is required"),
4471
- ELGORA_ARTIFACT_STORAGE_BUCKET: z5.string().min(1, "ELGORA_ARTIFACT_STORAGE_BUCKET is required"),
4472
- ELGORA_ARTIFACT_STORAGE_ACCESS_KEY_ID: z5.string().min(1, "ELGORA_ARTIFACT_STORAGE_ACCESS_KEY_ID is required"),
4473
- ELGORA_ARTIFACT_STORAGE_SECRET_ACCESS_KEY: z5.string().min(1, "ELGORA_ARTIFACT_STORAGE_SECRET_ACCESS_KEY is required"),
4474
- ELGORA_DELIVERY_PUBLIC_KEY: z5.string().min(1, "ELGORA_DELIVERY_PUBLIC_KEY is required"),
4475
- ELGORA_DELIVERY_PRIVATE_KEY: z5.string().min(1, "ELGORA_DELIVERY_PRIVATE_KEY is required")
4602
+ ELGORA_DATABASE_URL: z6.string().min(1, "ELGORA_DATABASE_URL is required"),
4603
+ ELGORA_SUBGRAPH_ENDPOINT: z6.string().min(1, "ELGORA_SUBGRAPH_ENDPOINT is required"),
4604
+ ELGORA_ARTIFACT_STORAGE_ENDPOINT: z6.string().min(1, "ELGORA_ARTIFACT_STORAGE_ENDPOINT is required"),
4605
+ ELGORA_ARTIFACT_STORAGE_REGION: z6.string().min(1, "ELGORA_ARTIFACT_STORAGE_REGION is required"),
4606
+ ELGORA_ARTIFACT_STORAGE_BUCKET: z6.string().min(1, "ELGORA_ARTIFACT_STORAGE_BUCKET is required"),
4607
+ ELGORA_ARTIFACT_STORAGE_ACCESS_KEY_ID: z6.string().min(1, "ELGORA_ARTIFACT_STORAGE_ACCESS_KEY_ID is required"),
4608
+ ELGORA_ARTIFACT_STORAGE_SECRET_ACCESS_KEY: z6.string().min(1, "ELGORA_ARTIFACT_STORAGE_SECRET_ACCESS_KEY is required"),
4609
+ ELGORA_DELIVERY_PUBLIC_KEY: z6.string().min(1, "ELGORA_DELIVERY_PUBLIC_KEY is required"),
4610
+ ELGORA_DELIVERY_PRIVATE_KEY: z6.string().min(1, "ELGORA_DELIVERY_PRIVATE_KEY is required")
4476
4611
  };
4477
4612
  var publicationEnvShape = {
4478
4613
  ELGORA_CHAIN_ID: envShape.ELGORA_CHAIN_ID,
@@ -4480,88 +4615,89 @@ var publicationEnvShape = {
4480
4615
  ELGORA_ESCROW_TOKEN_ADDRESS: envShape.ELGORA_ESCROW_TOKEN_ADDRESS,
4481
4616
  ELGORA_DATABASE_URL: envShape.ELGORA_DATABASE_URL,
4482
4617
  ELGORA_SUBGRAPH_ENDPOINT: envShape.ELGORA_SUBGRAPH_ENDPOINT,
4483
- ELGORA_PUBLICATION_REVIEW_ENABLED: z5.string().trim().toLowerCase().pipe(z5.enum(["true", "false"])).default("true").transform((value) => value === "true"),
4484
- ELGORA_READINESS_TIMEOUT_MS: z5.coerce.number().int().min(1).max(READINESS_TIMEOUT_MS).default(READINESS_TIMEOUT_MS)
4618
+ ELGORA_PUBLICATION_REVIEW_ENABLED: z6.string().trim().toLowerCase().pipe(z6.enum(["true", "false"])).default("true").transform((value) => value === "true"),
4619
+ ELGORA_READINESS_TIMEOUT_MS: z6.coerce.number().int().min(1).max(READINESS_TIMEOUT_MS).default(READINESS_TIMEOUT_MS)
4485
4620
  };
4486
- var envSchema = z5.object(envShape);
4487
- var bountyPublicationEnvSchema = z5.object(publicationEnvShape);
4621
+ var envSchema = z6.object(envShape);
4622
+ var bountyPublicationEnvSchema = z6.object(publicationEnvShape);
4488
4623
 
4489
4624
  // ../../packages/core/src/api/solver-submission-responses.ts
4490
4625
  import { isHex as isHex3 } from "viem";
4491
- import { z as z6 } from "zod";
4492
- var hexAddressSchema = z6.string().refine(isHexAddress, "Must be a hex-encoded address");
4493
- var hexBytes32Schema = z6.string().refine(isHexBytes32, "Must be 32-byte hex");
4494
- var hexDataSchema = z6.string().refine((value) => isHex3(value), "Must be hex-encoded calldata");
4495
- var artifactUploadUrlResponseSchema = z6.object({
4496
- locator: z6.string().min(1),
4497
- upload_url: z6.string().min(1),
4498
- method: z6.literal("PUT"),
4499
- expires_at: z6.string().min(1)
4626
+ import { z as z7 } from "zod";
4627
+ var hexAddressSchema = z7.string().refine(isHexAddress, "Must be a hex-encoded address");
4628
+ var hexBytes32Schema = z7.string().refine(isHexBytes32, "Must be 32-byte hex");
4629
+ var hexDataSchema = z7.string().refine((value) => isHex3(value), "Must be hex-encoded calldata");
4630
+ var artifactUploadUrlResponseSchema = z7.object({
4631
+ locator: z7.string().min(1),
4632
+ upload_url: z7.string().min(1),
4633
+ method: z7.literal("PUT"),
4634
+ expires_at: z7.string().min(1)
4500
4635
  });
4501
- var solverSubmissionPrepareResponseSchema = z6.object({
4502
- content_record: z6.object({
4503
- content_kind: z6.literal(SOLVER_SUBMISSION_CONTENT_KIND),
4636
+ var solverSubmissionPrepareResponseSchema = z7.object({
4637
+ content_record: z7.object({
4638
+ content_kind: z7.literal(SOLVER_SUBMISSION_CONTENT_KIND),
4504
4639
  submission_commitment: hexBytes32Schema,
4505
- byte_length: z6.number().int().nonnegative(),
4506
- created_at: z6.string().min(1)
4640
+ byte_length: z7.number().int().nonnegative(),
4641
+ created_at: z7.string().min(1)
4507
4642
  }),
4508
- submit_transaction: z6.object({
4509
- chain_id: z6.number().int().positive(),
4643
+ submit_transaction: z7.object({
4644
+ chain_id: z7.number().int().positive(),
4510
4645
  to: hexAddressSchema,
4511
- value: z6.literal("0"),
4646
+ value: z7.literal("0"),
4512
4647
  data: hexDataSchema,
4513
- function_name: z6.literal("submit"),
4514
- args: z6.object({
4515
- bounty_id: z6.string().min(1),
4648
+ function_name: z7.literal("submit"),
4649
+ args: z7.object({
4650
+ bounty_id: z7.string().min(1),
4516
4651
  submission_commitment: hexBytes32Schema,
4517
4652
  guardian_roster_hash: hexBytes32Schema,
4518
- extra_data: z6.literal("0x")
4653
+ extra_data: z7.literal("0x")
4519
4654
  })
4520
4655
  })
4521
4656
  });
4522
- var solverSubmissionContentResponseSchema = z6.object({
4523
- content_record: z6.object({
4524
- bounty_id: z6.string().min(1),
4525
- canonical_json: z6.string().min(1)
4657
+ var solverSubmissionContentResponseSchema = z7.object({
4658
+ content_record: z7.object({
4659
+ bounty_id: z7.string().min(1),
4660
+ canonical_json: z7.string().min(1)
4526
4661
  }),
4527
- ciphertext_download_url: z6.string().min(1),
4528
- expires_at: z6.string().min(1)
4662
+ ciphertext_download_url: z7.string().min(1),
4663
+ expires_at: z7.string().min(1)
4529
4664
  });
4530
- var bountyGuardianRosterResponseSchema = z6.object({
4531
- bounty: z6.object({
4665
+ var bountySubmissionDetailResponseSchema = z7.object({
4666
+ bounty: z7.object({
4532
4667
  guardian_roster_hash: hexBytes32Schema,
4533
- guardian_roster: z6.array(
4534
- z6.object({
4535
- name: z6.string(),
4668
+ guardian_roster: z7.array(
4669
+ z7.object({
4670
+ name: z7.string(),
4536
4671
  account: hexAddressSchema,
4537
- encryption_public_key: z6.string().min(1)
4672
+ encryption_public_key: z7.string().min(1)
4538
4673
  })
4539
4674
  ).min(1)
4540
- })
4675
+ }),
4676
+ challenge: z7.string().nullable()
4541
4677
  });
4542
4678
 
4543
4679
  // ../../packages/core/src/api/verification-records.ts
4544
- import { z as z7 } from "zod";
4545
- var hexAddressSchema2 = z7.string().refine(isHexAddress, "Must be a hex-encoded address").transform((value) => value.toLowerCase());
4546
- var hexBytes32Schema2 = z7.string().refine(isHexBytes32, "Must be 32-byte hex").transform((value) => value.toLowerCase());
4547
- var integerStringSchema = z7.string().regex(/^(0|[1-9][0-9]*)$/);
4548
- var verdictEntrySchema = z7.object({
4549
- name: z7.string().min(1),
4550
- outcome: z7.enum(["awarded", "no_valid_submission"]).nullable(),
4680
+ import { z as z8 } from "zod";
4681
+ var hexAddressSchema2 = z8.string().refine(isHexAddress, "Must be a hex-encoded address").transform((value) => value.toLowerCase());
4682
+ var hexBytes32Schema2 = z8.string().refine(isHexBytes32, "Must be 32-byte hex").transform((value) => value.toLowerCase());
4683
+ var integerStringSchema = z8.string().regex(/^(0|[1-9][0-9]*)$/);
4684
+ var verdictEntrySchema = z8.object({
4685
+ name: z8.string().min(1),
4686
+ outcome: z8.enum(["awarded", "no_valid_submission"]).nullable(),
4551
4687
  winner: hexAddressSchema2.nullable(),
4552
4688
  report_commitment: hexBytes32Schema2.nullable(),
4553
- committed_at: z7.string().datetime({ offset: true }).nullable(),
4554
- supporting: z7.boolean()
4689
+ committed_at: z8.string().datetime({ offset: true }).nullable(),
4690
+ supporting: z8.boolean()
4555
4691
  }).strict();
4556
- var formattedVerificationRecordSchema = z7.object({
4557
- chain_id: z7.number().int().positive(),
4692
+ var formattedVerificationRecordSchema = z8.object({
4693
+ chain_id: z8.number().int().positive(),
4558
4694
  hub_address: hexAddressSchema2,
4559
4695
  bounty_id: integerStringSchema,
4560
- status: z7.enum(["awarded", "no_valid_submission", "timed_out"]),
4696
+ status: z8.enum(["awarded", "no_valid_submission", "timed_out"]),
4561
4697
  spec_commitment: hexBytes32Schema2,
4562
4698
  winner: hexAddressSchema2.nullable(),
4563
- verdicts: z7.record(z7.string(), verdictEntrySchema),
4564
- settlement: z7.object({
4699
+ verdicts: z8.record(z8.string(), verdictEntrySchema),
4700
+ settlement: z8.object({
4565
4701
  escrow_amount: integerStringSchema,
4566
4702
  settlement_recipient: hexAddressSchema2,
4567
4703
  settlement_amount: integerStringSchema,
@@ -4572,10 +4708,10 @@ var formattedVerificationRecordSchema = z7.object({
4572
4708
  settled_tx_hash: hexBytes32Schema2,
4573
4709
  settled_block_number: integerStringSchema
4574
4710
  }).strict(),
4575
- content_record: z7.object({
4576
- spec_content_byte_length: z7.number().int().positive()
4711
+ content_record: z8.object({
4712
+ spec_content_byte_length: z8.number().int().positive()
4577
4713
  }).strict(),
4578
- verified_at: z7.string().datetime({ offset: true })
4714
+ verified_at: z8.string().datetime({ offset: true })
4579
4715
  }).strict();
4580
4716
  function parseBountyVerificationRecord(input, expected) {
4581
4717
  const slice = input;
@@ -4597,66 +4733,66 @@ function parseBountyVerificationRecord(input, expected) {
4597
4733
  }
4598
4734
 
4599
4735
  // ../../packages/core/src/api/winning-solution-delivery.ts
4600
- import { z as z8 } from "zod";
4601
- var hexBytes32Schema3 = z8.string().refine(isHexBytes32, "Must be 32-byte hex").refine((value) => value !== ZERO_BYTES32, "Must not be zero").transform((value) => value.toLowerCase());
4602
- var hexAddressSchema3 = z8.string().refine((value) => /^0x[0-9a-fA-F]{40}$/.test(value), "Must be an address").transform((value) => value.toLowerCase());
4603
- var deliveryKeyIdSchema = z8.string().min(1).max(128);
4604
- var winningSolutionWrappedKeySchema = z8.strictObject({
4605
- enc: z8.string().min(1),
4606
- ciphertext: z8.string().min(1)
4736
+ import { z as z9 } from "zod";
4737
+ var hexBytes32Schema3 = z9.string().refine(isHexBytes32, "Must be 32-byte hex").refine((value) => value !== ZERO_BYTES32, "Must not be zero").transform((value) => value.toLowerCase());
4738
+ var hexAddressSchema3 = z9.string().refine((value) => /^0x[0-9a-fA-F]{40}$/.test(value), "Must be an address").transform((value) => value.toLowerCase());
4739
+ var deliveryKeyIdSchema = z9.string().min(1).max(128);
4740
+ var winningSolutionWrappedKeySchema = z9.strictObject({
4741
+ enc: z9.string().min(1),
4742
+ ciphertext: z9.string().min(1)
4607
4743
  });
4608
- var deliveryWrapRequestSchema = z8.strictObject({
4744
+ var deliveryWrapRequestSchema = z9.strictObject({
4609
4745
  solver_address: hexAddressSchema3,
4610
4746
  submission_commitment: hexBytes32Schema3,
4611
4747
  wrapped_key: winningSolutionWrappedKeySchema
4612
4748
  });
4613
- var deliveryWrapRecordSchema = z8.strictObject({
4614
- bounty_id: z8.string().min(1),
4749
+ var deliveryWrapRecordSchema = z9.strictObject({
4750
+ bounty_id: z9.string().min(1),
4615
4751
  guardian_address: hexAddressSchema3,
4616
4752
  solver_address: hexAddressSchema3,
4617
4753
  submission_commitment: hexBytes32Schema3,
4618
4754
  delivery_key_id: deliveryKeyIdSchema,
4619
- created_at: z8.string().min(1)
4755
+ created_at: z9.string().min(1)
4620
4756
  });
4621
- var deliveryWrapResponseSchema = z8.strictObject({
4622
- status: z8.literal("stored"),
4757
+ var deliveryWrapResponseSchema = z9.strictObject({
4758
+ status: z9.literal("stored"),
4623
4759
  wrap: deliveryWrapRecordSchema
4624
4760
  });
4625
- var deliveryRetrieveRequestSchema = z8.strictObject({
4761
+ var deliveryRetrieveRequestSchema = z9.strictObject({
4626
4762
  poster_ephemeral_public_key: x25519PublicKeySchema
4627
4763
  });
4628
- var deliveryRetrieveResponseSchema = z8.strictObject({
4764
+ var deliveryRetrieveResponseSchema = z9.strictObject({
4629
4765
  submission_commitment: hexBytes32Schema3,
4630
4766
  wrapped_key: winningSolutionWrappedKeySchema
4631
4767
  });
4632
- var deliveryStatusResponseSchema = z8.strictObject({
4633
- exists: z8.boolean()
4768
+ var deliveryStatusResponseSchema = z9.strictObject({
4769
+ exists: z9.boolean()
4634
4770
  });
4635
- var deliveryKeyConfigResponseSchema = z8.strictObject({
4771
+ var deliveryKeyConfigResponseSchema = z9.strictObject({
4636
4772
  delivery_key_id: deliveryKeyIdSchema,
4637
- public_key: z8.string().min(1)
4773
+ public_key: z9.string().min(1)
4638
4774
  });
4639
4775
 
4640
4776
  // ../../packages/core/src/api/written-verdicts.ts
4641
4777
  import { keccak256 as keccak2564, toBytes as toBytes3, zeroAddress } from "viem";
4642
- import { z as z9 } from "zod";
4778
+ import { z as z10 } from "zod";
4643
4779
  var WRITTEN_VERDICT_FORMAT_VERSION = 0;
4644
4780
  var FRONTMATTER_DELIMITER2 = "---";
4645
- var decimalSchema = z9.string().regex(/^[1-9][0-9]*$/, "Must be a positive integer").transform(Number).refine(Number.isSafeInteger, "Must be a safe integer");
4646
- var bountyIdSchema2 = z9.string().regex(/^(0|[1-9][0-9]*)$/, "Must be a non-negative integer");
4647
- var addressSchema2 = z9.string().refine(isHexAddress, "Must be a hex-encoded address").refine((value) => value === value.toLowerCase(), "Must use lowercase hex").transform((value) => value);
4648
- var bytes32Schema3 = z9.string().refine(isHexBytes32, "Must be 32-byte hex").refine((value) => value === value.toLowerCase(), "Must use lowercase hex").transform((value) => value);
4781
+ var decimalSchema = z10.string().regex(/^[1-9][0-9]*$/, "Must be a positive integer").transform(Number).refine(Number.isSafeInteger, "Must be a safe integer");
4782
+ var bountyIdSchema2 = z10.string().regex(/^(0|[1-9][0-9]*)$/, "Must be a non-negative integer");
4783
+ var addressSchema2 = z10.string().refine(isHexAddress, "Must be a hex-encoded address").refine((value) => value === value.toLowerCase(), "Must use lowercase hex").transform((value) => value);
4784
+ var bytes32Schema3 = z10.string().refine(isHexBytes32, "Must be 32-byte hex").refine((value) => value === value.toLowerCase(), "Must use lowercase hex").transform((value) => value);
4649
4785
  var commonFields = {
4650
- format_version: z9.literal(WRITTEN_VERDICT_FORMAT_VERSION.toString()).transform(() => WRITTEN_VERDICT_FORMAT_VERSION),
4786
+ format_version: z10.literal(WRITTEN_VERDICT_FORMAT_VERSION.toString()).transform(() => WRITTEN_VERDICT_FORMAT_VERSION),
4651
4787
  chain_id: decimalSchema,
4652
4788
  hub_address: addressSchema2,
4653
4789
  bounty_id: bountyIdSchema2,
4654
- verdict_body: z9.string().refine((value) => value.trim().length > 0, "Must not be empty")
4790
+ verdict_body: z10.string().refine((value) => value.trim().length > 0, "Must not be empty")
4655
4791
  };
4656
- var writtenVerdictSchema = z9.discriminatedUnion("outcome", [
4657
- z9.object({
4792
+ var writtenVerdictSchema = z10.discriminatedUnion("outcome", [
4793
+ z10.object({
4658
4794
  ...commonFields,
4659
- outcome: z9.literal("awarded"),
4795
+ outcome: z10.literal("awarded"),
4660
4796
  winner: addressSchema2.refine(
4661
4797
  (value) => value !== zeroAddress,
4662
4798
  "Must not be the zero address"
@@ -4666,10 +4802,10 @@ var writtenVerdictSchema = z9.discriminatedUnion("outcome", [
4666
4802
  "Must not be zero bytes"
4667
4803
  )
4668
4804
  }).strict(),
4669
- z9.object({
4805
+ z10.object({
4670
4806
  ...commonFields,
4671
- outcome: z9.literal("no_valid_submission"),
4672
- winner: z9.literal(zeroAddress)
4807
+ outcome: z10.literal("no_valid_submission"),
4808
+ winner: z10.literal(zeroAddress)
4673
4809
  }).strict()
4674
4810
  ]);
4675
4811
  function buildWrittenVerdictMarkdown(input) {
@@ -4751,10 +4887,10 @@ ${FRONTMATTER_DELIMITER2}
4751
4887
  }
4752
4888
  return result.data;
4753
4889
  }
4754
- function invalidWrittenVerdict(path6, message) {
4890
+ function invalidWrittenVerdict(path7, message) {
4755
4891
  return new BoundaryValidationError(
4756
4892
  "Invalid written Verdict",
4757
- [{ path: path6, message }],
4893
+ [{ path: path7, message }],
4758
4894
  "invalid_written_verdict"
4759
4895
  );
4760
4896
  }
@@ -4839,12 +4975,12 @@ async function getAuthenticatedJson(apiBaseUrl, pathname, authorization) {
4839
4975
  });
4840
4976
  return parseJsonResponse(url, response);
4841
4977
  }
4842
- async function requestBountyGuardianRoster(input) {
4978
+ async function requestBountySubmissionDetail(input) {
4843
4979
  const body = await getJson(
4844
4980
  input.apiBaseUrl,
4845
4981
  `/api/bounties/${input.bountyId}`
4846
4982
  );
4847
- return bountyGuardianRosterResponseSchema.parse(body).bounty;
4983
+ return bountySubmissionDetailResponseSchema.parse(body);
4848
4984
  }
4849
4985
  async function requestDeploymentDeliveryPublicKey(input) {
4850
4986
  const body = await getJson(input.apiBaseUrl, "/api/deployment/delivery-key");
@@ -5351,8 +5487,8 @@ async function waitForSponsoredHash(config, transactionId, call) {
5351
5487
  `Privy ${call.function_name} transaction ${transactionId} had no transaction hash after ${SPONSORED_TIMEOUT_MS / 1e3} s`
5352
5488
  );
5353
5489
  }
5354
- async function privyRequest(config, method, path6, body) {
5355
- const url = `${PRIVY_API_URL}${path6}`;
5490
+ async function privyRequest(config, method, path7, body) {
5491
+ const url = `${PRIVY_API_URL}${path7}`;
5356
5492
  const headers = {
5357
5493
  authorization: `Basic ${Buffer.from(`${config.appId}:${config.appSecret}`).toString("base64")}`,
5358
5494
  "privy-app-id": config.appId
@@ -5384,12 +5520,12 @@ async function privyRequest(config, method, path6, body) {
5384
5520
  if (!response.ok) {
5385
5521
  const message = parsed && typeof parsed === "object" && typeof parsed.error === "string" ? parsed.error : `HTTP ${response.status}`;
5386
5522
  throw new PrivyRequestError(
5387
- `Privy ${method} ${path6} failed (${response.status}): ${message}`,
5523
+ `Privy ${method} ${path7} failed (${response.status}): ${message}`,
5388
5524
  response.status
5389
5525
  );
5390
5526
  }
5391
5527
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
5392
- throw new Error(`Privy ${method} ${path6} returned a non-object body`);
5528
+ throw new Error(`Privy ${method} ${path7} returned a non-object body`);
5393
5529
  }
5394
5530
  return parsed;
5395
5531
  }
@@ -6085,7 +6221,7 @@ function typedDataForRequest(request, block) {
6085
6221
  }
6086
6222
 
6087
6223
  // ../../packages/core/src/auth/request-approval-header.ts
6088
- import { z as z10 } from "zod";
6224
+ import { z as z11 } from "zod";
6089
6225
  async function buildAuthorizationHeader(params) {
6090
6226
  const url = new URL(params.requestUrl);
6091
6227
  const typedData = typedDataForRequest(
@@ -6112,8 +6248,8 @@ async function authorizeRequest(params) {
6112
6248
  block: await readChallengeBlock(params)
6113
6249
  });
6114
6250
  }
6115
- var challengeBlockSchema = z10.object({
6116
- error: z10.object({ block_number: z10.string(), block_hash: z10.string() })
6251
+ var challengeBlockSchema = z11.object({
6252
+ error: z11.object({ block_number: z11.string(), block_hash: z11.string() })
6117
6253
  });
6118
6254
  async function readChallengeBlock(params) {
6119
6255
  const response = await fetch(params.requestUrl, {
@@ -6126,98 +6262,553 @@ async function readChallengeBlock(params) {
6126
6262
  `Expected an approval challenge (401) from ${params.requestUrl}, got ${response.status}.`
6127
6263
  );
6128
6264
  }
6129
- const { block_number, block_hash } = challengeBlockSchema.parse(
6130
- await response.json()
6131
- ).error;
6132
- return { number: BigInt(block_number), hash: block_hash };
6265
+ const { block_number, block_hash } = challengeBlockSchema.parse(
6266
+ await response.json()
6267
+ ).error;
6268
+ return { number: BigInt(block_number), hash: block_hash };
6269
+ }
6270
+
6271
+ // ../../packages/core/src/winning-solution-delivery.ts
6272
+ async function wrapWinningSolutionKey(input) {
6273
+ const deploymentDeliveryPublicKey = await requestDeploymentDeliveryPublicKey({
6274
+ apiBaseUrl: input.config.apiBaseUrl
6275
+ });
6276
+ const contentPathname = `/api/bounties/${input.bountyId}/submissions/${input.submissionCommitment}/content`;
6277
+ const contentAuthorization = await authorizeRequest({
6278
+ requestUrl: new URL(contentPathname, input.config.apiBaseUrl).toString(),
6279
+ method: "GET",
6280
+ body: "",
6281
+ chainId: input.config.chainId,
6282
+ hubAddress: input.config.hubAddress,
6283
+ address: input.owner.address,
6284
+ sign: input.owner.signTypedData
6285
+ });
6286
+ const { content_record } = solverSubmissionContentResponseSchema.parse(
6287
+ await getAuthenticatedJson(
6288
+ input.config.apiBaseUrl,
6289
+ contentPathname,
6290
+ contentAuthorization
6291
+ )
6292
+ );
6293
+ const envelope = parseSubmissionEnvelope(
6294
+ JSON.parse(content_record.canonical_json)
6295
+ );
6296
+ const opened = await openSubmissionEnvelopeForDelivery({
6297
+ envelope,
6298
+ guardian_private_keys: input.guardianPrivateKeys,
6299
+ timelock_decrypt: createSubmissionTimelockDecrypt(),
6300
+ delivery_recipient_public_key: deploymentDeliveryPublicKey,
6301
+ context: {
6302
+ chain_id: input.config.chainId,
6303
+ hub_address: input.config.hubAddress,
6304
+ bounty_id: input.bountyId.toString(),
6305
+ submission_commitment: input.submissionCommitment
6306
+ }
6307
+ });
6308
+ const requestUrl = new URL(
6309
+ `/api/bounties/${input.bountyId}/delivery/wrap`,
6310
+ input.config.apiBaseUrl
6311
+ );
6312
+ const body = JSON.stringify({
6313
+ solver_address: input.solver,
6314
+ submission_commitment: input.submissionCommitment,
6315
+ wrapped_key: opened.wrapped_key
6316
+ });
6317
+ const authorization = await authorizeRequest({
6318
+ requestUrl: requestUrl.toString(),
6319
+ method: "POST",
6320
+ contentType: "application/json",
6321
+ body,
6322
+ chainId: input.config.chainId,
6323
+ hubAddress: input.config.hubAddress,
6324
+ address: input.owner.address,
6325
+ sign: input.owner.signTypedData
6326
+ });
6327
+ const responseBody = await postText(
6328
+ requestUrl,
6329
+ body,
6330
+ "application/json",
6331
+ authorization
6332
+ );
6333
+ return deliveryWrapResponseSchema.parse(responseBody);
6334
+ }
6335
+
6336
+ // src/guardian-open.ts
6337
+ import path3 from "path";
6338
+
6339
+ // src/artifact-output.ts
6340
+ import { constants } from "fs";
6341
+ import { lstat, mkdir, open, readdir } from "fs/promises";
6342
+ import path2 from "path";
6343
+ var CREATE_FILE_FLAGS = constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW;
6344
+ var MAX_HOST_PATH_BYTES = process.platform === "darwin" ? 1023 : 4095;
6345
+ async function writeOpenedArtifacts(outputDir, files) {
6346
+ for (const file of files) {
6347
+ const issue = describeSolverSubmissionPathIssue(file.path);
6348
+ if (issue !== null) {
6349
+ throw new Error(`Refusing to write the Submission: ${issue}`);
6350
+ }
6351
+ }
6352
+ const conflict = findSolverSubmissionPathConflict(
6353
+ files.map((file) => file.path)
6354
+ );
6355
+ if (conflict !== null) {
6356
+ throw new Error(
6357
+ `Refusing to write the Submission: ${describeSolverSubmissionPathConflict(conflict)}.`
6358
+ );
6359
+ }
6360
+ const root = path2.resolve(outputDir);
6361
+ const targets = files.map((file) => {
6362
+ const target = path2.resolve(root, ...file.path.split("/"));
6363
+ const relative = path2.relative(root, target);
6364
+ if (relative === "" || relative.startsWith("..") || path2.isAbsolute(relative)) {
6365
+ throw new Error(
6366
+ `Refusing to write the Submission: "${file.path}" resolves outside ${outputDir}.`
6367
+ );
6368
+ }
6369
+ return { target, bytes: file.bytes };
6370
+ });
6371
+ const longest = targets.reduce(
6372
+ (max, { target }) => Math.max(max, Buffer.byteLength(target)),
6373
+ 0
6374
+ );
6375
+ if (longest > MAX_HOST_PATH_BYTES) {
6376
+ throw new Error(
6377
+ `Refusing to write the Submission: its longest path under ${outputDir} is ${longest} bytes, and this host opens at most ${MAX_HOST_PATH_BYTES}. Choose a shorter output directory.`
6378
+ );
6379
+ }
6380
+ await prepareOutputDirectory(root, outputDir);
6381
+ for (const { target, bytes } of targets) {
6382
+ await mkdir(path2.dirname(target), { recursive: true });
6383
+ await assertRealDirectoriesBelow(root, path2.dirname(target));
6384
+ const handle = await open(target, CREATE_FILE_FLAGS, 420);
6385
+ try {
6386
+ await handle.writeFile(bytes);
6387
+ } finally {
6388
+ await handle.close();
6389
+ }
6390
+ }
6391
+ }
6392
+ async function prepareOutputDirectory(root, outputDir) {
6393
+ const existing = await lstat(root).catch((error) => {
6394
+ if (error.code === "ENOENT") return null;
6395
+ throw error;
6396
+ });
6397
+ if (existing === null) {
6398
+ await mkdir(root, { recursive: true });
6399
+ return;
6400
+ }
6401
+ if (!existing.isDirectory()) {
6402
+ throw new Error(
6403
+ `Output directory ${outputDir} exists and is not a directory. Choose a new output directory.`
6404
+ );
6405
+ }
6406
+ if ((await readdir(root)).length > 0) {
6407
+ throw new Error(
6408
+ `Output directory ${outputDir} is not empty. Choose a new output directory, or remove this one, before opening the Submission again.`
6409
+ );
6410
+ }
6411
+ }
6412
+ async function assertRealDirectoriesBelow(root, directory) {
6413
+ let current = directory;
6414
+ while (current !== root) {
6415
+ if (!(await lstat(current)).isDirectory()) {
6416
+ throw new Error(
6417
+ `Refusing to write the Submission: ${current} is not a real directory.`
6418
+ );
6419
+ }
6420
+ current = path2.dirname(current);
6421
+ }
6422
+ }
6423
+
6424
+ // src/submission-archive.ts
6425
+ import { createHash as createHash2 } from "crypto";
6426
+ import { gunzipSync, gzipSync } from "zlib";
6427
+
6428
+ // ../../packages/tarts/src/tar.ts
6429
+ var BLOCK = 512;
6430
+ var NAME_FIELD = 100;
6431
+ var encoder = new TextEncoder();
6432
+ var decoder = new TextDecoder("utf-8");
6433
+ var TarFormatError = class extends Error {
6434
+ };
6435
+ function writeTar(files) {
6436
+ const blocks = [];
6437
+ for (const file of files) {
6438
+ const name = encoder.encode(file.path);
6439
+ if (name.byteLength > NAME_FIELD) {
6440
+ const record = paxRecord("path", file.path);
6441
+ blocks.push(header("PaxHeader", "x", record.byteLength), padded(record));
6442
+ }
6443
+ blocks.push(
6444
+ header(file.path, "0", file.bytes.byteLength),
6445
+ padded(file.bytes)
6446
+ );
6447
+ }
6448
+ blocks.push(new Uint8Array(2 * BLOCK));
6449
+ return concat(blocks);
6450
+ }
6451
+ function readTar(tar) {
6452
+ const entries = [];
6453
+ let path7;
6454
+ for (let offset = 0; offset + BLOCK <= tar.byteLength; ) {
6455
+ const block = tar.subarray(offset, offset + BLOCK);
6456
+ if (block.every((byte) => byte === 0)) return entries;
6457
+ if (!checksumMatches(block)) {
6458
+ throw new TarFormatError(
6459
+ `the header at byte ${offset} fails its checksum`
6460
+ );
6461
+ }
6462
+ const size2 = readSize(block.subarray(124, 136));
6463
+ const start = offset + BLOCK;
6464
+ if (start + size2 > tar.byteLength) {
6465
+ throw new TarFormatError(`the entry at byte ${offset} is truncated`);
6466
+ }
6467
+ const data = tar.subarray(start, start + size2);
6468
+ offset = start + Math.ceil(size2 / BLOCK) * BLOCK;
6469
+ const type = String.fromCharCode(block[156] ?? 0);
6470
+ if (type === "x") {
6471
+ path7 = readPaxPath(data) ?? path7;
6472
+ continue;
6473
+ }
6474
+ if (type === "L") {
6475
+ path7 = decoder.decode(nulTerminated(data));
6476
+ continue;
6477
+ }
6478
+ if (type === "g") continue;
6479
+ const prefix = decoder.decode(nulTerminated(block.subarray(345, 500)));
6480
+ const headerName = decoder.decode(
6481
+ nulTerminated(block.subarray(0, NAME_FIELD))
6482
+ );
6483
+ entries.push({
6484
+ name: path7 ?? (prefix ? `${prefix}/${headerName}` : headerName),
6485
+ type: type === "\0" ? "0" : type,
6486
+ data
6487
+ });
6488
+ path7 = void 0;
6489
+ }
6490
+ throw new TarFormatError("it is truncated: no end-of-archive block");
6491
+ }
6492
+ function header(name, type, size2) {
6493
+ const block = new Uint8Array(BLOCK);
6494
+ const put = (offset, value) => block.set(encoder.encode(value), offset);
6495
+ block.set(encoder.encode(name).subarray(0, NAME_FIELD), 0);
6496
+ put(100, "0000644\0");
6497
+ put(108, "0000000\0");
6498
+ put(116, "0000000\0");
6499
+ put(124, `${size2.toString(8).padStart(11, "0")}\0`);
6500
+ put(136, "00000000000\0");
6501
+ put(156, type);
6502
+ put(257, "ustar\0");
6503
+ put(263, "00");
6504
+ block.fill(32, 148, 156);
6505
+ const sum = block.reduce((total, byte) => total + byte, 0);
6506
+ put(148, `${sum.toString(8).padStart(6, "0")}\0 `);
6507
+ return block;
6508
+ }
6509
+ function paxRecord(key, value) {
6510
+ const body = ` ${key}=${value}
6511
+ `;
6512
+ const bodyLength = encoder.encode(body).byteLength;
6513
+ let length = bodyLength + 1;
6514
+ while (String(length).length + bodyLength !== length) length += 1;
6515
+ return encoder.encode(`${length}${body}`);
6516
+ }
6517
+ function readPaxPath(data) {
6518
+ let path7;
6519
+ let offset = 0;
6520
+ while (offset < data.byteLength) {
6521
+ const space = data.indexOf(32, offset);
6522
+ const length = Number.parseInt(latin1(data.subarray(offset, space)), 10);
6523
+ if (space < 0 || !Number.isSafeInteger(length) || length <= 0) {
6524
+ throw new TarFormatError("a pax header does not parse");
6525
+ }
6526
+ const record = data.subarray(space + 1, offset + length - 1);
6527
+ const equals = record.indexOf(61);
6528
+ if (equals >= 0 && latin1(record.subarray(0, equals)) === "path") {
6529
+ path7 = decoder.decode(record.subarray(equals + 1));
6530
+ }
6531
+ offset += length;
6532
+ }
6533
+ return path7;
6534
+ }
6535
+ function checksumMatches(block) {
6536
+ let sum = 0;
6537
+ for (let index = 0; index < BLOCK; index += 1) {
6538
+ sum += index >= 148 && index < 156 ? 32 : block[index] ?? 0;
6539
+ }
6540
+ return readOctal(block.subarray(148, 156)) === sum;
6541
+ }
6542
+ function readSize(field) {
6543
+ let size2;
6544
+ if (((field[0] ?? 0) & 128) !== 0) {
6545
+ size2 = 0;
6546
+ for (const [index, byte] of field.entries()) {
6547
+ size2 = size2 * 256 + (index === 0 ? byte & 127 : byte);
6548
+ }
6549
+ } else {
6550
+ size2 = readOctal(field);
6551
+ }
6552
+ if (!Number.isSafeInteger(size2) || size2 < 0) {
6553
+ throw new TarFormatError("a header declares no readable size");
6554
+ }
6555
+ return size2;
6556
+ }
6557
+ function readOctal(field) {
6558
+ const digits = latin1(field).replace(/^[ \0]+|[ \0]+$/g, "");
6559
+ return /^[0-7]+$/.test(digits) ? Number.parseInt(digits, 8) : Number.NaN;
6560
+ }
6561
+ function nulTerminated(field) {
6562
+ const end = field.indexOf(0);
6563
+ return end < 0 ? field : field.subarray(0, end);
6564
+ }
6565
+ function latin1(bytes) {
6566
+ return String.fromCharCode(...bytes);
6567
+ }
6568
+ function padded(bytes) {
6569
+ const out = new Uint8Array(Math.ceil(bytes.byteLength / BLOCK) * BLOCK);
6570
+ out.set(bytes);
6571
+ return out;
6572
+ }
6573
+ function concat(chunks) {
6574
+ const out = new Uint8Array(
6575
+ chunks.reduce((total, chunk) => total + chunk.byteLength, 0)
6576
+ );
6577
+ let offset = 0;
6578
+ for (const chunk of chunks) {
6579
+ out.set(chunk, offset);
6580
+ offset += chunk.byteLength;
6581
+ }
6582
+ return out;
6583
+ }
6584
+
6585
+ // src/submission-archive.ts
6586
+ function buildSubmissionArchive(files) {
6587
+ const sorted = [...files].sort(
6588
+ (left, right) => compareBytewise(left.path, right.path)
6589
+ );
6590
+ assertSubmissionFiles(sorted);
6591
+ const archive = gzipSync(writeTar(sorted), { level: 9 });
6592
+ return {
6593
+ archive: new Uint8Array(archive.buffer, archive.byteOffset, archive.length),
6594
+ ...describeFiles(sorted)
6595
+ };
6596
+ }
6597
+ function readSubmissionArchive(archive) {
6598
+ let tar;
6599
+ try {
6600
+ const inflated = gunzipSync(archive);
6601
+ tar = new Uint8Array(
6602
+ inflated.buffer,
6603
+ inflated.byteOffset,
6604
+ inflated.byteLength
6605
+ );
6606
+ } catch {
6607
+ reject("it is not a gzip stream");
6608
+ }
6609
+ let entries;
6610
+ try {
6611
+ entries = readTar(tar);
6612
+ } catch (error) {
6613
+ if (error instanceof TarFormatError) reject(error.message);
6614
+ throw error;
6615
+ }
6616
+ const files = entries.map((entry) => {
6617
+ if (entry.type !== "0") {
6618
+ reject(`"${entry.name}" is not a regular file entry`);
6619
+ }
6620
+ return { path: entry.name, bytes: entry.data };
6621
+ });
6622
+ files.sort((left, right) => compareBytewise(left.path, right.path));
6623
+ assertSubmissionFiles(files);
6624
+ return { files, ...describeFiles(files) };
6625
+ }
6626
+ function requireCurrentFormat(alg, bountyId, command18) {
6627
+ if (alg !== SUBMISSION_ENCRYPTION_TYPE_V1) {
6628
+ throw new Error(
6629
+ `Bounty ${bountyId} declares ${BOUNTY_CHALLENGE_PROFILE}, which this CLI does not handle. Use CLI 11 for it: npx @elgora/cli@11 ${command18}`
6630
+ );
6631
+ }
6632
+ }
6633
+ function readOpenedSubmission(bundle) {
6634
+ if (bundle.alg !== SUBMISSION_ENCRYPTION_TYPE_V1) {
6635
+ throw new Error("A _v0 Submission is opened with CLI 11.");
6636
+ }
6637
+ const { files, content_sha256 } = readSubmissionArchive(bundle.archive);
6638
+ return { files, content_sha256 };
6639
+ }
6640
+ function describeFiles(sorted) {
6641
+ const manifest = {
6642
+ kind: SUBMISSION_ENCRYPTED_BUNDLE_V1_KIND,
6643
+ artifacts: sorted.map((file) => ({
6644
+ path: file.path,
6645
+ size_bytes: file.bytes.byteLength,
6646
+ sha256: sha256Hex(file.bytes)
6647
+ }))
6648
+ };
6649
+ const manifestBytes = new TextEncoder().encode(
6650
+ canonicalizeJson(manifest)
6651
+ );
6652
+ return { manifest, content_sha256: sha256Hex(manifestBytes) };
6653
+ }
6654
+ function assertSubmissionFiles(files) {
6655
+ if (files.length === 0) {
6656
+ reject("a Submission must hold at least one file");
6657
+ }
6658
+ if (files.length > MAX_SOLVER_SUBMISSION_FILES_V1) {
6659
+ reject(
6660
+ `a Submission holds at most ${MAX_SOLVER_SUBMISSION_FILES_V1} files; this one holds ${files.length}`
6661
+ );
6662
+ }
6663
+ for (const file of files) {
6664
+ const issue = describeSolverSubmissionPathIssue(file.path);
6665
+ if (issue !== null) reject(issue);
6666
+ }
6667
+ const conflict = findSolverSubmissionPathConflict(
6668
+ files.map((file) => file.path)
6669
+ );
6670
+ if (conflict !== null) reject(describeSolverSubmissionPathConflict(conflict));
6671
+ const total = files.reduce((sum, file) => sum + file.bytes.byteLength, 0);
6672
+ if (total > MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES) {
6673
+ reject(
6674
+ `a Submission extracts to at most ${MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES} bytes; this one holds ${total}`
6675
+ );
6676
+ }
6677
+ }
6678
+ function sha256Hex(bytes) {
6679
+ return createHash2("sha256").update(bytes).digest("hex");
6680
+ }
6681
+ function compareBytewise(left, right) {
6682
+ if (left < right) return -1;
6683
+ if (left > right) return 1;
6684
+ return 0;
6685
+ }
6686
+ function reject(reason) {
6687
+ throw new Error(
6688
+ `Submission archive is not a valid Elgora tar.gz: ${reason.replace(/\.$/, "")}.`
6689
+ );
6133
6690
  }
6134
6691
 
6135
- // ../../packages/core/src/winning-solution-delivery.ts
6136
- async function wrapWinningSolutionKey(input) {
6137
- const deploymentDeliveryPublicKey = await requestDeploymentDeliveryPublicKey({
6138
- apiBaseUrl: input.config.apiBaseUrl
6139
- });
6140
- const contentPathname = `/api/bounties/${input.bountyId}/submissions/${input.submissionCommitment}/content`;
6141
- const contentAuthorization = await authorizeRequest({
6142
- requestUrl: new URL(contentPathname, input.config.apiBaseUrl).toString(),
6143
- method: "GET",
6144
- body: "",
6145
- chainId: input.config.chainId,
6146
- hubAddress: input.config.hubAddress,
6147
- address: input.owner.address,
6148
- sign: input.owner.signTypedData
6149
- });
6150
- const { content_record, ciphertext_download_url } = solverSubmissionContentResponseSchema.parse(
6151
- await getAuthenticatedJson(
6152
- input.config.apiBaseUrl,
6153
- contentPathname,
6154
- contentAuthorization
6155
- )
6692
+ // src/submission-check.ts
6693
+ function checkSubmissionArchive(input) {
6694
+ const { files, content_sha256 } = readSubmissionArchive(input.archive);
6695
+ return {
6696
+ content_sha256,
6697
+ ...checkOpenedSubmission({
6698
+ files,
6699
+ profile: input.profile,
6700
+ challenge: input.challenge
6701
+ })
6702
+ };
6703
+ }
6704
+ function checkOpenedSubmission(input) {
6705
+ const { files } = input;
6706
+ const constraints = readChallengeConstraints(
6707
+ parseBountyChallengeMarkdown(input.challenge).constraints
6708
+ );
6709
+ const budget = Math.min(
6710
+ input.profile.submission.maxExtractedBytes,
6711
+ constraints.maxExtractedBytes ?? Number.POSITIVE_INFINITY
6712
+ );
6713
+ const issues = [];
6714
+ const maxFiles = Math.min(
6715
+ input.profile.submission.maxFiles,
6716
+ constraints.maxFiles ?? Number.POSITIVE_INFINITY
6717
+ );
6718
+ if (files.length > maxFiles) {
6719
+ issues.push({
6720
+ outcome: "disqualifies",
6721
+ code: "max_files",
6722
+ message: `The Submission holds ${files.length} files; at most ${maxFiles} are allowed. An included archive counts as one file.`
6723
+ });
6724
+ }
6725
+ const paths = new Set(files.map((file) => file.path));
6726
+ for (const required2 of constraints.requiredFiles ?? []) {
6727
+ if (!paths.has(required2)) {
6728
+ issues.push({
6729
+ outcome: "fails_acceptance",
6730
+ code: "required_files",
6731
+ path: required2,
6732
+ message: `The challenge requires "${required2}", and the Submission has no file at that path.`
6733
+ });
6734
+ }
6735
+ }
6736
+ if (constraints.allowedExtensions !== void 0) {
6737
+ for (const file of files) {
6738
+ if (!hasAllowedExtension(file.path, constraints.allowedExtensions)) {
6739
+ issues.push({
6740
+ outcome: "fails_acceptance",
6741
+ code: "allowed_extensions",
6742
+ path: file.path,
6743
+ message: `"${file.path}" has an extension outside the challenge's allowed_extensions (${constraints.allowedExtensions.join(", ")}).`
6744
+ });
6745
+ }
6746
+ }
6747
+ }
6748
+ const extracted = files.reduce(
6749
+ (total, file) => total + file.bytes.byteLength,
6750
+ 0
6156
6751
  );
6157
- if (content_record.bounty_id !== input.bountyId.toString()) {
6752
+ if (extracted > budget) {
6753
+ issues.push({
6754
+ outcome: "disqualifies",
6755
+ code: "max_extracted_bytes",
6756
+ message: `The Submission's files total more than ${budget} bytes.`
6757
+ });
6758
+ }
6759
+ return {
6760
+ file_count: files.length,
6761
+ extracted_bytes: extracted,
6762
+ extracted_budget_bytes: budget,
6763
+ issues
6764
+ };
6765
+ }
6766
+
6767
+ // src/verified-challenge.ts
6768
+ async function readVerifiedChallenge(input) {
6769
+ const [{ challenge }, [parameters]] = await Promise.all([
6770
+ requestBountySubmissionDetail({
6771
+ apiBaseUrl: input.apiBaseUrl,
6772
+ bountyId: input.bountyId
6773
+ }),
6774
+ input.publicClient.readContract({
6775
+ address: input.hubAddress,
6776
+ abi: ELGORA_HUB_BOUNTY_READ_ABI,
6777
+ functionName: "getBounty",
6778
+ args: [BigInt(input.bountyId)],
6779
+ ...input.blockNumber !== void 0 && {
6780
+ blockNumber: input.blockNumber
6781
+ }
6782
+ })
6783
+ ]);
6784
+ const profile = verifyChallenge({
6785
+ bountyId: input.bountyId,
6786
+ challenge,
6787
+ specCommitment: parameters.specCommitment.toLowerCase()
6788
+ });
6789
+ return { challenge, profile };
6790
+ }
6791
+ function verifyChallenge(input) {
6792
+ if (input.challenge === null) {
6158
6793
  throw new Error(
6159
- `Submission ${input.submissionCommitment} belongs to bounty_id ${content_record.bounty_id}, not ${input.bountyId}`
6794
+ `Elgora API served no verified challenge for bounty ${input.bountyId}, so the Submission format it declares cannot be determined. Try again later.`
6160
6795
  );
6161
6796
  }
6162
- const envelope = parsePrivateArtifactEnvelope(
6163
- JSON.parse(content_record.canonical_json)
6164
- );
6165
- const envelopeCommitment = derivePrivateArtifactEnvelopeCommitment(envelope);
6166
- if (envelopeCommitment !== input.submissionCommitment) {
6797
+ if (deriveBountyChallengeContentCommitment(input.challenge).toLowerCase() !== input.specCommitment.toLowerCase()) {
6167
6798
  throw new Error(
6168
- `Elgora API returned an envelope whose commitment ${envelopeCommitment} does not match the requested submission_commitment ${input.submissionCommitment}`
6799
+ `Elgora API returned a challenge that does not match bounty ${input.bountyId}'s spec commitment in ElgoraHub`
6169
6800
  );
6170
6801
  }
6171
- if (envelope.context.chain_id !== input.config.chainId || envelope.context.hub_address.toLowerCase() !== input.config.hubAddress.toLowerCase() || envelope.context.bounty_id !== input.bountyId.toString() || envelope.context.actor_address.toLowerCase() !== input.solver.toLowerCase()) {
6802
+ const { profile } = checkChallengeConformance(input.challenge);
6803
+ if (profile === null) {
6172
6804
  throw new Error(
6173
- `Submission ${input.submissionCommitment} does not belong to the requested ElgoraHub bounty and Solver`
6805
+ `Bounty ${input.bountyId} declares a protocol profile this CLI does not know. Upgrade @elgora/cli and submit again.`
6174
6806
  );
6175
6807
  }
6176
- const ciphertext = await getBytes(ciphertext_download_url);
6177
- const opened = await openPrivateArtifactEnvelopeForDelivery({
6178
- envelope,
6179
- ciphertext,
6180
- guardian_private_keys: input.guardianPrivateKeys,
6181
- timelock_decrypt: createPrivateArtifactTimelockDecrypt(),
6182
- delivery_recipient_public_key: deploymentDeliveryPublicKey,
6183
- context: {
6184
- chain_id: input.config.chainId,
6185
- hub_address: input.config.hubAddress,
6186
- bounty_id: input.bountyId.toString(),
6187
- submission_commitment: input.submissionCommitment
6188
- }
6189
- });
6190
- const requestUrl = new URL(
6191
- `/api/bounties/${input.bountyId}/delivery/wrap`,
6192
- input.config.apiBaseUrl
6193
- );
6194
- const body = JSON.stringify({
6195
- solver_address: input.solver,
6196
- submission_commitment: input.submissionCommitment,
6197
- wrapped_key: opened.wrapped_key
6198
- });
6199
- const authorization = await authorizeRequest({
6200
- requestUrl: requestUrl.toString(),
6201
- method: "POST",
6202
- contentType: "application/json",
6203
- body,
6204
- chainId: input.config.chainId,
6205
- hubAddress: input.config.hubAddress,
6206
- address: input.owner.address,
6207
- sign: input.owner.signTypedData
6208
- });
6209
- const responseBody = await postText(
6210
- requestUrl,
6211
- body,
6212
- "application/json",
6213
- authorization
6214
- );
6215
- return deliveryWrapResponseSchema.parse(responseBody);
6808
+ return profile;
6216
6809
  }
6217
6810
 
6218
6811
  // src/guardian-open.ts
6219
- import { mkdir, writeFile as writeFile2 } from "fs/promises";
6220
- import path2 from "path";
6221
6812
  var OPTIONS3 = {
6222
6813
  ...API_OPTIONS,
6223
6814
  ...ACCOUNT_OPTIONS
@@ -6255,7 +6846,7 @@ async function main3(env = process.env, argv = process.argv.slice(2)) {
6255
6846
  `Submission ${args.submissionCommitment} belongs to bounty_id ${content_record.bounty_id}, not ${args.bountyId}`
6256
6847
  );
6257
6848
  }
6258
- const envelope = await verifyPrivateArtifactEnvelope(
6849
+ const envelope = await verifySubmissionEnvelope(
6259
6850
  JSON.parse(content_record.canonical_json)
6260
6851
  );
6261
6852
  if (envelope.context.chain_id !== config.chainId || envelope.context.hub_address !== config.hubAddress || envelope.context.bounty_id !== args.bountyId || envelope.context.actor_address !== args.solver) {
@@ -6263,7 +6854,8 @@ async function main3(env = process.env, argv = process.argv.slice(2)) {
6263
6854
  `Submission envelope context does not match chain ${config.chainId}, ElgoraHub ${config.hubAddress}, bounty_id ${args.bountyId}, and Solver ${args.solver}`
6264
6855
  );
6265
6856
  }
6266
- const envelopeCommitment = derivePrivateArtifactEnvelopeCommitment(envelope);
6857
+ requireCurrentFormat(envelope.alg, args.bountyId, "guardian:open");
6858
+ const envelopeCommitment = deriveSubmissionEnvelopeCommitment(envelope);
6267
6859
  if (envelopeCommitment !== args.submissionCommitment) {
6268
6860
  throw new Error(
6269
6861
  `Elgora API returned an envelope whose commitment ${envelopeCommitment} does not match the requested submission_commitment ${args.submissionCommitment}`
@@ -6284,29 +6876,35 @@ async function main3(env = process.env, argv = process.argv.slice(2)) {
6284
6876
  `ElgoraHub does not record Solver ${args.solver} with submission_commitment ${args.submissionCommitment} for bounty_id ${args.bountyId}`
6285
6877
  );
6286
6878
  }
6879
+ const { challenge, profile } = await readVerifiedChallenge({
6880
+ publicClient,
6881
+ apiBaseUrl: config.apiBaseUrl,
6882
+ hubAddress: config.hubAddress,
6883
+ bountyId: args.bountyId,
6884
+ blockNumber: finalizedBlock.number
6885
+ });
6287
6886
  const ciphertext = await getBytes(ciphertext_download_url);
6288
- const opened = await openPrivateArtifactEnvelope({
6887
+ const opened = await openSubmissionEnvelope({
6289
6888
  envelope,
6290
6889
  ciphertext,
6291
6890
  guardian_private_keys: guardianPrivateKeys,
6292
- timelock_decrypt: createPrivateArtifactTimelockDecrypt()
6891
+ timelock_decrypt: createSubmissionTimelockDecrypt()
6892
+ });
6893
+ const { files, content_sha256 } = readOpenedSubmission(opened);
6894
+ await writeOpenedArtifacts(args.outputDir, files);
6895
+ const check = checkOpenedSubmission({
6896
+ files,
6897
+ profile,
6898
+ challenge
6293
6899
  });
6294
- await mkdir(args.outputDir, { recursive: true });
6295
- for (const artifact of opened.artifacts) {
6296
- const filePath = path2.join(args.outputDir, artifact.name);
6297
- await writeFile2(filePath, artifact.bytes);
6298
- }
6299
6900
  console.log(
6300
6901
  JSON.stringify({
6301
6902
  event: "guardian_open.completed",
6302
6903
  bounty_id: args.bountyId,
6303
6904
  solver: args.solver,
6304
6905
  submission_commitment: args.submissionCommitment,
6305
- artifact_count: opened.artifacts.length,
6306
- // The budget the Guardian has to vet these artifacts against before
6307
- // extracting any archive among them. Reported here so it comes from the
6308
- // running client rather than a possibly older copy of the skill.
6309
- max_extracted_bytes: MAX_SOLVER_SUBMISSION_EXTRACTED_BYTES
6906
+ content_sha256,
6907
+ ...check
6310
6908
  })
6311
6909
  );
6312
6910
  }
@@ -6338,7 +6936,7 @@ function readGuardianOpenArgs(argv) {
6338
6936
  bountyId,
6339
6937
  solver: normalizedSolver,
6340
6938
  submissionCommitment: normalizedCommitment,
6341
- outputDir: outputDirOverride ?? path2.join(DEFAULT_OUTPUT_ROOT, bountyId, normalizedCommitment)
6939
+ outputDir: outputDirOverride ?? path3.join(DEFAULT_OUTPUT_ROOT, bountyId, normalizedCommitment)
6342
6940
  };
6343
6941
  }
6344
6942
  function readGuardianPrivateKeys(env) {
@@ -7367,11 +7965,11 @@ var OPTIONS6 = { ...HUB_OPTIONS, ...SIGNER_OPTIONS };
7367
7965
  var USAGE6 = "elgora-cli guardian:prove-roster-key <challenge_json_path|->";
7368
7966
  async function main6(env = process.env, argv = process.argv.slice(2)) {
7369
7967
  const parsed = parseArgs(argv, OPTIONS6);
7370
- const [path6] = parsed.positionals;
7371
- if (!path6 || parsed.positionals.length !== 1) {
7968
+ const [path7] = parsed.positionals;
7969
+ if (!path7 || parsed.positionals.length !== 1) {
7372
7970
  throw usageError(USAGE6);
7373
7971
  }
7374
- const challenge = parseChallenge(await readInput(path6));
7972
+ const challenge = parseChallenge(await readInput(path7));
7375
7973
  const envWithDefaults = deploymentEnv(env, parsed);
7376
7974
  const { chainId, hubAddress } = loadPublicDeploymentConfig(envWithDefaults);
7377
7975
  if (challenge.context.chain_id !== chainId || challenge.context.hub_address.toLowerCase() !== hubAddress.toLowerCase()) {
@@ -7697,7 +8295,7 @@ async function assertCheckedSubmission(input) {
7697
8295
  });
7698
8296
  let envelope;
7699
8297
  try {
7700
- envelope = await verifyPrivateArtifactEnvelope(
8298
+ envelope = await verifySubmissionEnvelope(
7701
8299
  JSON.parse(response.content_record.canonical_json)
7702
8300
  );
7703
8301
  } catch (error) {
@@ -7705,7 +8303,7 @@ async function assertCheckedSubmission(input) {
7705
8303
  cause: error
7706
8304
  });
7707
8305
  }
7708
- const matchesCheckedPair = response.content_record.bounty_id === bountyId && derivePrivateArtifactEnvelopeCommitment(envelope) === input.submissionCommitment && envelope.context.chain_id === input.chainId && envelope.context.hub_address === input.hubAddress.toLowerCase() && envelope.context.bounty_id === bountyId && envelope.context.actor_address === input.winner.toLowerCase();
8306
+ const matchesCheckedPair = response.content_record.bounty_id === bountyId && deriveSubmissionEnvelopeCommitment(envelope) === input.submissionCommitment && envelope.context.chain_id === input.chainId && envelope.context.hub_address === input.hubAddress.toLowerCase() && envelope.context.bounty_id === bountyId && envelope.context.actor_address === input.winner.toLowerCase();
7709
8307
  if (!matchesCheckedPair) {
7710
8308
  throw new Error(
7711
8309
  "Submission envelope does not match the checked Solver and Submission"
@@ -7788,7 +8386,7 @@ function resolveChainId(selector) {
7788
8386
  }
7789
8387
 
7790
8388
  // src/version.ts
7791
- var CLI_VERSION = true ? "11.0.0" : "0.0.0-dev";
8389
+ var CLI_VERSION = true ? "12.0.0" : "0.0.0-dev";
7792
8390
  var CLI_NODE_ENGINES = true ? "24.x || 26.x" : "24.x || 26.x";
7793
8391
 
7794
8392
  // src/node-engines.ts
@@ -7852,8 +8450,8 @@ function compareVersions(left, right) {
7852
8450
  }
7853
8451
 
7854
8452
  // src/poster-open-winning-submission.ts
7855
- import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
7856
- import path3 from "path";
8453
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
8454
+ import path4 from "path";
7857
8455
  var OPTIONS8 = { ...API_OPTIONS, ...ACCOUNT_OPTIONS };
7858
8456
  var USAGE8 = "elgora-cli poster:open-winning-submission <bounty_id> [output_dir]";
7859
8457
  var DEFAULT_OUTPUT_ROOT2 = "./winning-submission";
@@ -7917,47 +8515,34 @@ async function main8(env = process.env, argv = process.argv.slice(2)) {
7917
8515
  contentAuthorization
7918
8516
  )
7919
8517
  );
7920
- if (content_record.bounty_id !== args.bountyId) {
7921
- throw new Error(
7922
- `Submission ${retrieved.submission_commitment} belongs to bounty_id ${content_record.bounty_id}, not ${args.bountyId}`
7923
- );
7924
- }
7925
- const envelope = await verifyPrivateArtifactEnvelope(
8518
+ const envelope = parseSubmissionEnvelope(
7926
8519
  JSON.parse(content_record.canonical_json)
7927
8520
  );
7928
- if (envelope.context.chain_id !== config.chainId || envelope.context.hub_address !== config.hubAddress.toLowerCase() || envelope.context.bounty_id !== args.bountyId) {
7929
- throw new Error(
7930
- `Winning Submission envelope does not belong to chain ${config.chainId}, ElgoraHub ${config.hubAddress}, and bounty_id ${args.bountyId}`
7931
- );
7932
- }
7933
- const envelopeCommitment = derivePrivateArtifactEnvelopeCommitment(envelope);
7934
- if (envelopeCommitment !== retrieved.submission_commitment) {
7935
- throw new Error(
7936
- `Winning Submission envelope commitment ${envelopeCommitment} does not match the retrieved submission_commitment ${retrieved.submission_commitment}`
7937
- );
7938
- }
7939
- const ciphertext = await getBytes(ciphertext_download_url);
7940
- const opened = await openPrivateArtifactEnvelopeWithKnownAesKey({
8521
+ requireCurrentFormat(
8522
+ envelope.alg,
8523
+ args.bountyId,
8524
+ "poster:open-winning-submission"
8525
+ );
8526
+ const opened = await openSubmissionEnvelopeWithKnownAesKey({
7941
8527
  envelope,
7942
- ciphertext,
8528
+ ciphertext: await getBytes(ciphertext_download_url),
7943
8529
  rawAesKey
7944
8530
  });
7945
- const outputDir = args.outputDir ?? path3.join(
7946
- DEFAULT_OUTPUT_ROOT2,
7947
- args.bountyId,
7948
- retrieved.submission_commitment
8531
+ if (opened.alg !== SUBMISSION_ENCRYPTION_TYPE_V1) return;
8532
+ const outputDir = args.outputDir ?? DEFAULT_OUTPUT_ROOT2;
8533
+ const file = path4.join(
8534
+ outputDir,
8535
+ `elgora-${args.bountyId}-${retrieved.submission_commitment.slice(2, 12)}.tar.gz`
7949
8536
  );
7950
8537
  await mkdir2(outputDir, { recursive: true });
7951
- for (const artifact of opened.artifacts) {
7952
- await writeFile3(path3.join(outputDir, artifact.name), artifact.bytes);
7953
- }
8538
+ await writeFile2(file, opened.archive);
7954
8539
  console.log(
7955
8540
  JSON.stringify({
7956
8541
  event: "poster_open_winning_submission.completed",
7957
8542
  bounty_id: args.bountyId,
7958
8543
  submission_commitment: retrieved.submission_commitment,
7959
- artifact_count: opened.artifacts.length,
7960
- output_dir: outputDir
8544
+ file,
8545
+ byte_length: opened.archive.byteLength
7961
8546
  })
7962
8547
  );
7963
8548
  } finally {
@@ -8006,10 +8591,10 @@ var OPTIONS9 = {
8006
8591
  var USAGE9 = "elgora-cli poster:publish-fund [--prepare-only] <bounty_challenge_md_path|->";
8007
8592
  async function main9(env = process.env, argv = process.argv.slice(2)) {
8008
8593
  const args = parseArgs(argv, OPTIONS9);
8009
- const [path6] = args.positionals;
8010
- if (args.positionals.length !== 1 || !path6) throw usageError(USAGE9);
8594
+ const [path7] = args.positionals;
8595
+ if (args.positionals.length !== 1 || !path7) throw usageError(USAGE9);
8011
8596
  const config = loadApiDeployment(env, args);
8012
- const bountyChallengeMarkdown = await readInput(path6);
8597
+ const bountyChallengeMarkdown = await readInput(path7);
8013
8598
  const localSpecCommitment = deriveBountyChallengeContentCommitment(
8014
8599
  bountyChallengeMarkdown
8015
8600
  );
@@ -8387,7 +8972,7 @@ function parseRosterAddGuardianArgs(argv) {
8387
8972
  if (!name) throw usageError(USAGE11);
8388
8973
  if (!KEY_ID_RE.test(name)) {
8389
8974
  throw new Error(
8390
- // A Solver's CLI seals private artifacts against this exact string as
8975
+ // A Solver's CLI seals Submissions against this exact string as
8391
8976
  // the Guardian's key_id, so it must be non-empty, start with an
8392
8977
  // alphanumeric, and stay within printable ASCII (no control/formatting
8393
8978
  // characters, no leading whitespace).
@@ -8566,72 +9151,23 @@ function get(value, key) {
8566
9151
  }
8567
9152
  var command13 = { main: main13, OPTIONS: OPTIONS13, USAGE: USAGE13 };
8568
9153
 
8569
- // src/spec-commitment.ts
8570
- var OPTIONS14 = {};
8571
- var USAGE14 = "elgora-cli spec-commitment <bounty_challenge_md_path|->";
8572
- async function main14(_env = process.env, argv = process.argv.slice(2)) {
8573
- const markdown = await readBountyChallengeMarkdown2(
8574
- parseArgs(argv, OPTIONS14).positionals
8575
- );
8576
- const specCommitment = deriveBountyChallengeContentCommitment(markdown);
8577
- console.log(
8578
- JSON.stringify({
8579
- event: "spec_commitment.derived",
8580
- byte_length: Buffer.byteLength(markdown, "utf8"),
8581
- spec_commitment: specCommitment
8582
- })
8583
- );
8584
- }
8585
- function readBountyChallengeMarkdown2(argv) {
8586
- const path6 = argv[0];
8587
- if (!path6 || argv.length !== 1) throw usageError(USAGE14);
8588
- return readInput(path6);
8589
- }
8590
- var command14 = { main: main14, OPTIONS: OPTIONS14, USAGE: USAGE14 };
8591
-
8592
9154
  // src/submit.ts
8593
- import { readdir, readFile as readFile5, stat } from "fs/promises";
8594
- import path4 from "path";
8595
- var OPTIONS15 = {
9155
+ import { lstat as lstat2, readdir as readdir2, readFile as readFile5 } from "fs/promises";
9156
+ import path5 from "path";
9157
+ var OPTIONS14 = {
8596
9158
  ...API_OPTIONS,
8597
9159
  ...ACCOUNT_OPTIONS,
8598
9160
  ...PREPARE_ONLY_OPTIONS,
8599
9161
  ...REVEAL_DELAY_OPTIONS
8600
9162
  };
8601
- var USAGE15 = "elgora-cli solver:submit [--prepare-only] <bounty_id> <artifact_dir>";
9163
+ var USAGE14 = "elgora-cli solver:submit [--prepare-only] <bounty_id> <folder_or_file>";
8602
9164
  var PREPARE_SUBMISSION_ATTEMPTS = 5;
8603
9165
  var PREPARE_SUBMISSION_BASE_DELAY_MS = 1e3;
8604
9166
  var PREPARE_SUBMISSION_MAX_DELAY_MS = 4e3;
8605
9167
  var BOUNTY_PROJECTION_UNAVAILABLE_CODE = "bounty_projection_unavailable";
8606
9168
  var UNAUTHORIZED_CODE = "unauthorized";
8607
- var MEDIA_TYPE_BY_EXTENSION = {
8608
- txt: "text/plain",
8609
- md: "text/markdown",
8610
- json: "application/json",
8611
- csv: "text/csv",
8612
- pdf: "application/pdf",
8613
- zip: "application/zip",
8614
- gz: "application/gzip",
8615
- tar: "application/x-tar",
8616
- png: "image/png",
8617
- jpg: "image/jpeg",
8618
- jpeg: "image/jpeg",
8619
- gif: "image/gif",
8620
- svg: "image/svg+xml",
8621
- py: "text/x-python",
8622
- js: "text/javascript",
8623
- ts: "text/plain",
8624
- sol: "text/plain",
8625
- yaml: "application/yaml",
8626
- yml: "application/yaml",
8627
- toml: "application/toml",
8628
- log: "text/plain",
8629
- patch: "text/x-diff",
8630
- diff: "text/x-diff"
8631
- };
8632
- var DEFAULT_MEDIA_TYPE = "application/octet-stream";
8633
- async function main15(env = process.env, argv = process.argv.slice(2)) {
8634
- const parsed = parseArgs(argv, OPTIONS15);
9169
+ async function main14(env = process.env, argv = process.argv.slice(2)) {
9170
+ const parsed = parseArgs(argv, OPTIONS14);
8635
9171
  const config = {
8636
9172
  ...loadApiDeployment(env, parsed),
8637
9173
  timelockRevealDelaySeconds: readRevealDelaySeconds(parsed)
@@ -8651,10 +9187,11 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8651
9187
  address: solver,
8652
9188
  sign: approvals.signTypedData
8653
9189
  });
8654
- const rosterResponse = await requestBountyGuardianRoster({
9190
+ const detail = await requestBountySubmissionDetail({
8655
9191
  apiBaseUrl: config.apiBaseUrl,
8656
9192
  bountyId: bountyIdText
8657
9193
  });
9194
+ const rosterResponse = detail.bounty;
8658
9195
  const readBountySubmissionFacts = async () => {
8659
9196
  const [parameters, state] = await publicClient.readContract({
8660
9197
  address: config.hubAddress,
@@ -8664,6 +9201,7 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8664
9201
  });
8665
9202
  return parseBountySubmissionFacts({
8666
9203
  bountyId: bountyIdText,
9204
+ specCommitment: parameters.specCommitment,
8667
9205
  guardianRosterHash: parameters.guardianRosterHash,
8668
9206
  submissionDeadline: parameters.submissionDeadline,
8669
9207
  status: state.status
@@ -8675,12 +9213,29 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8675
9213
  response: rosterResponse,
8676
9214
  hubGuardianRosterHash: bountyFacts.guardianRosterHash
8677
9215
  });
8678
- const artifacts = await readArtifactDirectory(args.artifactDir);
9216
+ const profile = verifyChallenge({
9217
+ bountyId: bountyIdText,
9218
+ challenge: detail.challenge,
9219
+ specCommitment: bountyFacts.specCommitment
9220
+ });
9221
+ requireCurrentFormat(profile.encryption.type, bountyIdText, "solver:submit");
9222
+ const { plaintext, content_sha256, macos_metadata_skipped } = await readSubmissionPlaintext(args.inputPath, profile);
9223
+ const { issues } = checkSubmissionArchive({
9224
+ archive: plaintext.archive,
9225
+ profile,
9226
+ challenge: detail.challenge
9227
+ });
9228
+ if (issues.length > 0) {
9229
+ throw new Error(
9230
+ `The Submission does not meet bounty ${bountyIdText}'s rules, so nothing was submitted: ${issues.map((issue) => issue.message).join(" ")} Run solver:check for the full report.`
9231
+ );
9232
+ }
9233
+ const contentDigest = { content_sha256 };
8679
9234
  const { envelope, ciphertext } = await buildSolverSubmissionEnvelope({
8680
9235
  config,
8681
9236
  bountyId: args.bountyId,
8682
9237
  solver,
8683
- artifacts,
9238
+ plaintext,
8684
9239
  guardianRoster,
8685
9240
  submissionDeadline: bountyFacts.submissionDeadline
8686
9241
  });
@@ -8708,7 +9263,9 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8708
9263
  event: "submit.ciphertext_uploaded",
8709
9264
  bounty_id: bountyIdText,
8710
9265
  solver,
8711
- byte_length: ciphertext.byteLength
9266
+ byte_length: ciphertext.byteLength,
9267
+ ...contentDigest,
9268
+ ...macos_metadata_skipped.length > 0 && { macos_metadata_skipped }
8712
9269
  })
8713
9270
  );
8714
9271
  const prepared = await requestPrepareSubmission({
@@ -8718,7 +9275,7 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8718
9275
  envelope,
8719
9276
  authorizeRequest: authorizeSubmitRequest
8720
9277
  });
8721
- const localCommitment = derivePrivateArtifactEnvelopeCommitment(envelope);
9278
+ const localCommitment = deriveSubmissionEnvelopeCommitment(envelope);
8722
9279
  if (prepared.content_record.submission_commitment !== localCommitment) {
8723
9280
  throw new Error(
8724
9281
  "Elgora API returned a submission_commitment that does not match the locally sealed envelope"
@@ -8792,7 +9349,8 @@ async function main15(env = process.env, argv = process.argv.slice(2)) {
8792
9349
  bounty_id: bountyIdText,
8793
9350
  solver,
8794
9351
  submission_commitment: localCommitment,
8795
- guardian_roster_hash: signingFacts.guardianRosterHash
9352
+ guardian_roster_hash: signingFacts.guardianRosterHash,
9353
+ ...contentDigest
8796
9354
  })
8797
9355
  );
8798
9356
  }
@@ -8824,10 +9382,28 @@ function parseBountySubmissionFacts(input) {
8824
9382
  }
8825
9383
  return {
8826
9384
  status: decodeBountyStatus(input.status),
9385
+ specCommitment: input.specCommitment.toLowerCase(),
8827
9386
  submissionDeadline: Number(input.submissionDeadline),
8828
9387
  guardianRosterHash: input.guardianRosterHash.toLowerCase()
8829
9388
  };
8830
9389
  }
9390
+ async function readSubmissionPlaintext(artifactPath, profile) {
9391
+ const { files, macos_metadata_skipped } = await readSubmissionFiles(
9392
+ artifactPath,
9393
+ profile
9394
+ );
9395
+ const { archive, content_sha256 } = buildSubmissionArchive(files);
9396
+ if (archive.byteLength + AES_GCM_TAG_BYTES > MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES) {
9397
+ throw new Error(
9398
+ `This Submission packs to ${archive.byteLength + AES_GCM_TAG_BYTES} bytes encrypted and exceeds Elgora's ${formatMebibytes(MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES)} encrypted submission limit. Reduce the files before submitting.`
9399
+ );
9400
+ }
9401
+ return {
9402
+ plaintext: { encryption_type: SUBMISSION_ENCRYPTION_TYPE_V1, archive },
9403
+ content_sha256,
9404
+ macos_metadata_skipped
9405
+ };
9406
+ }
8831
9407
  function verifyPinnedGuardianRoster(input) {
8832
9408
  const roster = input.response.guardian_roster.map((guardian) => ({
8833
9409
  name: guardian.name,
@@ -8895,7 +9471,7 @@ async function buildSolverSubmissionEnvelope(input) {
8895
9471
  const guardianRecipients = input.guardianRoster.map(
8896
9472
  (guardian) => ({
8897
9473
  key_id: guardian.name,
8898
- suite: PRIVATE_ARTIFACT_HPKE_SUITE,
9474
+ suite: SUBMISSION_HPKE_SUITE,
8899
9475
  public_key: guardian.encryptionPublicKey
8900
9476
  })
8901
9477
  );
@@ -8907,69 +9483,132 @@ async function buildSolverSubmissionEnvelope(input) {
8907
9483
  bounty_id: bountyId,
8908
9484
  actor_address: solver
8909
9485
  },
8910
- artifacts: input.artifacts,
9486
+ ...input.plaintext,
8911
9487
  guardian_recipients: guardianRecipients,
8912
- reveal_policy: buildPrivateArtifactRevealPolicyForDeadline({
9488
+ reveal_policy: buildSubmissionRevealPolicyForDeadline({
8913
9489
  submissionDeadline: input.submissionDeadline,
8914
9490
  revealDelaySeconds: input.config.timelockRevealDelaySeconds
8915
9491
  }),
8916
- timelock_encrypt: input.timelockEncrypt ?? createPrivateArtifactTimelockEncrypt()
9492
+ timelock_encrypt: input.timelockEncrypt ?? createSubmissionTimelockEncrypt()
8917
9493
  };
8918
9494
  if (input.randomBytes) {
8919
9495
  envelopeInput.random_bytes = input.randomBytes;
8920
9496
  }
8921
- return sealPrivateArtifactEnvelope(envelopeInput);
9497
+ return sealSubmissionEnvelope(envelopeInput);
8922
9498
  }
8923
9499
  function readSubmitArgs(argv) {
8924
- const [bountyIdText, artifactDir] = argv;
8925
- if (argv.length !== 2 || !bountyIdText || !isNumeric(bountyIdText) || !artifactDir?.trim()) {
8926
- throw usageError(USAGE15);
9500
+ const [bountyIdText, inputPath] = argv;
9501
+ if (argv.length !== 2 || !bountyIdText || !isNumeric(bountyIdText) || !inputPath?.trim()) {
9502
+ throw usageError(USAGE14);
9503
+ }
9504
+ return { bountyId: BigInt(bountyIdText), inputPath };
9505
+ }
9506
+ async function readSubmissionFiles(inputPath, profile) {
9507
+ const { maxFiles, maxExtractedBytes } = profile.submission;
9508
+ const root = await lstat2(inputPath);
9509
+ const found = [];
9510
+ const skipped = [];
9511
+ if (root.isFile()) {
9512
+ const issue = describeSolverSubmissionPathIssue(path5.basename(inputPath));
9513
+ if (issue !== null) throw new Error(issue);
9514
+ found.push({
9515
+ path: path5.basename(inputPath),
9516
+ absolute: inputPath,
9517
+ size: root.size
9518
+ });
9519
+ } else if (root.isDirectory()) {
9520
+ await walkSubmissionDirectory(inputPath, "", found, skipped, maxFiles);
9521
+ if (found.length === 0) {
9522
+ throw new Error(`No files found in folder: ${inputPath}`);
9523
+ }
9524
+ } else {
9525
+ throw new Error(
9526
+ `${inputPath} is ${describeEntryType(root)}; point solver:submit at a folder or a regular file.`
9527
+ );
8927
9528
  }
8928
- return { bountyId: BigInt(bountyIdText), artifactDir };
8929
- }
8930
- async function readArtifactDirectory(artifactDir) {
8931
- const entries = await readdir(artifactDir, { withFileTypes: true });
8932
- const unsupportedEntry = entries.find((entry) => !entry.isFile());
8933
- if (unsupportedEntry) {
9529
+ const conflict = findSolverSubmissionPathConflict(
9530
+ found.map((file) => file.path)
9531
+ );
9532
+ if (conflict !== null) {
9533
+ throw new Error(describeSolverSubmissionPathConflict(conflict));
9534
+ }
9535
+ const declaredBytes = found.reduce((total, file) => total + file.size, 0);
9536
+ if (declaredBytes > maxExtractedBytes) {
8934
9537
  throw new Error(
8935
- `Artifact directory must be flat and contain only regular files; found unsupported entry "${unsupportedEntry.name}". Remove it and submit only regular files directly inside ${artifactDir}.`
9538
+ `The Submission holds ${declaredBytes} bytes of files and exceeds Elgora's ${maxExtractedBytes}-byte limit on what one Submission extracts to.`
8936
9539
  );
8937
9540
  }
8938
- if (entries.length === 0) {
8939
- throw new Error(`No files found in artifact directory: ${artifactDir}`);
9541
+ const files = [];
9542
+ for (const file of found) {
9543
+ files.push({
9544
+ path: file.path,
9545
+ bytes: new Uint8Array(await readFile5(file.absolute))
9546
+ });
8940
9547
  }
8941
- if (entries.length > MAX_SOLVER_SUBMISSION_FILES) {
9548
+ return { files, macos_metadata_skipped: skipped };
9549
+ }
9550
+ function isMacosMetadataFile(entry) {
9551
+ return entry.isFile() && (entry.name === ".DS_Store" || entry.name.startsWith("._"));
9552
+ }
9553
+ async function walkSubmissionDirectory(directory, relative, found, skipped, maxFiles) {
9554
+ const entries = await readdir2(directory, { withFileTypes: true });
9555
+ if (entries.length === 0 && relative !== "") {
8942
9556
  throw new Error(
8943
- `Artifact directory holds ${entries.length} files and exceeds Elgora's maximum of ${MAX_SOLVER_SUBMISSION_FILES} files per Submission. Combine them into fewer files, if the challenge allows it, before submitting.`
9557
+ `Folder "${relative}" is empty. A Submission cannot carry an empty folder; remove it or add the files it should hold.`
8944
9558
  );
8945
9559
  }
8946
- let plaintextByteLength = 0;
9560
+ const foundBefore = found.length;
8947
9561
  for (const entry of entries) {
8948
- plaintextByteLength += (await stat(path4.join(artifactDir, entry.name))).size;
8949
- if (plaintextByteLength > MAX_SOLVER_SUBMISSION_ARTIFACT_BYTES) {
9562
+ const entryPath = relative === "" ? entry.name : `${relative}/${entry.name}`;
9563
+ const absolute = path5.join(directory, entry.name);
9564
+ if (isMacosMetadataFile(entry)) {
9565
+ skipped.push(entryPath);
9566
+ continue;
9567
+ }
9568
+ const issue = describeSolverSubmissionPathIssue(entryPath);
9569
+ if (issue !== null) throw new Error(issue);
9570
+ if (entry.isDirectory()) {
9571
+ await walkSubmissionDirectory(
9572
+ absolute,
9573
+ entryPath,
9574
+ found,
9575
+ skipped,
9576
+ maxFiles
9577
+ );
9578
+ continue;
9579
+ }
9580
+ if (!entry.isFile()) {
9581
+ throw new Error(
9582
+ `"${entryPath}" is ${describeEntryType(entry)}. A Submission carries regular files only; replace it with the file it stands for or remove it.`
9583
+ );
9584
+ }
9585
+ found.push({
9586
+ path: entryPath,
9587
+ absolute,
9588
+ size: (await lstat2(absolute)).size
9589
+ });
9590
+ if (found.length > maxFiles) {
8950
9591
  throw new Error(
8951
- `Artifact directory exceeds Elgora's ${formatMebibytes(MAX_SOLVER_SUBMISSION_ARTIFACT_BYTES)} of files per Submission, which is what fits the ${formatMebibytes(MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES)} encrypted submission limit once packaged. Reduce the final artifact package before submitting.`
9592
+ `The Submission holds more than ${maxFiles} files, Elgora's maximum per Submission.`
8952
9593
  );
8953
9594
  }
8954
9595
  }
8955
- return Promise.all(
8956
- entries.map(async (entry) => {
8957
- const bytes = await readFile5(path4.join(artifactDir, entry.name));
8958
- return {
8959
- name: entry.name,
8960
- media_type: guessMediaType(entry.name),
8961
- bytes: new Uint8Array(bytes)
8962
- };
8963
- })
8964
- );
9596
+ if (found.length === foundBefore && relative !== "") {
9597
+ throw new Error(
9598
+ `Folder "${relative}" holds only macOS metadata (.DS_Store or ._ files), which is left out, so it would be empty. A Submission cannot carry an empty folder; remove it or add the files it should hold.`
9599
+ );
9600
+ }
9601
+ }
9602
+ function describeEntryType(entry) {
9603
+ if (entry.isSymbolicLink()) return "a symbolic link";
9604
+ if (entry.isFIFO()) return "a FIFO";
9605
+ if (entry.isSocket()) return "a socket";
9606
+ if (entry.isBlockDevice() || entry.isCharacterDevice()) return "a device";
9607
+ return "not a regular file";
8965
9608
  }
8966
9609
  function formatMebibytes(byteLength) {
8967
9610
  return `${byteLength / 1024 / 1024} MiB`;
8968
9611
  }
8969
- function guessMediaType(fileName) {
8970
- const extension = path4.extname(fileName).slice(1).toLowerCase();
8971
- return MEDIA_TYPE_BY_EXTENSION[extension] ?? DEFAULT_MEDIA_TYPE;
8972
- }
8973
9612
  async function requestArtifactUploadUrl(input) {
8974
9613
  const requestUrl = new URL(
8975
9614
  `/api/bounties/${input.bountyId}/submissions/${input.solver}/artifact-upload-url`,
@@ -8993,7 +9632,7 @@ async function requestPrepareSubmission(input) {
8993
9632
  `/api/bounties/${input.bountyId}/submissions/${input.solver}/prepare`,
8994
9633
  input.apiBaseUrl
8995
9634
  );
8996
- const bodyText = serializePrivateArtifactEnvelope(input.envelope);
9635
+ const bodyText = serializeSubmissionEnvelope(input.envelope);
8997
9636
  for (let attempt = 1; ; attempt += 1) {
8998
9637
  try {
8999
9638
  const authorization = await input.authorizeRequest(requestUrl, bodyText);
@@ -9027,13 +9666,107 @@ async function requestPrepareSubmission(input) {
9027
9666
  }
9028
9667
  }
9029
9668
  }
9669
+ var command14 = { main: main14, OPTIONS: OPTIONS14, USAGE: USAGE14 };
9670
+
9671
+ // src/solver-check.ts
9672
+ var OPTIONS15 = { ...API_OPTIONS, ...RPC_OPTIONS, ...HUB_OPTIONS };
9673
+ var USAGE15 = "elgora-cli solver:check <bounty_id> <folder_or_file>";
9674
+ async function main15(env = process.env, argv = process.argv.slice(2)) {
9675
+ const parsed = parseArgs(argv, OPTIONS15);
9676
+ const [bountyIdText, inputPath] = parsed.positionals;
9677
+ if (parsed.positionals.length !== 2 || !bountyIdText || !isNumeric(bountyIdText) || !inputPath?.trim()) {
9678
+ throw usageError(USAGE15);
9679
+ }
9680
+ const config = loadApiDeployment(env, parsed);
9681
+ const publicClient = createChainPublicClient(config.chainId, config.rpcUrl);
9682
+ await assertHubDeployment(publicClient, config.chainId, config.hubAddress);
9683
+ const { challenge, profile } = await readVerifiedChallenge({
9684
+ publicClient,
9685
+ apiBaseUrl: config.apiBaseUrl,
9686
+ hubAddress: config.hubAddress,
9687
+ bountyId: bountyIdText
9688
+ });
9689
+ requireCurrentFormat(profile.encryption.type, bountyIdText, "solver:submit");
9690
+ const report = await checkSubmission({ inputPath, profile, challenge });
9691
+ const blocking = report.issues.length > 0;
9692
+ console.log(
9693
+ JSON.stringify({
9694
+ event: blocking ? "solver_check.issues" : "solver_check.passed",
9695
+ bounty_id: bountyIdText,
9696
+ // Named so a passing result is never read as "this Submission wins".
9697
+ checked: "format_and_constraints",
9698
+ ...report
9699
+ })
9700
+ );
9701
+ if (blocking) process.exitCode = 1;
9702
+ }
9703
+ async function checkSubmission(input) {
9704
+ const base4 = {
9705
+ profile: input.profile.profile,
9706
+ max_encrypted_bytes: MAX_SOLVER_SUBMISSION_CIPHERTEXT_BYTES
9707
+ };
9708
+ let archive;
9709
+ let skipped;
9710
+ try {
9711
+ const { plaintext, macos_metadata_skipped } = await readSubmissionPlaintext(
9712
+ input.inputPath,
9713
+ input.profile
9714
+ );
9715
+ archive = plaintext.archive;
9716
+ skipped = macos_metadata_skipped;
9717
+ } catch (error) {
9718
+ return {
9719
+ ...base4,
9720
+ issues: [
9721
+ {
9722
+ outcome: "refused",
9723
+ code: "submission_input",
9724
+ message: error instanceof Error ? error.message : String(error)
9725
+ }
9726
+ ]
9727
+ };
9728
+ }
9729
+ return {
9730
+ ...base4,
9731
+ encrypted_bytes: archive.byteLength + AES_GCM_TAG_BYTES,
9732
+ ...skipped.length > 0 && { macos_metadata_skipped: skipped },
9733
+ ...checkSubmissionArchive({
9734
+ archive,
9735
+ profile: input.profile,
9736
+ challenge: input.challenge
9737
+ })
9738
+ };
9739
+ }
9030
9740
  var command15 = { main: main15, OPTIONS: OPTIONS15, USAGE: USAGE15 };
9031
9741
 
9742
+ // src/spec-commitment.ts
9743
+ var OPTIONS16 = {};
9744
+ var USAGE16 = "elgora-cli spec-commitment <bounty_challenge_md_path|->";
9745
+ async function main16(_env = process.env, argv = process.argv.slice(2)) {
9746
+ const markdown = await readBountyChallengeMarkdown2(
9747
+ parseArgs(argv, OPTIONS16).positionals
9748
+ );
9749
+ const specCommitment = deriveBountyChallengeContentCommitment(markdown);
9750
+ console.log(
9751
+ JSON.stringify({
9752
+ event: "spec_commitment.derived",
9753
+ byte_length: Buffer.byteLength(markdown, "utf8"),
9754
+ spec_commitment: specCommitment
9755
+ })
9756
+ );
9757
+ }
9758
+ function readBountyChallengeMarkdown2(argv) {
9759
+ const path7 = argv[0];
9760
+ if (!path7 || argv.length !== 1) throw usageError(USAGE16);
9761
+ return readInput(path7);
9762
+ }
9763
+ var command16 = { main: main16, OPTIONS: OPTIONS16, USAGE: USAGE16 };
9764
+
9032
9765
  // src/verification-record.ts
9033
- var OPTIONS16 = { ...API_OPTIONS, ...HUB_OPTIONS };
9034
- var USAGE16 = "elgora-cli verification-record <bounty_id>";
9035
- async function main16(env = process.env, argv = process.argv.slice(2)) {
9036
- const parsed = parseArgs(argv, OPTIONS16);
9766
+ var OPTIONS17 = { ...API_OPTIONS, ...HUB_OPTIONS };
9767
+ var USAGE17 = "elgora-cli verification-record <bounty_id>";
9768
+ async function main17(env = process.env, argv = process.argv.slice(2)) {
9769
+ const parsed = parseArgs(argv, OPTIONS17);
9037
9770
  const args = readVerificationRecordArgs(parsed.positionals);
9038
9771
  const config = loadClientDeploymentConfig(deploymentEnv(env, parsed));
9039
9772
  const record = await requestVerificationRecord({
@@ -9085,15 +9818,15 @@ function readVerificationRecordArgs(argv) {
9085
9818
  const [bountyIdText] = argv;
9086
9819
  const bountyId = bountyIdText?.trim();
9087
9820
  if (argv.length !== 1 || !bountyId || !isBountyId(bountyId)) {
9088
- throw usageError(USAGE16);
9821
+ throw usageError(USAGE17);
9089
9822
  }
9090
9823
  return { bountyId: BigInt(bountyId) };
9091
9824
  }
9092
- var command16 = { main: main16, OPTIONS: OPTIONS16, USAGE: USAGE16 };
9825
+ var command17 = { main: main17, OPTIONS: OPTIONS17, USAGE: USAGE17 };
9093
9826
 
9094
9827
  // src/cli.ts
9095
9828
  if (process.env.DOTENV_CONFIG_PATH) {
9096
- const dotenvPath = path5.resolve(process.env.DOTENV_CONFIG_PATH);
9829
+ const dotenvPath = path6.resolve(process.env.DOTENV_CONFIG_PATH);
9097
9830
  const dotenvResult = loadDotenv({ path: dotenvPath, quiet: true });
9098
9831
  process.stderr.write(
9099
9832
  `Env: ${dotenvPath} (${dotenvResult.error ? `not found: ${dotenvResult.error.message}` : "loaded"})
@@ -9116,11 +9849,12 @@ var COMMANDS = {
9116
9849
  "roster:add-guardian": command11,
9117
9850
  "roster:prepare-guardian-proof": command12,
9118
9851
  "roster:verify-guardian-proof": command13,
9119
- "solver:submit": command15,
9852
+ "solver:check": command15,
9853
+ "solver:submit": command14,
9120
9854
  claim: command2,
9121
9855
  "check-challenge": command,
9122
- "spec-commitment": command14,
9123
- "verification-record": command16
9856
+ "spec-commitment": command16,
9857
+ "verification-record": command17
9124
9858
  };
9125
9859
  var OPTION_HELP = {
9126
9860
  "--api-base-url": "--api-base-url <url> Elgora API (else ELGORA_API_BASE_URL)",
@@ -9146,7 +9880,7 @@ var COMMAND_HELP = {
9146
9880
  },
9147
9881
  "guardian:open": {
9148
9882
  title: "Verify and decrypt one Solver Submission.",
9149
- description: "Check the exact Solver and Submission against ElgoraHub and the\n commitment-addressed envelope, then sign the protected content GET\n with this Guardian's account before downloading ciphertext. Then\n decrypt with this Guardian's retained X25519 keys and write the\n artifacts locally. Without output_dir, files go under\n guardian-open/<bounty_id>/<submission_commitment>/. A fetch or decrypt\n failure never becomes a Verdict.",
9883
+ description: "Verify, decrypt and extract one Submission into a new or empty\n output_dir (default guardian-open/<bounty_id>/<submission_commitment>/).\n Prints content_sha256 and the issues solver:check reports.",
9150
9884
  required: ["ELGORA_GUARDIAN_PRIVATE_KEYS_JSON"]
9151
9885
  },
9152
9886
  "guardian:prove-roster-key": {
@@ -9176,7 +9910,7 @@ var COMMAND_HELP = {
9176
9910
  },
9177
9911
  "poster:open-winning-submission": {
9178
9912
  title: "Retrieve and decrypt the finalized winning Submission.",
9179
- description: "Authenticate as the funding Poster, ask the existing delivery API for\n the finalized winner's key rewrapped to a fresh local key, verify the\n committed envelope, and decrypt the artifacts locally. Without\n output_dir, files go under\n winning-submission/<bounty_id>/<submission_commitment>/.",
9913
+ description: "Save the finalized winning Submission, decrypted, as one .tar.gz in\n output_dir (default winning-submission/).",
9180
9914
  required: []
9181
9915
  },
9182
9916
  "roster:add-guardian": {
@@ -9194,9 +9928,14 @@ var COMMAND_HELP = {
9194
9928
  description: "Takes the unmodified stdout of roster:prepare-guardian-proof and of\n the Guardian's guardian:prove-roster-key, reconstructs the signed\n payload from the fixture the admin generated (never sent to the\n Guardian), and checks through the chain that the signature is valid\n for the exact proposed account, which also covers contract and\n EIP-7702 accounts (ERC-1271). Exits non-zero on any mismatch.\n Advisory only \u2014 convinces\n the admin, proves nothing to ElgoraHub, and does not itself add the\n Guardian; run roster:add-guardian separately once satisfied.",
9195
9929
  required: []
9196
9930
  },
9931
+ "solver:check": {
9932
+ title: "Check a Submission the way a Guardian will, before submitting.",
9933
+ description: "Run a Guardian's format and constraint checks on a folder or file\n without submitting anything. Exits 1 on any issue.",
9934
+ required: []
9935
+ },
9197
9936
  "solver:submit": {
9198
- title: "Encrypt and submit a Solver's private artifacts.",
9199
- description: "Encrypt for the Guardian roster pinned to this bounty, upload the\n ciphertext, check ElgoraHub immediately before signing, and locally\n encode all four V2 submit values.",
9937
+ title: "Encrypt and submit a Solver's private files.",
9938
+ description: "Pack a folder or one file into a .tar.gz, encrypt it for the bounty's\n Guardians, upload it and submit it on ElgoraHub. Refuses it on any issue\n solver:check reports. Prints content_sha256, which stays on this machine.",
9200
9939
  required: []
9201
9940
  },
9202
9941
  claim: {
@@ -9241,8 +9980,8 @@ async function run(rawArgv) {
9241
9980
  printCommandHelpOrUsage(rest[0]);
9242
9981
  return;
9243
9982
  }
9244
- const command17 = COMMANDS[subcommand];
9245
- if (!command17) {
9983
+ const command18 = COMMANDS[subcommand];
9984
+ if (!command18) {
9246
9985
  process.stderr.write(`Unknown command: ${subcommand}
9247
9986
  `);
9248
9987
  printUsage();
@@ -9255,8 +9994,8 @@ async function run(rawArgv) {
9255
9994
  }
9256
9995
  let args;
9257
9996
  try {
9258
- args = parseArgs(rest, command17.OPTIONS);
9259
- if ("--signer" in command17.OPTIONS) readSignerOptions(args);
9997
+ args = parseArgs(rest, command18.OPTIONS);
9998
+ if ("--signer" in command18.OPTIONS) readSignerOptions(args);
9260
9999
  } catch (error) {
9261
10000
  process.stderr.write(
9262
10001
  `Error: ${error instanceof Error ? error.message : String(error)}
@@ -9273,10 +10012,10 @@ Run \`elgora-cli help ${subcommand}\` for its arguments and options.
9273
10012
  process.exitCode = 1;
9274
10013
  return;
9275
10014
  }
9276
- const networkNotice = usesDeployment(command17.OPTIONS) ? defaultNetworkNotice(env) : void 0;
10015
+ const networkNotice = usesDeployment(command18.OPTIONS) ? defaultNetworkNotice(env) : void 0;
9277
10016
  if (networkNotice) process.stderr.write(`${networkNotice}
9278
10017
  `);
9279
- const needsLocalKey = "--signer" in command17.OPTIONS && (args.value("--signer") ?? "local") === "local";
10018
+ const needsLocalKey = "--signer" in command18.OPTIONS && (args.value("--signer") ?? "local") === "local";
9280
10019
  const missing = missingRequiredEnv(
9281
10020
  [
9282
10021
  ...COMMAND_HELP[subcommand]?.required ?? [],
@@ -9303,7 +10042,7 @@ Run \`elgora-cli help ${subcommand}\` for its arguments and options.
9303
10042
  process.exitCode = 1;
9304
10043
  return;
9305
10044
  }
9306
- await command17.main(env, rest);
10045
+ await command18.main(env, rest);
9307
10046
  }
9308
10047
  function usesDeployment(options) {
9309
10048
  return DEPLOYMENT_OPTIONS.some((option) => option in options);
@@ -9344,14 +10083,14 @@ function printUsage() {
9344
10083
  }
9345
10084
  function printCommandHelpOrUsage(name) {
9346
10085
  const help = name ? COMMAND_HELP[name] : void 0;
9347
- const command17 = name ? COMMANDS[name] : void 0;
9348
- if (!help || !command17) {
10086
+ const command18 = name ? COMMANDS[name] : void 0;
10087
+ if (!help || !command18) {
9349
10088
  printUsage();
9350
10089
  process.exitCode = name ? 1 : 0;
9351
10090
  return;
9352
10091
  }
9353
- const options = command17.OPTIONS;
9354
- const lines = [`Usage: ${command17.USAGE}`, "", help.description];
10092
+ const options = command18.OPTIONS;
10093
+ const lines = [`Usage: ${command18.USAGE}`, "", help.description];
9355
10094
  const described = Object.keys(options).filter(
9356
10095
  (option) => option in OPTION_HELP
9357
10096
  );
@@ -9440,9 +10179,9 @@ function extractIssues(error) {
9440
10179
  if (!Array.isArray(issues)) return [];
9441
10180
  return issues.map((issue) => {
9442
10181
  if (issue && typeof issue === "object" && "message" in issue) {
9443
- const path6 = "path" in issue ? issue.path : void 0;
10182
+ const path7 = "path" in issue ? issue.path : void 0;
9444
10183
  return {
9445
- path: typeof path6 === "string" ? path6 : "",
10184
+ path: typeof path7 === "string" ? path7 : "",
9446
10185
  message: String(issue.message)
9447
10186
  };
9448
10187
  }