@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.
- package/README.md +23 -5
- package/dist/cli.js +1486 -747
- 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
|
|
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
|
|
44
|
-
return base64ToBytes(
|
|
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
|
|
133
|
-
var
|
|
134
|
-
var
|
|
135
|
-
var
|
|
136
|
-
var
|
|
137
|
-
var
|
|
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
|
|
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
|
|
147
|
-
var
|
|
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 =
|
|
153
|
-
var lowerAddressSchema =
|
|
154
|
-
var bytes32Schema =
|
|
155
|
-
var sha256HexSchema =
|
|
156
|
-
var keyIdSchema =
|
|
157
|
-
var
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
).
|
|
161
|
-
var
|
|
162
|
-
|
|
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 =
|
|
247
|
+
var guardianRecipientSchema = z2.strictObject({
|
|
172
248
|
key_id: keyIdSchema,
|
|
173
|
-
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 =
|
|
255
|
+
var guardianRecipientPrivateKeySchema = z2.strictObject({
|
|
180
256
|
key_id: keyIdSchema,
|
|
181
|
-
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 =
|
|
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 =
|
|
201
|
-
var
|
|
202
|
-
name:
|
|
276
|
+
var guardianRecipientPrivateKeysSchema = z2.array(guardianRecipientPrivateKeySchema).min(1);
|
|
277
|
+
var bundleFileEntrySchema = z2.strictObject({
|
|
278
|
+
name: fileNameSchema,
|
|
203
279
|
media_type: mediaTypeSchema,
|
|
204
|
-
size_bytes:
|
|
280
|
+
size_bytes: z2.number().int().nonnegative().lte(Number.MAX_SAFE_INTEGER),
|
|
205
281
|
sha256: sha256HexSchema
|
|
206
282
|
});
|
|
207
|
-
var
|
|
208
|
-
kind:
|
|
209
|
-
context:
|
|
283
|
+
var submissionPublicManifestSchema = z2.strictObject({
|
|
284
|
+
kind: z2.literal(SUBMISSION_PUBLIC_MANIFEST_KIND),
|
|
285
|
+
context: submissionContextSchema
|
|
210
286
|
});
|
|
211
|
-
var
|
|
287
|
+
var encryptedBundleFileSchema = bundleFileEntrySchema.extend({
|
|
212
288
|
bytes: base64UrlSchema
|
|
213
289
|
});
|
|
214
|
-
var
|
|
215
|
-
kind:
|
|
216
|
-
context:
|
|
217
|
-
artifacts:
|
|
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
|
-
(
|
|
222
|
-
"
|
|
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
|
|
226
|
-
kind:
|
|
227
|
-
network:
|
|
228
|
-
chain_hash:
|
|
229
|
-
round:
|
|
230
|
-
not_before:
|
|
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 =
|
|
308
|
+
var wrappedRecipientKeySchema = z2.strictObject({
|
|
233
309
|
key_id: keyIdSchema,
|
|
234
|
-
suite:
|
|
310
|
+
suite: z2.literal(SUBMISSION_HPKE_SUITE),
|
|
235
311
|
enc: base64UrlSchema,
|
|
236
312
|
ciphertext: base64UrlSchema
|
|
237
313
|
});
|
|
238
|
-
var wrappedKeyBagSchema =
|
|
239
|
-
kind:
|
|
240
|
-
keys:
|
|
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
|
|
243
|
-
kind:
|
|
244
|
-
alg:
|
|
245
|
-
context:
|
|
246
|
-
public_manifest:
|
|
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:
|
|
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:
|
|
335
|
+
ciphertext_locator: z2.string().min(1)
|
|
260
336
|
});
|
|
261
|
-
function
|
|
262
|
-
return
|
|
337
|
+
function parseSubmissionEnvelope(input) {
|
|
338
|
+
return submissionEnvelopeSchema.parse(input);
|
|
263
339
|
}
|
|
264
|
-
function
|
|
340
|
+
function serializeSubmissionEnvelope(envelope) {
|
|
265
341
|
return canonicalizeJson(
|
|
266
|
-
|
|
342
|
+
submissionEnvelopeSchema.parse(envelope)
|
|
267
343
|
);
|
|
268
344
|
}
|
|
269
|
-
function
|
|
270
|
-
return keccak256(toBytes(
|
|
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
|
|
277
|
-
const envelope =
|
|
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 =
|
|
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 =
|
|
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
|
|
469
|
-
const context =
|
|
470
|
-
|
|
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
|
|
478
|
-
|
|
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
|
|
483
|
-
|
|
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(
|
|
563
|
+
const envelopeNonce = randomBytes(SUBMISSION_NONCE_BYTES);
|
|
500
564
|
const iv = randomBytes(AES_GCM_IV_BYTES);
|
|
501
565
|
const aadInput = {
|
|
502
|
-
kind:
|
|
503
|
-
alg
|
|
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:
|
|
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:
|
|
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 =
|
|
536
|
-
kind:
|
|
537
|
-
alg
|
|
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
|
|
554
|
-
const opened = await
|
|
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
|
|
562
|
-
const
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
658
|
+
...bundle,
|
|
597
659
|
rawAesKey: openResult.rawAesKey
|
|
598
660
|
};
|
|
599
661
|
}
|
|
600
|
-
async function
|
|
601
|
-
const envelope = await
|
|
662
|
+
async function openSubmissionEnvelopeWithKnownAesKey(input) {
|
|
663
|
+
const envelope = await verifySubmissionEnvelope(input.envelope);
|
|
602
664
|
await assertCiphertextIntegrity(input.ciphertext, envelope.ciphertext_sha256);
|
|
603
|
-
const
|
|
665
|
+
const bundle = await decryptAndVerifyBundle(envelope, input.ciphertext, {
|
|
604
666
|
rawAesKey: input.rawAesKey
|
|
605
667
|
});
|
|
606
|
-
return { envelope,
|
|
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:
|
|
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
|
-
|
|
631
|
-
|
|
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
|
-
|
|
667
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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("
|
|
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
|
-
`${
|
|
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
|
|
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
|
|
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
|
|
914
|
+
var SubmissionTimelockOpenError = class extends Error {
|
|
896
915
|
code;
|
|
897
916
|
constructor(code, message, options) {
|
|
898
917
|
super(message, options);
|
|
899
|
-
this.name = "
|
|
918
|
+
this.name = "SubmissionTimelockOpenError";
|
|
900
919
|
this.code = code;
|
|
901
920
|
}
|
|
902
921
|
};
|
|
903
|
-
function
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
966
|
+
assertSubmissionTimelockCiphertextMatchesPolicy({
|
|
948
967
|
ciphertext,
|
|
949
968
|
revealPolicy: reveal_policy
|
|
950
969
|
});
|
|
951
|
-
const releaseState =
|
|
970
|
+
const releaseState = getSubmissionTimelockReleaseState({
|
|
952
971
|
revealPolicy: reveal_policy,
|
|
953
972
|
nowSeconds: nowSeconds()
|
|
954
973
|
});
|
|
955
974
|
if (releaseState.status !== "released") {
|
|
956
|
-
throw new
|
|
975
|
+
throw new SubmissionTimelockOpenError(
|
|
957
976
|
"timelock_not_released",
|
|
958
|
-
`
|
|
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
|
|
986
|
+
if (error instanceof SubmissionTimelockOpenError) {
|
|
968
987
|
throw error;
|
|
969
988
|
}
|
|
970
|
-
throw new
|
|
989
|
+
throw new SubmissionTimelockOpenError(
|
|
971
990
|
"timelock_open_failed",
|
|
972
|
-
"
|
|
991
|
+
"Submission timelock ciphertext could not be opened",
|
|
973
992
|
{ cause: error }
|
|
974
993
|
);
|
|
975
994
|
}
|
|
976
995
|
};
|
|
977
996
|
}
|
|
978
|
-
function
|
|
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
|
|
1005
|
-
const
|
|
1006
|
-
if (
|
|
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 (
|
|
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 !==
|
|
1053
|
-
throw new Error("unsupported
|
|
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
|
|
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
|
|
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
|
|
1081
|
-
return safeDecodeUtf8(
|
|
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
|
|
1135
|
+
throw new SubmissionTimelockOpenError(
|
|
1117
1136
|
"timelock_beacon_unavailable",
|
|
1118
|
-
"drand beacon is unavailable for
|
|
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(
|
|
1189
|
-
return
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
1345
|
-
|
|
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
|
|
1386
|
-
if (!
|
|
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
|
|
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
|
|
1557
|
+
if (profile !== void 0 && !isKnownProtocolProfile(profile)) {
|
|
1443
1558
|
issues.push({
|
|
1444
1559
|
path: "frontmatter.profile",
|
|
1445
|
-
message: `Must
|
|
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
|
|
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,
|
|
1590
|
+
function normalizeFrontmatterScalar(rawValue, path7, issues) {
|
|
1476
1591
|
const value = rawValue.trim();
|
|
1477
1592
|
if (value.length === 0) {
|
|
1478
|
-
issues.push({ path:
|
|
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:
|
|
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,
|
|
1607
|
+
function parsePositiveSafeIntegerFrontmatter(value, path7, issues) {
|
|
1493
1608
|
if (value === void 0) return null;
|
|
1494
1609
|
if (!isPositiveInteger(value)) {
|
|
1495
|
-
issues.push({ path:
|
|
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:
|
|
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,
|
|
1620
|
+
function parsePositiveIntegerStringFrontmatter(value, path7, issues) {
|
|
1506
1621
|
if (value === void 0) return null;
|
|
1507
1622
|
if (!isPositiveInteger(value)) {
|
|
1508
|
-
issues.push({ path:
|
|
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
|
|
1533
|
-
var addressSchema =
|
|
1534
|
-
var bytes32Schema2 =
|
|
1535
|
-
var hexBytesSchema =
|
|
1536
|
-
var bountyChallengeContentRecordSchema =
|
|
1537
|
-
content_kind:
|
|
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:
|
|
1654
|
+
byte_length: z4.number().int().positive(),
|
|
1540
1655
|
token_address: addressSchema,
|
|
1541
|
-
created_at:
|
|
1656
|
+
created_at: z4.string().datetime({ offset: true })
|
|
1542
1657
|
}).strict();
|
|
1543
|
-
var bountyPublicationResponseSchema =
|
|
1658
|
+
var bountyPublicationResponseSchema = z4.object({
|
|
1544
1659
|
content_record: bountyChallengeContentRecordSchema,
|
|
1545
|
-
create_bounty_transaction:
|
|
1546
|
-
chain_id:
|
|
1660
|
+
create_bounty_transaction: z4.object({
|
|
1661
|
+
chain_id: z4.number().int().positive(),
|
|
1547
1662
|
to: addressSchema,
|
|
1548
|
-
value:
|
|
1663
|
+
value: z4.literal("0"),
|
|
1549
1664
|
data: hexBytesSchema,
|
|
1550
|
-
function_name:
|
|
1551
|
-
args:
|
|
1665
|
+
function_name: z4.literal("create_bounty"),
|
|
1666
|
+
args: z4.object({
|
|
1552
1667
|
spec_commitment: bytes32Schema2,
|
|
1553
|
-
escrow_amount:
|
|
1554
|
-
submission_deadline:
|
|
1668
|
+
escrow_amount: z4.string().refine(isPositiveInteger),
|
|
1669
|
+
submission_deadline: z4.number().int().positive()
|
|
1555
1670
|
}).strict()
|
|
1556
1671
|
}).strict(),
|
|
1557
|
-
protocol_fee_policy:
|
|
1672
|
+
protocol_fee_policy: z4.object({
|
|
1558
1673
|
treasury_recipient: addressSchema,
|
|
1559
|
-
treasury_fee_bps:
|
|
1674
|
+
treasury_fee_bps: z4.number().int().min(0).max(1e4),
|
|
1560
1675
|
guardian_fee_recipient: addressSchema,
|
|
1561
|
-
guardian_fee_bps:
|
|
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 =
|
|
1573
|
-
issue_index:
|
|
1574
|
-
message:
|
|
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:
|
|
1692
|
+
status: z4.enum(["resolved", "open"])
|
|
1578
1693
|
}).strict();
|
|
1579
|
-
var readinessFindingSchema =
|
|
1580
|
-
path:
|
|
1581
|
-
message:
|
|
1582
|
-
code:
|
|
1583
|
-
anchor:
|
|
1584
|
-
hazard:
|
|
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 =
|
|
1702
|
+
var bountyReviewResponseSchema = z4.object({
|
|
1588
1703
|
content_record: bountyChallengeContentRecordSchema.omit({ created_at: true }),
|
|
1589
|
-
readiness:
|
|
1590
|
-
decision:
|
|
1704
|
+
readiness: z4.object({
|
|
1705
|
+
decision: z4.literal("ready"),
|
|
1591
1706
|
// Publication reuses the approval of these exact bytes until then.
|
|
1592
|
-
approved_until:
|
|
1707
|
+
approved_until: z4.string().datetime({ offset: true }),
|
|
1593
1708
|
// The dissenting pass's findings on a ready check; never blocking.
|
|
1594
|
-
warnings:
|
|
1595
|
-
prior_issues_resolved:
|
|
1596
|
-
prior_issues_open:
|
|
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
|
|
1684
|
-
if (parsed !== null &&
|
|
1685
|
-
checkTightenings(parsed.constraints,
|
|
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:
|
|
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
|
|
1707
|
-
if (
|
|
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
|
|
1766
|
+
message: `Unknown protocol profile "${declared}". A bounty can only declare a published profile version.`
|
|
1713
1767
|
});
|
|
1714
1768
|
}
|
|
1715
|
-
return
|
|
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
|
|
1898
|
-
if (!
|
|
1899
|
-
return readInput(
|
|
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
|
|
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 =
|
|
4405
|
-
ciphertext_sha256:
|
|
4406
|
-
ciphertext_byte_length:
|
|
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
|
|
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 =
|
|
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 =
|
|
4436
|
-
var publicDeploymentEnvSchema =
|
|
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:
|
|
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:
|
|
4457
|
-
ELGORA_RPC_URL:
|
|
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:
|
|
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:
|
|
4468
|
-
ELGORA_SUBGRAPH_ENDPOINT:
|
|
4469
|
-
ELGORA_ARTIFACT_STORAGE_ENDPOINT:
|
|
4470
|
-
ELGORA_ARTIFACT_STORAGE_REGION:
|
|
4471
|
-
ELGORA_ARTIFACT_STORAGE_BUCKET:
|
|
4472
|
-
ELGORA_ARTIFACT_STORAGE_ACCESS_KEY_ID:
|
|
4473
|
-
ELGORA_ARTIFACT_STORAGE_SECRET_ACCESS_KEY:
|
|
4474
|
-
ELGORA_DELIVERY_PUBLIC_KEY:
|
|
4475
|
-
ELGORA_DELIVERY_PRIVATE_KEY:
|
|
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:
|
|
4484
|
-
ELGORA_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 =
|
|
4487
|
-
var bountyPublicationEnvSchema =
|
|
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
|
|
4492
|
-
var hexAddressSchema =
|
|
4493
|
-
var hexBytes32Schema =
|
|
4494
|
-
var hexDataSchema =
|
|
4495
|
-
var artifactUploadUrlResponseSchema =
|
|
4496
|
-
locator:
|
|
4497
|
-
upload_url:
|
|
4498
|
-
method:
|
|
4499
|
-
expires_at:
|
|
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 =
|
|
4502
|
-
content_record:
|
|
4503
|
-
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:
|
|
4506
|
-
created_at:
|
|
4640
|
+
byte_length: z7.number().int().nonnegative(),
|
|
4641
|
+
created_at: z7.string().min(1)
|
|
4507
4642
|
}),
|
|
4508
|
-
submit_transaction:
|
|
4509
|
-
chain_id:
|
|
4643
|
+
submit_transaction: z7.object({
|
|
4644
|
+
chain_id: z7.number().int().positive(),
|
|
4510
4645
|
to: hexAddressSchema,
|
|
4511
|
-
value:
|
|
4646
|
+
value: z7.literal("0"),
|
|
4512
4647
|
data: hexDataSchema,
|
|
4513
|
-
function_name:
|
|
4514
|
-
args:
|
|
4515
|
-
bounty_id:
|
|
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:
|
|
4653
|
+
extra_data: z7.literal("0x")
|
|
4519
4654
|
})
|
|
4520
4655
|
})
|
|
4521
4656
|
});
|
|
4522
|
-
var solverSubmissionContentResponseSchema =
|
|
4523
|
-
content_record:
|
|
4524
|
-
bounty_id:
|
|
4525
|
-
canonical_json:
|
|
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:
|
|
4528
|
-
expires_at:
|
|
4662
|
+
ciphertext_download_url: z7.string().min(1),
|
|
4663
|
+
expires_at: z7.string().min(1)
|
|
4529
4664
|
});
|
|
4530
|
-
var
|
|
4531
|
-
bounty:
|
|
4665
|
+
var bountySubmissionDetailResponseSchema = z7.object({
|
|
4666
|
+
bounty: z7.object({
|
|
4532
4667
|
guardian_roster_hash: hexBytes32Schema,
|
|
4533
|
-
guardian_roster:
|
|
4534
|
-
|
|
4535
|
-
name:
|
|
4668
|
+
guardian_roster: z7.array(
|
|
4669
|
+
z7.object({
|
|
4670
|
+
name: z7.string(),
|
|
4536
4671
|
account: hexAddressSchema,
|
|
4537
|
-
encryption_public_key:
|
|
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
|
|
4545
|
-
var hexAddressSchema2 =
|
|
4546
|
-
var hexBytes32Schema2 =
|
|
4547
|
-
var integerStringSchema =
|
|
4548
|
-
var verdictEntrySchema =
|
|
4549
|
-
name:
|
|
4550
|
-
outcome:
|
|
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:
|
|
4554
|
-
supporting:
|
|
4689
|
+
committed_at: z8.string().datetime({ offset: true }).nullable(),
|
|
4690
|
+
supporting: z8.boolean()
|
|
4555
4691
|
}).strict();
|
|
4556
|
-
var formattedVerificationRecordSchema =
|
|
4557
|
-
chain_id:
|
|
4692
|
+
var formattedVerificationRecordSchema = z8.object({
|
|
4693
|
+
chain_id: z8.number().int().positive(),
|
|
4558
4694
|
hub_address: hexAddressSchema2,
|
|
4559
4695
|
bounty_id: integerStringSchema,
|
|
4560
|
-
status:
|
|
4696
|
+
status: z8.enum(["awarded", "no_valid_submission", "timed_out"]),
|
|
4561
4697
|
spec_commitment: hexBytes32Schema2,
|
|
4562
4698
|
winner: hexAddressSchema2.nullable(),
|
|
4563
|
-
verdicts:
|
|
4564
|
-
settlement:
|
|
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:
|
|
4576
|
-
spec_content_byte_length:
|
|
4711
|
+
content_record: z8.object({
|
|
4712
|
+
spec_content_byte_length: z8.number().int().positive()
|
|
4577
4713
|
}).strict(),
|
|
4578
|
-
verified_at:
|
|
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
|
|
4601
|
-
var hexBytes32Schema3 =
|
|
4602
|
-
var hexAddressSchema3 =
|
|
4603
|
-
var deliveryKeyIdSchema =
|
|
4604
|
-
var winningSolutionWrappedKeySchema =
|
|
4605
|
-
enc:
|
|
4606
|
-
ciphertext:
|
|
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 =
|
|
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 =
|
|
4614
|
-
bounty_id:
|
|
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:
|
|
4755
|
+
created_at: z9.string().min(1)
|
|
4620
4756
|
});
|
|
4621
|
-
var deliveryWrapResponseSchema =
|
|
4622
|
-
status:
|
|
4757
|
+
var deliveryWrapResponseSchema = z9.strictObject({
|
|
4758
|
+
status: z9.literal("stored"),
|
|
4623
4759
|
wrap: deliveryWrapRecordSchema
|
|
4624
4760
|
});
|
|
4625
|
-
var deliveryRetrieveRequestSchema =
|
|
4761
|
+
var deliveryRetrieveRequestSchema = z9.strictObject({
|
|
4626
4762
|
poster_ephemeral_public_key: x25519PublicKeySchema
|
|
4627
4763
|
});
|
|
4628
|
-
var deliveryRetrieveResponseSchema =
|
|
4764
|
+
var deliveryRetrieveResponseSchema = z9.strictObject({
|
|
4629
4765
|
submission_commitment: hexBytes32Schema3,
|
|
4630
4766
|
wrapped_key: winningSolutionWrappedKeySchema
|
|
4631
4767
|
});
|
|
4632
|
-
var deliveryStatusResponseSchema =
|
|
4633
|
-
exists:
|
|
4768
|
+
var deliveryStatusResponseSchema = z9.strictObject({
|
|
4769
|
+
exists: z9.boolean()
|
|
4634
4770
|
});
|
|
4635
|
-
var deliveryKeyConfigResponseSchema =
|
|
4771
|
+
var deliveryKeyConfigResponseSchema = z9.strictObject({
|
|
4636
4772
|
delivery_key_id: deliveryKeyIdSchema,
|
|
4637
|
-
public_key:
|
|
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
|
|
4778
|
+
import { z as z10 } from "zod";
|
|
4643
4779
|
var WRITTEN_VERDICT_FORMAT_VERSION = 0;
|
|
4644
4780
|
var FRONTMATTER_DELIMITER2 = "---";
|
|
4645
|
-
var decimalSchema =
|
|
4646
|
-
var bountyIdSchema2 =
|
|
4647
|
-
var addressSchema2 =
|
|
4648
|
-
var bytes32Schema3 =
|
|
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:
|
|
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:
|
|
4790
|
+
verdict_body: z10.string().refine((value) => value.trim().length > 0, "Must not be empty")
|
|
4655
4791
|
};
|
|
4656
|
-
var writtenVerdictSchema =
|
|
4657
|
-
|
|
4792
|
+
var writtenVerdictSchema = z10.discriminatedUnion("outcome", [
|
|
4793
|
+
z10.object({
|
|
4658
4794
|
...commonFields,
|
|
4659
|
-
outcome:
|
|
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
|
-
|
|
4805
|
+
z10.object({
|
|
4670
4806
|
...commonFields,
|
|
4671
|
-
outcome:
|
|
4672
|
-
winner:
|
|
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(
|
|
4890
|
+
function invalidWrittenVerdict(path7, message) {
|
|
4755
4891
|
return new BoundaryValidationError(
|
|
4756
4892
|
"Invalid written Verdict",
|
|
4757
|
-
[{ path:
|
|
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
|
|
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
|
|
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,
|
|
5355
|
-
const url = `${PRIVY_API_URL}${
|
|
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} ${
|
|
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} ${
|
|
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
|
|
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 =
|
|
6116
|
-
error:
|
|
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
|
-
//
|
|
6136
|
-
|
|
6137
|
-
const
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
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 (
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
|
6799
|
+
`Elgora API returned a challenge that does not match bounty ${input.bountyId}'s spec commitment in ElgoraHub`
|
|
6169
6800
|
);
|
|
6170
6801
|
}
|
|
6171
|
-
|
|
6802
|
+
const { profile } = checkChallengeConformance(input.challenge);
|
|
6803
|
+
if (profile === null) {
|
|
6172
6804
|
throw new Error(
|
|
6173
|
-
`
|
|
6805
|
+
`Bounty ${input.bountyId} declares a protocol profile this CLI does not know. Upgrade @elgora/cli and submit again.`
|
|
6174
6806
|
);
|
|
6175
6807
|
}
|
|
6176
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
6887
|
+
const opened = await openSubmissionEnvelope({
|
|
6289
6888
|
envelope,
|
|
6290
6889
|
ciphertext,
|
|
6291
6890
|
guardian_private_keys: guardianPrivateKeys,
|
|
6292
|
-
timelock_decrypt:
|
|
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
|
-
|
|
6306
|
-
|
|
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 ??
|
|
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 [
|
|
7371
|
-
if (!
|
|
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(
|
|
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
|
|
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 &&
|
|
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 ? "
|
|
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
|
|
7856
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
const
|
|
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
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7960
|
-
|
|
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 [
|
|
8010
|
-
if (args.positionals.length !== 1 || !
|
|
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(
|
|
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
|
|
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
|
|
8594
|
-
import
|
|
8595
|
-
var
|
|
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
|
|
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
|
-
|
|
8608
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
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
|
-
|
|
9486
|
+
...input.plaintext,
|
|
8911
9487
|
guardian_recipients: guardianRecipients,
|
|
8912
|
-
reveal_policy:
|
|
9488
|
+
reveal_policy: buildSubmissionRevealPolicyForDeadline({
|
|
8913
9489
|
submissionDeadline: input.submissionDeadline,
|
|
8914
9490
|
revealDelaySeconds: input.config.timelockRevealDelaySeconds
|
|
8915
9491
|
}),
|
|
8916
|
-
timelock_encrypt: input.timelockEncrypt ??
|
|
9492
|
+
timelock_encrypt: input.timelockEncrypt ?? createSubmissionTimelockEncrypt()
|
|
8917
9493
|
};
|
|
8918
9494
|
if (input.randomBytes) {
|
|
8919
9495
|
envelopeInput.random_bytes = input.randomBytes;
|
|
8920
9496
|
}
|
|
8921
|
-
return
|
|
9497
|
+
return sealSubmissionEnvelope(envelopeInput);
|
|
8922
9498
|
}
|
|
8923
9499
|
function readSubmitArgs(argv) {
|
|
8924
|
-
const [bountyIdText,
|
|
8925
|
-
if (argv.length !== 2 || !bountyIdText || !isNumeric(bountyIdText) || !
|
|
8926
|
-
throw usageError(
|
|
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
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
8931
|
-
|
|
8932
|
-
|
|
8933
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
8939
|
-
|
|
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
|
-
|
|
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
|
-
`
|
|
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
|
-
|
|
9560
|
+
const foundBefore = found.length;
|
|
8947
9561
|
for (const entry of entries) {
|
|
8948
|
-
|
|
8949
|
-
|
|
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
|
-
`
|
|
9592
|
+
`The Submission holds more than ${maxFiles} files, Elgora's maximum per Submission.`
|
|
8952
9593
|
);
|
|
8953
9594
|
}
|
|
8954
9595
|
}
|
|
8955
|
-
|
|
8956
|
-
|
|
8957
|
-
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
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 =
|
|
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
|
|
9034
|
-
var
|
|
9035
|
-
async function
|
|
9036
|
-
const parsed = parseArgs(argv,
|
|
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(
|
|
9821
|
+
throw usageError(USAGE17);
|
|
9089
9822
|
}
|
|
9090
9823
|
return { bountyId: BigInt(bountyId) };
|
|
9091
9824
|
}
|
|
9092
|
-
var
|
|
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 =
|
|
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:
|
|
9852
|
+
"solver:check": command15,
|
|
9853
|
+
"solver:submit": command14,
|
|
9120
9854
|
claim: command2,
|
|
9121
9855
|
"check-challenge": command,
|
|
9122
|
-
"spec-commitment":
|
|
9123
|
-
"verification-record":
|
|
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: "
|
|
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: "
|
|
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
|
|
9199
|
-
description: "
|
|
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
|
|
9245
|
-
if (!
|
|
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,
|
|
9259
|
-
if ("--signer" in
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
9348
|
-
if (!help || !
|
|
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 =
|
|
9354
|
-
const lines = [`Usage: ${
|
|
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
|
|
10182
|
+
const path7 = "path" in issue ? issue.path : void 0;
|
|
9444
10183
|
return {
|
|
9445
|
-
path: typeof
|
|
10184
|
+
path: typeof path7 === "string" ? path7 : "",
|
|
9446
10185
|
message: String(issue.message)
|
|
9447
10186
|
};
|
|
9448
10187
|
}
|