market-settlement: the dispute and settlement rail of a verifiable infrastructure market (AereComputeMarketV3 on testnet 28001, five scenarios measured end to end, a pure judge over recorded timelines, AIP-23 receipts); README index
This commit is contained in:
parent
cf8c56792f
commit
52634a1300
@ -16,6 +16,7 @@ notarization command of the verification layer, and the agents' x402 wallet (EIP
|
||||
| [`control-plane/`](control-plane/) | from findings to a finished migration: a prioritized plan from the inventory and the scanner, its execution through the gateway, KMS and PKI with consent per action and a measured proof afterwards, recipes for the servers the products do not touch and a rescan that judges them, a compliance report against NIST IR 8547, the EU roadmap and optionally CNSA 2.0, and a console that checks it all again |
|
||||
| [`agents/`](agents/) | limits an AI agent cannot break unseen: a post-quantum identity (ML-DSA-65), a policy (spending per time window, allowed tools and recipients, which actions need human approval), a signed ledger of every action judged against the policy, approvals and revocation signed by people, and a verifier that re-runs the policy over the whole ledger without trusting the agent; two branches of one ledger are a proof of equivocation anyone can check; and a wallet that pays over x402 only what the agent's ledger records and its policy allows, either holding the payment key for the owner or co-signing from a 2-of-2 contract wallet that neither the agent nor the owner can spend alone; both run on the public testnet with their evidence; and the chain as the witness of a ledger: a notarized head, read through the AIP-23 verifier under a post-quantum certified anchor, bounds entry times from below (a backdated entry is caught) |
|
||||
| [`proof-of-ai/`](proof-of-ai/) | the AIP-23 envelope of an AI agent's action: model, version, weights digest, prompt, input, output, tools, agent, requester, as digests; verified by the same AIP-23 verifier as every other proof |
|
||||
| [`market-settlement/`](market-settlement/) | the dispute and settlement rail of a verifiable infrastructure market, measured end to end on testnet 28001: an order with its reward in escrow and a provider bond, the result committed on chain with its execution envelope notarized under a post-quantum certified anchor, a challenge window in which anyone disputes with a recomputed result and a bond, an arbiter that can only route the escrow (with a permissionless 14-day timeout), deadline reclaims, and an AIP-23 settlement receipt; a pure judge over the job's timeline that checks deadlines, the window, who resolved, the payout, the replay and the proof |
|
||||
| [`verifiable-data/`](verifiable-data/) | a dataset whose every answer is checkable against a short commitment without the data: RFC 9162 commitments per version (append-only, consistency proofs), equality and range queries proven correct AND complete (contiguous run of a sorted index plus its two neighbours), hash-based selective disclosure of fields, availability sampling against the serving endpoint, and the version's AIP-23 envelope notarized and read back under a post-quantum certified anchor; not zero knowledge, and says so |
|
||||
| [`ai-trust/`](ai-trust/) | the trust chain of an AI result: the agent's identity (agents/), the model artifact's attestation, the host's runtime record (verify-layer/), the policy and signed ledger, the Proof of AI envelope and the chain as witness, joined in one bundle whose verifier checks only the links between them |
|
||||
| [`identity/`](identity/) | post-quantum credentials: an issuer signs claims (hybrid Ed25519 + ML-DSA-65, both required), the holder shows only the claims it picks (selective disclosure in the manner of SD-JWT, RFC 9901, in a format of its own), bound to the verifier's audience and nonce; delegation to a phone, an agent or a ten-minute session key that can only narrow; revocation and an issuer status list in the manner of W3C Bitstring Status List; all checked offline from the files; and compliance without surveillance: a verifier's policy (issuers, required claims) judged on a presentation, recorded as an AIP-23 envelope that carries no personal data; and the Travel Rule between VASPs, the IVMS101 data sealed for the receiving VASP with a hybrid X25519 + ML-KEM-768 key encapsulation, signed, bound to the transfer and acknowledged; and the same credentials as standard SD-JWT (IETF RFC 9901): issued with the issuer's Ed25519 key (EdDSA) or its ML-DSA-65 key, presented with a key binding JWT, and verified against the RFC's own example vectors (`identity-cli.mjs verify-sdjwt`, behind Aere Cloud's POST /v1/identity/sd-jwt/verify) |
|
||||
@ -41,6 +42,7 @@ test, counts as a failure of the control. Results measured on 2026-09-29 (Node.j
|
||||
| identity | 44/44 (`node proba-identity.mjs`, with the AIP-23 verifier for its envelope test; without it that test is reported as skipped and the exit code is 2), compliance 15/15 (`node proba-conformitate.mjs`), Travel Rule 19/19 (`node proba-travel-rule.mjs`), SD-JWT 23/23 (`node proba-sdjwt.mjs`, the RFC 9901 vectors included), measured 2026-09-30 | 49/49 (`node control-negativ-identity.mjs`); compliance 14/14 (`node control-negativ-conformitate.mjs`); Travel Rule 19/19 (`node control-negativ-travel-rule.mjs`); SD-JWT 28/28 (`node control-negativ-sdjwt.mjs`) |
|
||||
| agents | policy 27/27 with the AIP-23 verifier (without it 25 run, 2 are reported as skipped and the exit code is 2), ledger 51/51, approval and revocation 39/39, the chain as witness 26/26 without a network and 7/7 on testnet 28001 on 2026-09-30 (`proba-agent-ancora-testnet.mjs`, needs a funded testnet key and the AIP-23 verifier), command line 23/23 through files and processes only (on Linux and macOS one more test checks the key file mode; not measured here); x402 wallet 25/25, 2-of-2 co-signing 16/16 and payment verifier 14/14 without a network; on the public testnet 28001, 9/9 with the wallet key and 13/13 with the 2-of-2 contract wallet (`x402/proba-x402-testnet.mjs`, `x402/proba-cosign-testnet.mjs`, each needs a funded testnet key); the contract's artifact recompiles byte for byte with solc 0.8.23 (`node x402/recompileaza-contract.mjs --solc <solc>`) | 26/26 (`node control-negativ-aprobare.mjs`); the chain as witness 11/11 (`node control-negativ-ancora.mjs`); x402 30/30 (`node x402/control-negativ-wallet.mjs`); the contract's own tests (7) and their negative control (4/4) run in the Aere Network contracts project, not in this repository |
|
||||
| proof-of-ai | 6/6 with the AIP-23 verifier (`AERE_VERIFY_PROOF=... node proba-proof-of-ai.mjs`); without it the probe reports NEMASURAT and exits 2 | one negative control inside the test (an envelope touched after its hash: INVALID) |
|
||||
| market-settlement | 20/20 on testnet 28001 on 2026-10-02 (`AERE_TESTNET_KEY_FILE=... AERE_ETHERS_DIR=... node proba-piata-28001.mjs`: five scenarios on AereComputeMarketV3 deployed from the committed artifact, every refusal by its named custom error, the provider's notarized envelope read back under a post-quantum certified anchor); the judge on the recorded timelines 16/16 without a chain (`node proba-judecata.mjs`: five honest timelines VALID, ten forged ones INVALID for their named reason, the settlement receipt) | 7/7 (`node proba-judecata.mjs --control-negativ`: deadline, window, arbiter, payout, result bytes, replay, proof, each planted out on a copy and each forged fixture then accepted) |
|
||||
| verifiable-data | 22/22 with the AIP-23 verifier (`AERE_VERIFY_PROOF=... node proba-verifiable-data.mjs`: honest answers VALID, nine forged ones INVALID for their named reason, availability sampling on a local server; the envelope row is NEMASURAT without the verifier, exit 2); 24/24 with `--viu` on testnet 28001 on 2026-10-02 (a version notarized with a test key and read back under a post-quantum certified anchor; evidence in `dovezi/`) | 5/5 (`node proba-verifiable-data.mjs --control-negativ`: field commitment, index order, completeness, consistency, sampling, each planted out on a copy and each forged fixture then accepted) |
|
||||
| ai-trust | 13/13 with the AIP-23 verifier (`AERE_VERIFY_PROOF=... node proba-lant-de-incredere.mjs`: the whole chain on a committed fixture, then ten links broken on purpose, each refused for its named reason); without the verifier it exits 2 | 8/8 (`node proba-lant-de-incredere.mjs --control-negativ`) |
|
||||
|
||||
@ -51,4 +53,4 @@ interface, command line and data (`definePolicy`, `verifyLedger`, `approve`, ...
|
||||
|
||||
## Licence
|
||||
|
||||
MIT, see [LICENSE](LICENSE). Files: 174 (pq-gateway 6, pq-kms 10, pq-pki 6, crypto-inventory 42, verify-layer 12, proof-kinds 3, control-plane 19, agents 39, identity 15, proof-of-ai 3, ai-trust 6, verifiable-data 7, readiness 4).
|
||||
MIT, see [LICENSE](LICENSE). Files: 183 (pq-gateway 6, pq-kms 10, pq-pki 6, crypto-inventory 42, verify-layer 12, proof-kinds 3, control-plane 19, agents 39, identity 15, proof-of-ai 3, ai-trust 6, verifiable-data 7, market-settlement 9, readiness 4).
|
||||
|
||||
1458
market-settlement/AereComputeMarketV3.json
Normal file
1458
market-settlement/AereComputeMarketV3.json
Normal file
File diff suppressed because one or more lines are too long
50
market-settlement/README.md
Normal file
50
market-settlement/README.md
Normal file
@ -0,0 +1,50 @@
|
||||
# AERE Market Settlement
|
||||
|
||||
The dispute and settlement rail of a verifiable infrastructure market, measured end to end on testnet 28001: an order with its reward in
|
||||
escrow, a provider bond, the provider's result committed on chain and its proof notarized under a post-quantum certified anchor, a
|
||||
challenge window in which anyone can dispute with a recomputed result and a matching bond, an arbiter who can only route the escrow
|
||||
between the three parties (and a permissionless timeout if the arbiter never answers), a deadline after which an undelivered order is
|
||||
reclaimed, and an AIP-23 settlement receipt of the payout. Built from what already exists: `AereComputeMarketV3` (the contract, here
|
||||
as a committed artifact), `proof-kinds/` (the execution envelope and the settlement receipt), `agents/agent-ancora.mjs` (the one
|
||||
notarization call of the house) and the AIP-23 reference verifier.
|
||||
|
||||
| step | on chain | off chain, checkable by anyone |
|
||||
|---|---|---|
|
||||
| order | `postJob`: reward escrowed, `specHash` of the public workload, deadline, challenge window, required provider bond | the spec itself (`sarcina.mjs`: a keccak chain anyone recomputes) |
|
||||
| claim | `claimJob` with the bond, only before the deadline | |
|
||||
| proof | `submitResult`: `keccak256(result)` recorded, the bytes emitted; the window starts | the provider's `aere-proof-of-execution` envelope (program, `inputHash` = specHash, `outputHash` = resultHash) notarized on AereNotary: its `firstSeen` is the chain's time, read back under a post-quantum certified anchor by `verify-proof.mjs` |
|
||||
| dispute | `dispute(recomputedHash)` with a bond equal to the provider's, only inside the window | the judge replays the spec and says which side the arbiter should take |
|
||||
| settlement | `finalize` by anyone after the window (Paid); `resolveDispute` by the ARBITER (Paid or Slashed); `resolveByTimeout` by anyone after 14 days (Refunded, bonds returned); `reclaimExpired` by the requester after the deadline (Refunded, bond slashed); `cancelJob` on an open order | `piata.mjs judeca`: the timeline against the rules; `chitanta`: the AIP-23 `settlement` envelope of the payout tx |
|
||||
|
||||
`piata.mjs` has the three pieces a reader needs: `codulAsteptat` rebuilds the runtime code a market must have from the committed
|
||||
artifact and the four constructor arguments (so `eth_getCode` is compared before any event is believed), `cronologie` reads a job's
|
||||
state and every event with its block time and sender, and `judeca` is a pure function over that timeline: claim and submission before
|
||||
the deadline, dispute strictly inside the window, finalize only after it (unless the requester accepts early), resolution only by the
|
||||
arbiter or after `ARBITRATION_TIMEOUT`, the terminal payout matching the state, the result bytes hashing to the commitment, the replay of
|
||||
the public spec agreeing with whoever won, and the notarized envelope naming this job's spec and result before the window closed.
|
||||
|
||||
```
|
||||
AERE_TESTNET_KEY_FILE=<key> AERE_ETHERS_DIR=<dir with node_modules/ethers> node proba-piata-28001.mjs # the flow on 28001 (five scenarios)
|
||||
node proba-judecata.mjs # the judge on the recorded timelines: 5 honest VALID, 10 forged INVALID, the receipt
|
||||
node proba-judecata.mjs --control-negativ # 7 plantings on a copy of piata.mjs; each forged fixture must then be accepted
|
||||
node extrage-artefact.mjs # regenerate AereComputeMarketV3.json from the contracts build (refuses a changed source)
|
||||
```
|
||||
|
||||
## What it is not, and what is not measured
|
||||
|
||||
- **Not a marketplace.** No discovery, no pricing, no reputation, no real GPU, storage or inference behind the workload; the task is a
|
||||
keccak chain chosen because anyone can replay it. The rail settles; it does not find providers.
|
||||
- **The arbiter is one key.** A dispute is decided by `ARBITER`, who can only route the escrow between requester, provider and
|
||||
challenger, never elsewhere, and whose silence is unwound by anyone after 14 days. That is a trust assumption, named, not a proof;
|
||||
the judge checks the arbiter's decision against the replay afterwards, which makes a wrong ruling visible, not impossible.
|
||||
- **REPLAY mode only.** `ZK_VERIFIED` (pay only on an SP1 proof) cannot be exercised on this deployment: 28001 has no SP1 gateway, so the
|
||||
market was built with a contract that has code but no `verifyProof` (the notary), and a ZK job reverts, fail-closed. `pqcSettlement`
|
||||
(a Falcon-512 payout authorization) is not exercised either. The 14-day arbitration timeout and the 7-day PQC grace are not waited for:
|
||||
the probe shows they refuse early (`ArbitrationTimeoutNotPassed`), not that they unwind.
|
||||
- **Native tAERE only.** An allowlisted ERC-20 reward and the 2-of-2 agent wallet as requester are not measured: the contract wallet has
|
||||
no function that calls `postJob`, so an agent that orders through this rail does it from its EOA, under its ledger and policy.
|
||||
- **Chain 2800: nothing.** `AereComputeMarketV3` is held back from mainnet until an external audit of fund-flow contracts; everything here
|
||||
ran on testnet 28001 with test keys (the provider's and the arbiter's derived from the requester's test key, never printed).
|
||||
- The replay uses a dependency-free keccak-256 (BigInt): about 7 s for the 20,000 iterations of the laboratory task on a laptop, against well under a second with a native or WebAssembly keccak; the cost is the price of a component with nothing to install, and it is measured, not hidden.
|
||||
- The judge trusts the RPC for the timeline (events and block times), like every reader of a chain through an endpoint; the notarized
|
||||
envelope's time is checked under a post-quantum certified anchor by the reference verifier, the rest is not.
|
||||
14
market-settlement/desfasurare-28001.json
Normal file
14
market-settlement/desfasurare-28001.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"chainId": 28001,
|
||||
"market": "0x4145d6f639436d840957F2D4B9f2e73f34F56B5D",
|
||||
"deployTx": "0x8f45502e40a71d45550756fdcb3e4ed3af0532134212704f161fd2c4004d8ae2",
|
||||
"block": 4445446,
|
||||
"args": {
|
||||
"ZK_VERIFIER": "0x70099E62735500AA2F85B60C518551a57B202d54",
|
||||
"PINNED_PROOF_SELECTOR": "0x4388a21c",
|
||||
"ARBITER": "0x5521aC7371Afdbf6BE986B46Da4116fb256d9e58",
|
||||
"GOVERNANCE": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF"
|
||||
},
|
||||
"artifactSourceSha256": "0x6db4d53dffb0afd79cfb41f973ac4d2ca82589ab7bdd206296b66b4c72dd4fe4",
|
||||
"at": "2026-10-02T16:40:39.149Z"
|
||||
}
|
||||
815
market-settlement/dovezi/piata-28001-2026-10-02T1652Z.json
Normal file
815
market-settlement/dovezi/piata-28001-2026-10-02T1652Z.json
Normal file
@ -0,0 +1,815 @@
|
||||
{
|
||||
"la": "2026-10-02T16:46:56.361Z",
|
||||
"chainId": 28001,
|
||||
"rpc": "https://testnet-rpc.aere.network",
|
||||
"fereastra": 90,
|
||||
"scenarii": {
|
||||
"S1": {
|
||||
"spec": {
|
||||
"task": "keccak-chain",
|
||||
"seed": "0xbfcf8dae6e6f105b4e7ead898a4fdb0319745deb3d38042e3815d75805e1060d",
|
||||
"iters": 20000
|
||||
},
|
||||
"pasi": [],
|
||||
"jobId": 5,
|
||||
"computeMs": 597,
|
||||
"envelope": {
|
||||
"v": 1,
|
||||
"kind": "aere-proof-of-execution-attestation",
|
||||
"statement": {
|
||||
"v": 1,
|
||||
"kind": "aere-proof-of-execution",
|
||||
"inputHash": "0xf2a0d3e03521234fd6be921e346817e6f77e20db038ff5325318a2a5a47cfd25",
|
||||
"outputHash": "0xeba5b6ff363f15d358926ab16bc4c7c8c979ec2d217868134810488304dde0be",
|
||||
"programSha256": "0x1824b9e133c368fab195cb2f33e35bee78abdb4de7b03f0c377ac5af951eb9e0",
|
||||
"program": "keccak-chain",
|
||||
"exitCode": 0,
|
||||
"createdAt": "2026-10-02T16:47:11.789Z"
|
||||
},
|
||||
"statementHash": "0xfffa43d50b6222a17f915d528f0f636b16882f736f357b8418d30dfec2f88200",
|
||||
"notarization": {
|
||||
"chainId": 28001,
|
||||
"notary": "0x70099E62735500AA2F85B60C518551a57B202d54",
|
||||
"firstSeen": 1790959633,
|
||||
"txHash": "0x86f094ae0ba1030b97060792344eda1b6018367dbf14088c6c2de1e5a4e2fc6e",
|
||||
"block": 4446125
|
||||
}
|
||||
},
|
||||
"date": {
|
||||
"chainId": 28001,
|
||||
"market": "0x4145d6f639436d840957F2D4B9f2e73f34F56B5D",
|
||||
"jobId": "5",
|
||||
"constants": {
|
||||
"arbiter": "0x5521aC7371Afdbf6BE986B46Da4116fb256d9e58",
|
||||
"governance": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"arbitrationTimeout": 1209600,
|
||||
"zkVerifier": "0x70099E62735500AA2F85B60C518551a57B202d54",
|
||||
"pinnedProofSelector": "0x4388a21c"
|
||||
},
|
||||
"job": {
|
||||
"requester": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"provider": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "0",
|
||||
"providerBond": "0",
|
||||
"specHash": "0xf2a0d3e03521234fd6be921e346817e6f77e20db038ff5325318a2a5a47cfd25",
|
||||
"resultHash": "0xeba5b6ff363f15d358926ab16bc4c7c8c979ec2d217868134810488304dde0be",
|
||||
"zkProgramVKey": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"mode": 0,
|
||||
"status": 5,
|
||||
"pqcSettlement": false,
|
||||
"offchainResult": false,
|
||||
"deadline": 1790960218,
|
||||
"challengeWindow": 90,
|
||||
"submittedAt": 1790959639,
|
||||
"disputedAt": 0
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"name": "JobPosted",
|
||||
"args": {
|
||||
"id": "5",
|
||||
"requester": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "10000000000000000",
|
||||
"mode": "0",
|
||||
"specHash": "0xf2a0d3e03521234fd6be921e346817e6f77e20db038ff5325318a2a5a47cfd25",
|
||||
"deadline": "1790960218",
|
||||
"pqcSettlement": false
|
||||
},
|
||||
"blockNumber": 4446103,
|
||||
"blockHash": "0xfc22d0155293bef0b5349507a2a45495de7d3baa37b1f9e5e482585d93ecdc3e",
|
||||
"timestamp": 1790959620,
|
||||
"txHash": "0x5178c7b523d9ad5516f5e649f50bc98c44aacd6c4564403ac6b7528a9e63791e",
|
||||
"from": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "JobClaimed",
|
||||
"args": {
|
||||
"id": "5",
|
||||
"provider": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"providerBond": "4000000000000000"
|
||||
},
|
||||
"blockNumber": 4446113,
|
||||
"blockHash": "0xd59b44a60e225a952c0ef940a62f6ebe7325dc4d75627161c329aa6f48bc9ce4",
|
||||
"timestamp": 1790959626,
|
||||
"txHash": "0xf46a947200113d014c3e8b0282267feb2882f0d8895ae4b13a72f1db20fa7ee3",
|
||||
"from": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "ResultSubmitted",
|
||||
"args": {
|
||||
"id": "5",
|
||||
"resultHash": "0xeba5b6ff363f15d358926ab16bc4c7c8c979ec2d217868134810488304dde0be",
|
||||
"result": "0x7b22646967657374223a22307864346139306165303264396364623232353863373239633532636166613662613263623164306539323532326135313739313733383362366535386131323535222c226974657273223a32303030302c2273656564223a22307862666366386461653665366631303562346537656164383938613466646230333139373435646562336433383034326533383135643735383035653130363064222c227461736b223a226b656363616b2d636861696e227d"
|
||||
},
|
||||
"blockNumber": 4446136,
|
||||
"blockHash": "0x7cedaf1f5da474b94c41fd0925f181459ff642fa449843886d94226daaf2650f",
|
||||
"timestamp": 1790959639,
|
||||
"txHash": "0xea128ff1992e39f6690ebfbfbd9896ff27bcea0db4b0070c32062d2de599d446",
|
||||
"from": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "JobPaid",
|
||||
"args": {
|
||||
"id": "5",
|
||||
"provider": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "10000000000000000",
|
||||
"bondReturned": "4000000000000000"
|
||||
},
|
||||
"blockNumber": 4446295,
|
||||
"blockHash": "0x736c818d1833f0c88ccda5ec5101d392fd842af32a6b152018a085c7a75a7c5c",
|
||||
"timestamp": 1790959733,
|
||||
"txHash": "0x5e4bfedda879e4e016b7f4434f75da4a8af63ccc4cce285d714b9e56e453e65f",
|
||||
"from": "0x5521aC7371Afdbf6BE986B46Da4116fb256d9e58",
|
||||
"logIndex": 0
|
||||
}
|
||||
],
|
||||
"result": "0x7b22646967657374223a22307864346139306165303264396364623232353863373239633532636166613662613263623164306539323532326135313739313733383362366535386131323535222c226974657273223a32303030302c2273656564223a22307862666366386461653665366631303562346537656164383938613466646230333139373435646562336433383034326533383135643735383035653130363064222c227461736b223a226b656363616b2d636861696e227d",
|
||||
"spec": {
|
||||
"task": "keccak-chain",
|
||||
"seed": "0xbfcf8dae6e6f105b4e7ead898a4fdb0319745deb3d38042e3815d75805e1060d",
|
||||
"iters": 20000
|
||||
},
|
||||
"envelope": {
|
||||
"v": 1,
|
||||
"kind": "aere-proof-of-execution-attestation",
|
||||
"statement": {
|
||||
"v": 1,
|
||||
"kind": "aere-proof-of-execution",
|
||||
"inputHash": "0xf2a0d3e03521234fd6be921e346817e6f77e20db038ff5325318a2a5a47cfd25",
|
||||
"outputHash": "0xeba5b6ff363f15d358926ab16bc4c7c8c979ec2d217868134810488304dde0be",
|
||||
"programSha256": "0x1824b9e133c368fab195cb2f33e35bee78abdb4de7b03f0c377ac5af951eb9e0",
|
||||
"program": "keccak-chain",
|
||||
"exitCode": 0,
|
||||
"createdAt": "2026-10-02T16:47:11.789Z"
|
||||
},
|
||||
"statementHash": "0xfffa43d50b6222a17f915d528f0f636b16882f736f357b8418d30dfec2f88200",
|
||||
"notarization": {
|
||||
"chainId": 28001,
|
||||
"notary": "0x70099E62735500AA2F85B60C518551a57B202d54",
|
||||
"firstSeen": 1790959633,
|
||||
"txHash": "0x86f094ae0ba1030b97060792344eda1b6018367dbf14088c6c2de1e5a4e2fc6e",
|
||||
"block": 4446125
|
||||
}
|
||||
}
|
||||
},
|
||||
"judecata": {
|
||||
"verdict": "VALID",
|
||||
"motive": [],
|
||||
"randuri": [
|
||||
"2026-10-02T16:47:00.000Z #4446103 JobPosted by 0x86D6C684.. id=5 requester=0x86D6C684.. token=0x00000000.. reward=10000000000000000 mode=0 specHash=0xf2a0d3e0.. deadline=1790960218 pqcSettlement=false",
|
||||
"2026-10-02T16:47:06.000Z #4446113 JobClaimed by 0xeC2261CE.. id=5 provider=0xeC2261CE.. providerBond=4000000000000000",
|
||||
"2026-10-02T16:47:19.000Z #4446136 ResultSubmitted by 0xeC2261CE.. id=5 resultHash=0xeba5b6ff..",
|
||||
"2026-10-02T16:48:53.000Z #4446295 JobPaid by 0x5521aC73.. id=5 provider=0xeC2261CE.. token=0x00000000.. reward=10000000000000000 bondReturned=4000000000000000"
|
||||
],
|
||||
"status": "Paid",
|
||||
"terminal": {
|
||||
"status": "Paid",
|
||||
"payee": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"amount": "10000000000000000",
|
||||
"txHash": "0x5e4bfedda879e4e016b7f4434f75da4a8af63ccc4cce285d714b9e56e453e65f",
|
||||
"blockHash": "0x736c818d1833f0c88ccda5ec5101d392fd842af32a6b152018a085c7a75a7c5c"
|
||||
},
|
||||
"replay": {
|
||||
"recomputedHash": "0xeba5b6ff363f15d358926ab16bc4c7c8c979ec2d217868134810488304dde0be",
|
||||
"providerRight": true
|
||||
}
|
||||
},
|
||||
"verifyProof": {
|
||||
"verdict": "VALID",
|
||||
"statementHash": "0xfffa43d50b6222a17f915d528f0f636b16882f736f357b8418d30dfec2f88200",
|
||||
"levels": [
|
||||
{
|
||||
"level": "form",
|
||||
"state": "PASSED",
|
||||
"detail": "kind=aere-proof-of-execution-attestation"
|
||||
},
|
||||
{
|
||||
"level": "integrity",
|
||||
"state": "PASSED",
|
||||
"detail": "sha256(statement) == statementHash (0xfffa43d50b6222a1..)"
|
||||
},
|
||||
{
|
||||
"level": "signature",
|
||||
"state": "ABSENT",
|
||||
"detail": "the envelope carries no signature"
|
||||
},
|
||||
{
|
||||
"level": "finality",
|
||||
"state": "PASSED",
|
||||
"detail": "post-quantum: the state of block 4446303, the parent of anchor 4446304 whose post-quantum certificate verified here records this statementHash in AereNotary 0x70099e62735500aa2f85b60c518551a57b202d54 as first seen at 2026-10-02T16:47:13.000Z (1790959633); first seen in block 4446125, transaction 0x86f094ae0ba1030b.. (located through the endpoint's logs, not part of what is certified)"
|
||||
}
|
||||
]
|
||||
},
|
||||
"chitanta": {
|
||||
"v": 1,
|
||||
"kind": "aere-proof-of-settlement-attestation",
|
||||
"statement": {
|
||||
"v": 1,
|
||||
"kind": "aere-proof-of-settlement",
|
||||
"instructionHash": "0xacad5a190c81f60a3051a56f2a7fad505abbbe86da6ffd130fc7f2f8964eceb9",
|
||||
"txHash": "0x5e4bfedda879e4e016b7f4434f75da4a8af63ccc4cce285d714b9e56e453e65f",
|
||||
"blockHash": "0x736c818d1833f0c88ccda5ec5101d392fd842af32a6b152018a085c7a75a7c5c",
|
||||
"chainId": 28001,
|
||||
"from": "0x4145d6f639436d840957F2D4B9f2e73f34F56B5D",
|
||||
"to": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"amount": "10000000000000000",
|
||||
"asset": "native",
|
||||
"createdAt": "2026-10-02T16:49:13.443Z"
|
||||
},
|
||||
"statementHash": "0xab6c8e8876816ff3fbaef7b3fc364ad3b4e94b98116ede3fdb982bff3b886786"
|
||||
},
|
||||
"chitantaVerdict": {
|
||||
"verdict": "VALID",
|
||||
"statementHash": "0xab6c8e8876816ff3fbaef7b3fc364ad3b4e94b98116ede3fdb982bff3b886786",
|
||||
"levels": [
|
||||
{
|
||||
"level": "form",
|
||||
"state": "PASSED",
|
||||
"detail": "kind=aere-proof-of-settlement-attestation"
|
||||
},
|
||||
{
|
||||
"level": "integrity",
|
||||
"state": "PASSED",
|
||||
"detail": "sha256(statement) == statementHash (0xab6c8e8876816ff3..)"
|
||||
},
|
||||
{
|
||||
"level": "signature",
|
||||
"state": "ABSENT",
|
||||
"detail": "the envelope carries no signature"
|
||||
},
|
||||
{
|
||||
"level": "finality",
|
||||
"state": "ABSENT",
|
||||
"detail": "no notarization claimed and no --rpc given"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"S2": {
|
||||
"spec": {
|
||||
"task": "keccak-chain",
|
||||
"seed": "0x5de2558627fcce6d953b1a70358f167c9b364fc944a45fe0741abc0c0b8d42ce",
|
||||
"iters": 20000
|
||||
},
|
||||
"pasi": [],
|
||||
"jobId": 6,
|
||||
"date": {
|
||||
"chainId": 28001,
|
||||
"market": "0x4145d6f639436d840957F2D4B9f2e73f34F56B5D",
|
||||
"jobId": "6",
|
||||
"constants": {
|
||||
"arbiter": "0x5521aC7371Afdbf6BE986B46Da4116fb256d9e58",
|
||||
"governance": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"arbitrationTimeout": 1209600,
|
||||
"zkVerifier": "0x70099E62735500AA2F85B60C518551a57B202d54",
|
||||
"pinnedProofSelector": "0x4388a21c"
|
||||
},
|
||||
"job": {
|
||||
"requester": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"provider": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "0",
|
||||
"providerBond": "0",
|
||||
"specHash": "0x307e7d75c2de86416fd1df625fd5c00a12a4ff6257bea57871de8c7d47cf5a89",
|
||||
"resultHash": "0x2dea74737284c05166223abbb28b3eaf367e8964a5a12bffa0ab4bd1e2b300ae",
|
||||
"zkProgramVKey": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"mode": 0,
|
||||
"status": 7,
|
||||
"pqcSettlement": false,
|
||||
"offchainResult": false,
|
||||
"deadline": 1790960353,
|
||||
"challengeWindow": 90,
|
||||
"submittedAt": 1790959767,
|
||||
"disputedAt": 1790959773
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"name": "JobPosted",
|
||||
"args": {
|
||||
"id": "6",
|
||||
"requester": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "10000000000000000",
|
||||
"mode": "0",
|
||||
"specHash": "0x307e7d75c2de86416fd1df625fd5c00a12a4ff6257bea57871de8c7d47cf5a89",
|
||||
"deadline": "1790960353",
|
||||
"pqcSettlement": false
|
||||
},
|
||||
"blockNumber": 4446332,
|
||||
"blockHash": "0x373ef4cd4095e1db711ba4748c2a5bd2d78a26dd7892a98c14929aeb0b62cf1f",
|
||||
"timestamp": 1790959755,
|
||||
"txHash": "0xdf242547d54507d84bb9ac8d91ce24b65529d17940bd95504f5267ee74bdd92a",
|
||||
"from": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "JobClaimed",
|
||||
"args": {
|
||||
"id": "6",
|
||||
"provider": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"providerBond": "4000000000000000"
|
||||
},
|
||||
"blockNumber": 4446342,
|
||||
"blockHash": "0xa81e65065cd3c472cc571c64035031c66e975906a92e5b88acba4a6e08c9d7a1",
|
||||
"timestamp": 1790959761,
|
||||
"txHash": "0xfcfc913a260d29b519276a484199d372ed39aac81005166e5231025bf948d35b",
|
||||
"from": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "ResultSubmitted",
|
||||
"args": {
|
||||
"id": "6",
|
||||
"resultHash": "0x2dea74737284c05166223abbb28b3eaf367e8964a5a12bffa0ab4bd1e2b300ae",
|
||||
"result": "0x7b22646967657374223a22307865656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565222c226974657273223a32303030302c2273656564223a22307835646532353538363237666363653664393533623161373033353866313637633962333634666339343461343566653037343161626330633062386434326365222c227461736b223a226b656363616b2d636861696e227d"
|
||||
},
|
||||
"blockNumber": 4446353,
|
||||
"blockHash": "0xd4d23bd3f6d3883330ff25de0a81696c0e9ba473c5457b918c5aa82e55601777",
|
||||
"timestamp": 1790959767,
|
||||
"txHash": "0x5f921a8d67de79bfcfd4d77085fa220d5ecc4191631a7e4dc050e003e612c6c6",
|
||||
"from": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "JobDisputed",
|
||||
"args": {
|
||||
"id": "6",
|
||||
"challenger": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"bond": "4000000000000000",
|
||||
"recomputedResultHash": "0xb37eb93d1b69a562f94bb2de482a0a4dd3e04493d3020cb29eeef8df82c713c3"
|
||||
},
|
||||
"blockNumber": 4446364,
|
||||
"blockHash": "0xf6a3e7e6f51312f624cbed5251b0c5abe05f9bd2b1d255ec8fe65114d408311c",
|
||||
"timestamp": 1790959773,
|
||||
"txHash": "0x72c54a067ebf1341a588db66f61223a28d7f6977bc60ab8eff5e59e008ddf8de",
|
||||
"from": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "JobSlashed",
|
||||
"args": {
|
||||
"id": "6",
|
||||
"challenger": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"slashedProviderBond": "4000000000000000"
|
||||
},
|
||||
"blockNumber": 4446375,
|
||||
"blockHash": "0xf5aa67bf1e48b8fc8a8ff44b794b0f9cd501f45d10b7979b7b5fdc37e9d70a9a",
|
||||
"timestamp": 1790959780,
|
||||
"txHash": "0x7b1916485981fef836d5f43c3d8c4528678931f78d4b3110859198c630e5e69f",
|
||||
"from": "0x5521aC7371Afdbf6BE986B46Da4116fb256d9e58",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "DisputeResolved",
|
||||
"args": {
|
||||
"id": "6",
|
||||
"providerWon": false
|
||||
},
|
||||
"blockNumber": 4446375,
|
||||
"blockHash": "0xf5aa67bf1e48b8fc8a8ff44b794b0f9cd501f45d10b7979b7b5fdc37e9d70a9a",
|
||||
"timestamp": 1790959780,
|
||||
"txHash": "0x7b1916485981fef836d5f43c3d8c4528678931f78d4b3110859198c630e5e69f",
|
||||
"from": "0x5521aC7371Afdbf6BE986B46Da4116fb256d9e58",
|
||||
"logIndex": 1
|
||||
}
|
||||
],
|
||||
"result": "0x7b22646967657374223a22307865656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565222c226974657273223a32303030302c2273656564223a22307835646532353538363237666363653664393533623161373033353866313637633962333634666339343461343566653037343161626330633062386434326365222c227461736b223a226b656363616b2d636861696e227d",
|
||||
"spec": {
|
||||
"task": "keccak-chain",
|
||||
"seed": "0x5de2558627fcce6d953b1a70358f167c9b364fc944a45fe0741abc0c0b8d42ce",
|
||||
"iters": 20000
|
||||
},
|
||||
"envelope": null
|
||||
},
|
||||
"judecata": {
|
||||
"verdict": "VALID",
|
||||
"motive": [],
|
||||
"randuri": [
|
||||
"2026-10-02T16:49:15.000Z #4446332 JobPosted by 0x86D6C684.. id=6 requester=0x86D6C684.. token=0x00000000.. reward=10000000000000000 mode=0 specHash=0x307e7d75.. deadline=1790960353 pqcSettlement=false",
|
||||
"2026-10-02T16:49:21.000Z #4446342 JobClaimed by 0xeC2261CE.. id=6 provider=0xeC2261CE.. providerBond=4000000000000000",
|
||||
"2026-10-02T16:49:27.000Z #4446353 ResultSubmitted by 0xeC2261CE.. id=6 resultHash=0x2dea7473..",
|
||||
"2026-10-02T16:49:33.000Z #4446364 JobDisputed by 0x86D6C684.. id=6 challenger=0x86D6C684.. bond=4000000000000000 recomputedResultHash=0xb37eb93d..",
|
||||
"2026-10-02T16:49:40.000Z #4446375 JobSlashed by 0x5521aC73.. id=6 challenger=0x86D6C684.. slashedProviderBond=4000000000000000",
|
||||
"2026-10-02T16:49:40.000Z #4446375 DisputeResolved by 0x5521aC73.. id=6 providerWon=false"
|
||||
],
|
||||
"status": "Slashed",
|
||||
"terminal": {
|
||||
"status": "Slashed",
|
||||
"payee": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"amount": "4000000000000000",
|
||||
"refundedTo": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"txHash": "0x7b1916485981fef836d5f43c3d8c4528678931f78d4b3110859198c630e5e69f",
|
||||
"blockHash": "0xf5aa67bf1e48b8fc8a8ff44b794b0f9cd501f45d10b7979b7b5fdc37e9d70a9a"
|
||||
},
|
||||
"replay": {
|
||||
"recomputedHash": "0xb37eb93d1b69a562f94bb2de482a0a4dd3e04493d3020cb29eeef8df82c713c3",
|
||||
"providerRight": false,
|
||||
"challengerRecomputedRight": true
|
||||
}
|
||||
},
|
||||
"gasResolve": "70471"
|
||||
},
|
||||
"S3": {
|
||||
"spec": {
|
||||
"task": "keccak-chain",
|
||||
"seed": "0x53c2cdb3e3e6a740c9fd8800cca9d50ec84681b76f5547c2536090db7624b09d",
|
||||
"iters": 20000
|
||||
},
|
||||
"pasi": [],
|
||||
"jobId": 7,
|
||||
"date": {
|
||||
"chainId": 28001,
|
||||
"market": "0x4145d6f639436d840957F2D4B9f2e73f34F56B5D",
|
||||
"jobId": "7",
|
||||
"constants": {
|
||||
"arbiter": "0x5521aC7371Afdbf6BE986B46Da4116fb256d9e58",
|
||||
"governance": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"arbitrationTimeout": 1209600,
|
||||
"zkVerifier": "0x70099E62735500AA2F85B60C518551a57B202d54",
|
||||
"pinnedProofSelector": "0x4388a21c"
|
||||
},
|
||||
"job": {
|
||||
"requester": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"provider": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "0",
|
||||
"providerBond": "0",
|
||||
"specHash": "0x33d4d459ac50fbf9089cfbbdf51416f7f9d8fd8794f03f4788bef4b679be0e06",
|
||||
"resultHash": "0x20595189a5aae95406d466972f780d5b04408146e30afdcc66449836a67b240a",
|
||||
"zkProgramVKey": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"mode": 0,
|
||||
"status": 5,
|
||||
"pqcSettlement": false,
|
||||
"offchainResult": false,
|
||||
"deadline": 1790960390,
|
||||
"challengeWindow": 90,
|
||||
"submittedAt": 1790959804,
|
||||
"disputedAt": 1790959809
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"name": "JobPosted",
|
||||
"args": {
|
||||
"id": "7",
|
||||
"requester": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "10000000000000000",
|
||||
"mode": "0",
|
||||
"specHash": "0x33d4d459ac50fbf9089cfbbdf51416f7f9d8fd8794f03f4788bef4b679be0e06",
|
||||
"deadline": "1790960390",
|
||||
"pqcSettlement": false
|
||||
},
|
||||
"blockNumber": 4446395,
|
||||
"blockHash": "0xdaec8217b35341244eb65c7a06bc6d817c44888c0e2c673d2278091e7c308a83",
|
||||
"timestamp": 1790959791,
|
||||
"txHash": "0x8c0ab87432b8e3e4b8a53f9ef3fd9d217e214f106ff637368ab75f0ed68c300d",
|
||||
"from": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "JobClaimed",
|
||||
"args": {
|
||||
"id": "7",
|
||||
"provider": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"providerBond": "4000000000000000"
|
||||
},
|
||||
"blockNumber": 4446404,
|
||||
"blockHash": "0x31141aeb86c3bc3f7f3e0b493aa69a2a4d2e1952c09181f2ed3bc8e32b062c97",
|
||||
"timestamp": 1790959797,
|
||||
"txHash": "0x78e05e10e9b9aca97b0e26ce9e868dd017b57fcce4248bf65701b06ed44d1d1a",
|
||||
"from": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "ResultSubmitted",
|
||||
"args": {
|
||||
"id": "7",
|
||||
"resultHash": "0x20595189a5aae95406d466972f780d5b04408146e30afdcc66449836a67b240a",
|
||||
"result": "0x7b22646967657374223a22307838613832333664613464396231373164613863643538643832373738376535656539323739316533643262353665313537383163313938336363326464396531222c226974657273223a32303030302c2273656564223a22307835336332636462336533653661373430633966643838303063636139643530656338343638316237366635353437633235333630393064623736323462303964222c227461736b223a226b656363616b2d636861696e227d"
|
||||
},
|
||||
"blockNumber": 4446415,
|
||||
"blockHash": "0x27d0fdbd6383be65aa8549cae7f87436255111ccb57597f92f0eb78a8c568d20",
|
||||
"timestamp": 1790959804,
|
||||
"txHash": "0xf4b8f42fa5cad3827bbe234348b687dfec24d0b3e55f5ae162d66891239353e8",
|
||||
"from": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "JobDisputed",
|
||||
"args": {
|
||||
"id": "7",
|
||||
"challenger": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"bond": "4000000000000000",
|
||||
"recomputedResultHash": "0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
|
||||
},
|
||||
"blockNumber": 4446425,
|
||||
"blockHash": "0x42ff0ad6b3187d212824c078f5cbb09cae1f67e5d762b1500053d34188da73e4",
|
||||
"timestamp": 1790959809,
|
||||
"txHash": "0x8595234a75d21301720a71505d4aff1046b8a73171da9bf24d245b5512911cd2",
|
||||
"from": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "JobPaid",
|
||||
"args": {
|
||||
"id": "7",
|
||||
"provider": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "10000000000000000",
|
||||
"bondReturned": "4000000000000000"
|
||||
},
|
||||
"blockNumber": 4446436,
|
||||
"blockHash": "0x0e01a68f00de85f42a89054e785755699653ca065746982b153565be3051c2fd",
|
||||
"timestamp": 1790959815,
|
||||
"txHash": "0x7eb96d3a9703668a275d233daeb829435e6eb08fdb30f9179f0ed806b2926e8e",
|
||||
"from": "0x5521aC7371Afdbf6BE986B46Da4116fb256d9e58",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "DisputeResolved",
|
||||
"args": {
|
||||
"id": "7",
|
||||
"providerWon": true
|
||||
},
|
||||
"blockNumber": 4446436,
|
||||
"blockHash": "0x0e01a68f00de85f42a89054e785755699653ca065746982b153565be3051c2fd",
|
||||
"timestamp": 1790959815,
|
||||
"txHash": "0x7eb96d3a9703668a275d233daeb829435e6eb08fdb30f9179f0ed806b2926e8e",
|
||||
"from": "0x5521aC7371Afdbf6BE986B46Da4116fb256d9e58",
|
||||
"logIndex": 1
|
||||
}
|
||||
],
|
||||
"result": "0x7b22646967657374223a22307838613832333664613464396231373164613863643538643832373738376535656539323739316533643262353665313537383163313938336363326464396531222c226974657273223a32303030302c2273656564223a22307835336332636462336533653661373430633966643838303063636139643530656338343638316237366635353437633235333630393064623736323462303964222c227461736b223a226b656363616b2d636861696e227d",
|
||||
"spec": {
|
||||
"task": "keccak-chain",
|
||||
"seed": "0x53c2cdb3e3e6a740c9fd8800cca9d50ec84681b76f5547c2536090db7624b09d",
|
||||
"iters": 20000
|
||||
},
|
||||
"envelope": null
|
||||
},
|
||||
"judecata": {
|
||||
"verdict": "VALID",
|
||||
"motive": [],
|
||||
"randuri": [
|
||||
"2026-10-02T16:49:51.000Z #4446395 JobPosted by 0x86D6C684.. id=7 requester=0x86D6C684.. token=0x00000000.. reward=10000000000000000 mode=0 specHash=0x33d4d459.. deadline=1790960390 pqcSettlement=false",
|
||||
"2026-10-02T16:49:57.000Z #4446404 JobClaimed by 0xeC2261CE.. id=7 provider=0xeC2261CE.. providerBond=4000000000000000",
|
||||
"2026-10-02T16:50:04.000Z #4446415 ResultSubmitted by 0xeC2261CE.. id=7 resultHash=0x20595189..",
|
||||
"2026-10-02T16:50:09.000Z #4446425 JobDisputed by 0x86D6C684.. id=7 challenger=0x86D6C684.. bond=4000000000000000 recomputedResultHash=0xdddddddd..",
|
||||
"2026-10-02T16:50:15.000Z #4446436 JobPaid by 0x5521aC73.. id=7 provider=0xeC2261CE.. token=0x00000000.. reward=10000000000000000 bondReturned=4000000000000000",
|
||||
"2026-10-02T16:50:15.000Z #4446436 DisputeResolved by 0x5521aC73.. id=7 providerWon=true"
|
||||
],
|
||||
"status": "Paid",
|
||||
"terminal": {
|
||||
"status": "Paid",
|
||||
"payee": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"amount": "10000000000000000",
|
||||
"txHash": "0x7eb96d3a9703668a275d233daeb829435e6eb08fdb30f9179f0ed806b2926e8e",
|
||||
"blockHash": "0x0e01a68f00de85f42a89054e785755699653ca065746982b153565be3051c2fd"
|
||||
},
|
||||
"replay": {
|
||||
"recomputedHash": "0x20595189a5aae95406d466972f780d5b04408146e30afdcc66449836a67b240a",
|
||||
"providerRight": true,
|
||||
"challengerRecomputedRight": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"S4": {
|
||||
"spec": {
|
||||
"task": "keccak-chain",
|
||||
"seed": "0xd2356607ad7de44861e4098fc8de3628ab454527539afeb302e6cbc03450d500",
|
||||
"iters": 20000
|
||||
},
|
||||
"pasi": [],
|
||||
"jobId": 8,
|
||||
"date": {
|
||||
"chainId": 28001,
|
||||
"market": "0x4145d6f639436d840957F2D4B9f2e73f34F56B5D",
|
||||
"jobId": "8",
|
||||
"constants": {
|
||||
"arbiter": "0x5521aC7371Afdbf6BE986B46Da4116fb256d9e58",
|
||||
"governance": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"arbitrationTimeout": 1209600,
|
||||
"zkVerifier": "0x70099E62735500AA2F85B60C518551a57B202d54",
|
||||
"pinnedProofSelector": "0x4388a21c"
|
||||
},
|
||||
"job": {
|
||||
"requester": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"provider": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "0",
|
||||
"providerBond": "0",
|
||||
"specHash": "0xe39172c8a9a6c56862bc48d25c2b20f56c585d165d8140b9cd18da9784f1f5cd",
|
||||
"resultHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"zkProgramVKey": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"mode": 0,
|
||||
"status": 6,
|
||||
"pqcSettlement": false,
|
||||
"offchainResult": false,
|
||||
"deadline": 1790959871,
|
||||
"challengeWindow": 90,
|
||||
"submittedAt": 0,
|
||||
"disputedAt": 0
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"name": "JobPosted",
|
||||
"args": {
|
||||
"id": "8",
|
||||
"requester": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "10000000000000000",
|
||||
"mode": "0",
|
||||
"specHash": "0xe39172c8a9a6c56862bc48d25c2b20f56c585d165d8140b9cd18da9784f1f5cd",
|
||||
"deadline": "1790959871",
|
||||
"pqcSettlement": false
|
||||
},
|
||||
"blockNumber": 4446456,
|
||||
"blockHash": "0x4f912d1702c83e1c6fdd864b75471c8f30a69f37d8decdbf121415285c94492f",
|
||||
"timestamp": 1790959827,
|
||||
"txHash": "0x2129c617c59cf4fce4f0bb03cfd4d1d97ff2ea256be637f553dd63093dc2cee9",
|
||||
"from": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "JobClaimed",
|
||||
"args": {
|
||||
"id": "8",
|
||||
"provider": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"providerBond": "4000000000000000"
|
||||
},
|
||||
"blockNumber": 4446459,
|
||||
"blockHash": "0x5e3fb425890cf5d8e65b0992db0b872cb06b62df2d3dbe404b1e73b805891df6",
|
||||
"timestamp": 1790959829,
|
||||
"txHash": "0x351e7248fc153fd6f46f1d5fd2480ec6428b1ce351b35232b9099d5e1cd6aec3",
|
||||
"from": "0xeC2261CECBB06768F6db095822b429AF5c861E2e",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "JobRefunded",
|
||||
"args": {
|
||||
"id": "8",
|
||||
"requester": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "10000000000000000"
|
||||
},
|
||||
"blockNumber": 4446540,
|
||||
"blockHash": "0x269e3df7ecdcc85db01e702814eb766d235233eee90c0182179ddf7307c32cab",
|
||||
"timestamp": 1790959877,
|
||||
"txHash": "0xf041b9338843a0727acca19c794d940ec7d13df17dd17d88cc1c48da740c0a03",
|
||||
"from": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"logIndex": 0
|
||||
}
|
||||
],
|
||||
"result": null,
|
||||
"spec": {
|
||||
"task": "keccak-chain",
|
||||
"seed": "0xd2356607ad7de44861e4098fc8de3628ab454527539afeb302e6cbc03450d500",
|
||||
"iters": 20000
|
||||
},
|
||||
"envelope": null
|
||||
},
|
||||
"judecata": {
|
||||
"verdict": "VALID",
|
||||
"motive": [],
|
||||
"randuri": [
|
||||
"2026-10-02T16:50:27.000Z #4446456 JobPosted by 0x86D6C684.. id=8 requester=0x86D6C684.. token=0x00000000.. reward=10000000000000000 mode=0 specHash=0xe39172c8.. deadline=1790959871 pqcSettlement=false",
|
||||
"2026-10-02T16:50:29.000Z #4446459 JobClaimed by 0xeC2261CE.. id=8 provider=0xeC2261CE.. providerBond=4000000000000000",
|
||||
"2026-10-02T16:51:17.000Z #4446540 JobRefunded by 0x86D6C684.. id=8 requester=0x86D6C684.. token=0x00000000.. reward=10000000000000000"
|
||||
],
|
||||
"status": "Refunded",
|
||||
"terminal": {
|
||||
"status": "Refunded",
|
||||
"payee": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"amount": "10000000000000000",
|
||||
"txHash": "0xf041b9338843a0727acca19c794d940ec7d13df17dd17d88cc1c48da740c0a03",
|
||||
"blockHash": "0x269e3df7ecdcc85db01e702814eb766d235233eee90c0182179ddf7307c32cab"
|
||||
},
|
||||
"replay": null
|
||||
}
|
||||
},
|
||||
"S5": {
|
||||
"spec": {
|
||||
"task": "keccak-chain",
|
||||
"seed": "0x7fbe35a5bee832d3c35f100c82ebcc863a4d17ee1be045b5dfaba46aa4b93322",
|
||||
"iters": 20000
|
||||
},
|
||||
"pasi": [],
|
||||
"jobId": 9,
|
||||
"date": {
|
||||
"chainId": 28001,
|
||||
"market": "0x4145d6f639436d840957F2D4B9f2e73f34F56B5D",
|
||||
"jobId": "9",
|
||||
"constants": {
|
||||
"arbiter": "0x5521aC7371Afdbf6BE986B46Da4116fb256d9e58",
|
||||
"governance": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"arbitrationTimeout": 1209600,
|
||||
"zkVerifier": "0x70099E62735500AA2F85B60C518551a57B202d54",
|
||||
"pinnedProofSelector": "0x4388a21c"
|
||||
},
|
||||
"job": {
|
||||
"requester": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"provider": "0x0000000000000000000000000000000000000000",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "0",
|
||||
"providerBond": "0",
|
||||
"specHash": "0xac9d0a395f1ae6e8ca7adebdd44e4454000c349e80feba2c16dc3e0749c9a9b7",
|
||||
"resultHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"zkProgramVKey": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"mode": 0,
|
||||
"status": 6,
|
||||
"pqcSettlement": false,
|
||||
"offchainResult": false,
|
||||
"deadline": 1790959916,
|
||||
"challengeWindow": 90,
|
||||
"submittedAt": 0,
|
||||
"disputedAt": 0
|
||||
},
|
||||
"events": [
|
||||
{
|
||||
"name": "JobPosted",
|
||||
"args": {
|
||||
"id": "9",
|
||||
"requester": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "10000000000000000",
|
||||
"mode": "0",
|
||||
"specHash": "0xac9d0a395f1ae6e8ca7adebdd44e4454000c349e80feba2c16dc3e0749c9a9b7",
|
||||
"deadline": "1790959916",
|
||||
"pqcSettlement": false
|
||||
},
|
||||
"blockNumber": 4446558,
|
||||
"blockHash": "0xb3512a658a8969ec6e6516d19c9bd9d2c99134cc3d654ba5afac6da85ffe363f",
|
||||
"timestamp": 1790959888,
|
||||
"txHash": "0x5aba1b174fd5948b8ff555f0b4d4584607145914eec3ed1bdde00072d31ee1af",
|
||||
"from": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"logIndex": 0
|
||||
},
|
||||
{
|
||||
"name": "JobRefunded",
|
||||
"args": {
|
||||
"id": "9",
|
||||
"requester": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"token": "0x0000000000000000000000000000000000000000",
|
||||
"reward": "10000000000000000"
|
||||
},
|
||||
"blockNumber": 4446614,
|
||||
"blockHash": "0x6cb797489059f864e1e57a687cde487c0f8765945688cd673879c36e8be7e0e3",
|
||||
"timestamp": 1790959920,
|
||||
"txHash": "0xe13b6cdbb7bb49c2d74235eb3c014f793a9d62df079235a19adbe9c66020de21",
|
||||
"from": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"logIndex": 0
|
||||
}
|
||||
],
|
||||
"result": null,
|
||||
"spec": {
|
||||
"task": "keccak-chain",
|
||||
"seed": "0x7fbe35a5bee832d3c35f100c82ebcc863a4d17ee1be045b5dfaba46aa4b93322",
|
||||
"iters": 20000
|
||||
},
|
||||
"envelope": null
|
||||
},
|
||||
"judecata": {
|
||||
"verdict": "VALID",
|
||||
"motive": [],
|
||||
"randuri": [
|
||||
"2026-10-02T16:51:28.000Z #4446558 JobPosted by 0x86D6C684.. id=9 requester=0x86D6C684.. token=0x00000000.. reward=10000000000000000 mode=0 specHash=0xac9d0a39.. deadline=1790959916 pqcSettlement=false",
|
||||
"2026-10-02T16:52:00.000Z #4446614 JobRefunded by 0x86D6C684.. id=9 requester=0x86D6C684.. token=0x00000000.. reward=10000000000000000"
|
||||
],
|
||||
"status": "Refunded",
|
||||
"terminal": {
|
||||
"status": "Refunded",
|
||||
"payee": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF",
|
||||
"amount": "10000000000000000",
|
||||
"txHash": "0xe13b6cdbb7bb49c2d74235eb3c014f793a9d62df079235a19adbe9c66020de21",
|
||||
"blockHash": "0x6cb797489059f864e1e57a687cde487c0f8765945688cd673879c36e8be7e0e3"
|
||||
},
|
||||
"replay": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"desfasurare": {
|
||||
"chainId": 28001,
|
||||
"market": "0x4145d6f639436d840957F2D4B9f2e73f34F56B5D",
|
||||
"deployTx": "0x8f45502e40a71d45550756fdcb3e4ed3af0532134212704f161fd2c4004d8ae2",
|
||||
"block": 4445446,
|
||||
"args": {
|
||||
"ZK_VERIFIER": "0x70099E62735500AA2F85B60C518551a57B202d54",
|
||||
"PINNED_PROOF_SELECTOR": "0x4388a21c",
|
||||
"ARBITER": "0x5521aC7371Afdbf6BE986B46Da4116fb256d9e58",
|
||||
"GOVERNANCE": "0x86D6C68408DDb2CC0d1cf4dFd6e07d3830600fAF"
|
||||
},
|
||||
"artifactSourceSha256": "0x6db4d53dffb0afd79cfb41f973ac4d2ca82589ab7bdd206296b66b4c72dd4fe4",
|
||||
"at": "2026-10-02T16:40:39.149Z",
|
||||
"codeOk": true
|
||||
},
|
||||
"rezumat": {
|
||||
"verzi": 20,
|
||||
"rosii": 0
|
||||
}
|
||||
}
|
||||
32
market-settlement/extrage-artefact.mjs
Normal file
32
market-settlement/extrage-artefact.mjs
Normal file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env node
|
||||
// extrage-artefact.mjs: the compiled AereComputeMarketV3 as ONE file beside this component, taken from the hardhat build-info of the
|
||||
// contracts project (roadmap point 28, 2026-10-02): abi, creation and runtime bytecode, the immutable references WITH their names (so a
|
||||
// reader can rebuild the exact runtime code of a deployment from its constructor arguments and compare it with eth_getCode), the
|
||||
// compiler and its settings, and the SHA-256 of the Solidity source the build-info holds (checked equal to the source in the tree).
|
||||
// Deterministic: the same build-info gives the same file. Refuses a build-info whose source differs from the tree.
|
||||
// node extrage-artefact.mjs writes AereComputeMarketV3.json
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import crypto from 'node:crypto';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
||||
const RAD = path.resolve(AICI, '..', '..');
|
||||
const SOL = 'contracts/depin/AereComputeMarketV3.sol';
|
||||
const dbg = JSON.parse(fs.readFileSync(path.join(RAD, 'contracts', 'artifacts', SOL, 'AereComputeMarketV3.dbg.json'), 'utf8'));
|
||||
const bi = JSON.parse(fs.readFileSync(path.resolve(path.join(RAD, 'contracts', 'artifacts', SOL), dbg.buildInfo.replace(/\\/g, '/')), 'utf8'));
|
||||
const o = bi.output.contracts[SOL].AereComputeMarketV3;
|
||||
const src = bi.input.sources[SOL].content.replace(/\r\n/g, '\n');
|
||||
const tree = fs.readFileSync(path.join(RAD, 'contracts', SOL), 'utf8').replace(/\r\n/g, '\n');
|
||||
if (src !== tree) { console.log('REFUZ: the source in the build-info differs from contracts/' + SOL + ' (recompile first)'); process.exit(1); }
|
||||
const nume = {}; (function walk(x) { if (x && typeof x === 'object') { if (x.nodeType === 'VariableDeclaration' && x.mutability === 'immutable') nume[x.id] = { name: x.name, type: x.typeDescriptions.typeString }; for (const k in x) walk(x[k]); } })(bi.output.sources[SOL].ast);
|
||||
const refs = o.evm.deployedBytecode.immutableReferences || {};
|
||||
const immutables = Object.fromEntries(Object.entries(refs).map(([id, pos]) => [nume[id].name, { type: nume[id].type, positions: pos }]));
|
||||
const art = {
|
||||
kind: 'aere-compute-market-v3-artifact', contract: 'AereComputeMarketV3', source: SOL, sourceSha256: '0x' + crypto.createHash('sha256').update(src).digest('hex'),
|
||||
solc: bi.solcLongVersion, optimizer: bi.input.settings.optimizer, evmVersion: bi.input.settings.evmVersion || 'default',
|
||||
abi: o.abi, bytecode: '0x' + o.evm.bytecode.object, deployedBytecode: '0x' + o.evm.deployedBytecode.object, immutables,
|
||||
note: 'runtime code of a deployment = deployedBytecode with each immutable written at its positions as a 32-byte word (addresses right-aligned, bytes4 left-aligned); compare with eth_getCode',
|
||||
};
|
||||
fs.writeFileSync(path.join(AICI, 'AereComputeMarketV3.json'), JSON.stringify(art, null, 1) + '\n');
|
||||
console.log(`AereComputeMarketV3.json: ${art.solc}, runtime ${(art.deployedBytecode.length - 2) / 2} bytes, immutables ${Object.keys(immutables).join(', ')}, source sha256 ${art.sourceSha256.slice(0, 18)}..`);
|
||||
142
market-settlement/piata.mjs
Normal file
142
market-settlement/piata.mjs
Normal file
@ -0,0 +1,142 @@
|
||||
// piata.mjs: the dispute and settlement rail of the verifiable infrastructure market (roadmap master point 28, 2026-10-02), on what is
|
||||
// live: AereComputeMarketV3 (escrow, provider bond, challenge window, dispute with a challenger bond, arbiter with a permissionless
|
||||
// timeout, deadline reclaim), the AIP-23 execution envelope of the provider's result notarized on AereNotary (the chain's time for the
|
||||
// delivery, under a post-quantum certified anchor), and the AIP-23 settlement receipt of the terminal payout. This file has three parts:
|
||||
// 1. the deployment: the runtime code a market MUST have, rebuilt from the committed artifact and the constructor arguments
|
||||
// (`codulAsteptat`), so a reader checks eth_getCode before trusting any event the market emitted;
|
||||
// 2. the timeline: every event of a job with its block time and the sender of its transaction (`cronologie`), a JSON a reader can keep;
|
||||
// 3. the JUDGE (`judeca`): pure rules over the timeline, no chain: deadlines, the challenge window, who resolved, that the terminal
|
||||
// payout matches the state, that the result bytes hash to the on-chain commitment, that a REPLAY of the public spec agrees with
|
||||
// whoever the arbiter sided with, and that the provider's notarized envelope names this result and existed before the window
|
||||
// closed. Every rule carries a PAZA marker for the planting control. `chitanta` turns the terminal payout into an AIP-23
|
||||
// `settlement` envelope, verifiable with the reference verifier.
|
||||
// ethers is injected (createRequire of the caller's ethers); the rest is node:crypto and the siblings beside this component.
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import crypto from 'node:crypto';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { buildProof } from '../proof-kinds/proof-kinds.mjs';
|
||||
import { ruleaza, rezultatHash, rezultatOcteti, specHash as specHashOf, keccak, canonical } from './sarcina.mjs';
|
||||
|
||||
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
||||
export const ARTEFACT = path.join(AICI, 'AereComputeMarketV3.json');
|
||||
export const STATUS = ['None', 'Open', 'Claimed', 'Submitted', 'Disputed', 'Paid', 'Refunded', 'Slashed'];
|
||||
export const MODE = ['REPLAY', 'ZK_VERIFIED', 'OPTIMISTIC'];
|
||||
const H32 = /^0x[0-9a-f]{64}$/i;
|
||||
const sha256 = (b) => '0x' + crypto.createHash('sha256').update(b).digest('hex');
|
||||
|
||||
export function incarcaArtefact(f = ARTEFACT) {
|
||||
const a = JSON.parse(fs.readFileSync(f, 'utf8'));
|
||||
if (a.kind !== 'aere-compute-market-v3-artifact' || !a.deployedBytecode || !a.immutables || !a.abi) throw new Error('not a market artifact: ' + f);
|
||||
return a;
|
||||
}
|
||||
/** The runtime code of a deployment: the artifact's deployedBytecode with every immutable written at its positions. */
|
||||
export function codulAsteptat(a, args) {
|
||||
const cod = Buffer.from(a.deployedBytecode.slice(2), 'hex');
|
||||
for (const [nume, im] of Object.entries(a.immutables)) {
|
||||
const v = args[nume]; if (v === undefined) throw new Error('missing immutable ' + nume);
|
||||
const w = Buffer.alloc(32);
|
||||
if (/^address|^contract/.test(im.type)) Buffer.from(v.replace(/^0x/, '').padStart(40, '0'), 'hex').copy(w, 12);
|
||||
else if (/^bytes(\d+)$/.test(im.type)) Buffer.from(v.replace(/^0x/, ''), 'hex').copy(w, 0);
|
||||
else throw new Error('immutable type not handled: ' + im.type);
|
||||
for (const p of im.positions) { if (p.length !== 32) throw new Error('immutable slot not 32 bytes'); w.copy(cod, p.start); }
|
||||
}
|
||||
return '0x' + cod.toString('hex');
|
||||
}
|
||||
/** eth_getCode of the market equals the artifact with these constructor arguments; returns { ok, motiv }. */
|
||||
export async function verificaDesfasurarea({ provider, market, artifact, args }) {
|
||||
const viu = (await provider.getCode(market)).toLowerCase();
|
||||
const asteptat = codulAsteptat(artifact, args).toLowerCase();
|
||||
if (viu === '0x') return { ok: false, motiv: 'no code at ' + market };
|
||||
if (viu !== asteptat) return { ok: false, motiv: `the code at ${market} is not AereComputeMarketV3 with these immutables (${viu.length / 2 - 1} vs ${asteptat.length / 2 - 1} bytes, first difference at byte ${[...viu].findIndex((c, i) => c !== asteptat[i]) >> 1})` };
|
||||
return { ok: true, motiv: 'runtime code equals the artifact (' + (viu.length / 2 - 1) + ' bytes) with ZK_VERIFIER, PINNED_PROOF_SELECTOR, ARBITER, GOVERNANCE as given' };
|
||||
}
|
||||
export const contract = (ethers, provider, market, artifact, signer = null) => new ethers.Contract(market, artifact.abi, signer || provider);
|
||||
const s = (v) => (typeof v === 'bigint' ? v.toString() : v);
|
||||
export function jobDinLant(j) {
|
||||
return { requester: j.requester, provider: j.provider, token: j.token, reward: s(j.reward), providerBond: s(j.providerBond), specHash: j.specHash, resultHash: j.resultHash, zkProgramVKey: j.zkProgramVKey,
|
||||
mode: Number(j.mode), status: Number(j.status), pqcSettlement: j.pqcSettlement, offchainResult: j.offchainResult, deadline: Number(j.deadline), challengeWindow: Number(j.challengeWindow), submittedAt: Number(j.submittedAt), disputedAt: Number(j.disputedAt) };
|
||||
}
|
||||
/** The timeline of a job: its state, its constants, and every event (with block time and tx sender), oldest first. */
|
||||
export async function cronologie({ ethers, provider, market, artifact, jobId, fromBlock = 0, spec = null, envelope = null }) {
|
||||
const c = contract(ethers, provider, market, artifact);
|
||||
const [net, job, arbiter, governance, timeout, zk, pin] = await Promise.all([provider.getNetwork(), c.getJob(jobId), c.ARBITER(), c.GOVERNANCE(), c.ARBITRATION_TIMEOUT(), c.ZK_VERIFIER(), c.PINNED_PROOF_SELECTOR()]);
|
||||
const nume = artifact.abi.filter((x) => x.type === 'event' && x.inputs[0] && x.inputs[0].name === 'id').map((x) => x.name);
|
||||
const brute = [];
|
||||
for (const n of nume) for (const l of await c.queryFilter(c.filters[n](jobId), fromBlock, 'latest')) brute.push(l);
|
||||
brute.sort((a, b) => a.blockNumber - b.blockNumber || a.index - b.index);
|
||||
const blocuri = new Map(), txs = new Map();
|
||||
const events = [];
|
||||
for (const l of brute) {
|
||||
if (!blocuri.has(l.blockHash)) blocuri.set(l.blockHash, await provider.getBlock(l.blockHash));
|
||||
if (!txs.has(l.transactionHash)) txs.set(l.transactionHash, await provider.getTransaction(l.transactionHash));
|
||||
const b = blocuri.get(l.blockHash), t = txs.get(l.transactionHash);
|
||||
const args = {}; l.fragment.inputs.forEach((inp, i) => { args[inp.name] = s(l.args[i]); });
|
||||
events.push({ name: l.fragment.name, args, blockNumber: l.blockNumber, blockHash: l.blockHash, timestamp: b.timestamp, txHash: l.transactionHash, from: t.from, logIndex: l.index });
|
||||
}
|
||||
const sub = events.find((e) => e.name === 'ResultSubmitted');
|
||||
return { chainId: Number(net.chainId), market, jobId: String(jobId), constants: { arbiter, governance, arbitrationTimeout: Number(timeout), zkVerifier: zk, pinnedProofSelector: pin },
|
||||
job: jobDinLant(job), events, result: sub ? sub.args.result : null, spec, envelope };
|
||||
}
|
||||
|
||||
// ----- the judge: pure rules over a timeline -----
|
||||
const ok = (m, cond, text) => { if (!cond) m.push(text); return !!cond; };
|
||||
const low = (x) => String(x || '').toLowerCase();
|
||||
export function judeca(d) {
|
||||
const motive = [], randuri = []; const j = d.job, ev = d.events || []; const un = (n) => ev.find((e) => e.name === n); const toate = (n) => ev.filter((e) => e.name === n);
|
||||
const t = (e) => (e ? e.timestamp : null);
|
||||
if (!j || !ev.length) return { verdict: 'INCOMPLETE', motive: ['no job or no events'], randuri };
|
||||
for (const e of ev) randuri.push(`${new Date(e.timestamp * 1000).toISOString()} #${e.blockNumber} ${e.name} by ${e.from.slice(0, 10)}.. ${Object.entries(e.args).filter(([k]) => k !== 'result').map(([k, v]) => k + '=' + (H32.test(String(v)) ? String(v).slice(0, 10) + '..' : String(v).length > 20 ? String(v).slice(0, 10) + '..' : v)).join(' ')}`);
|
||||
const posted = un('JobPosted'), claimed = un('JobClaimed'), submitted = un('ResultSubmitted') || un('ResultReferenced'), disputed = un('JobDisputed'), resolved = un('DisputeResolved'), timedOut = un('DisputeTimedOut'), paid = un('JobPaid'), refunded = un('JobRefunded'), slashed = un('JobSlashed');
|
||||
// R0 the posting matches the job (the reward is compared through the payout events: the contract zeroes j.reward on settlement)
|
||||
ok(motive, posted && low(posted.args.requester) === low(j.requester) && low(posted.args.specHash) === low(j.specHash) && Number(posted.args.deadline) === j.deadline && (j.reward === '0' || posted.args.reward === j.reward), 'the JobPosted event does not match the job (requester, reward, specHash, deadline)');
|
||||
// R1 deadlines: a claim and a submission only up to the deadline
|
||||
if (claimed) ok(motive, t(claimed) <= j.deadline /*PAZA:termen*/, 'the job was claimed after its deadline');
|
||||
if (submitted) ok(motive, t(submitted) <= j.deadline && t(submitted) === j.submittedAt /*PAZA:termen*/, 'the result was submitted after the deadline, or submittedAt does not match the event');
|
||||
if (refunded && claimed && !submitted && !disputed) ok(motive, t(refunded) > j.deadline /*PAZA:termen*/, 'the escrow was reclaimed as expired before the deadline');
|
||||
// R2 the challenge window: a dispute strictly inside it, a finalize (by anyone but the requester) only after it
|
||||
if (disputed) ok(motive, submitted && t(disputed) < j.submittedAt + j.challengeWindow /*PAZA:fereastra*/, 'the dispute was raised after the challenge window closed');
|
||||
if (paid && !resolved && submitted) ok(motive, low(paid.from) === low(j.requester) || t(paid) >= j.submittedAt + j.challengeWindow /*PAZA:fereastra*/, 'the provider was paid inside the challenge window by someone other than the requester (acceptResult is the requester\'s)');
|
||||
// R3 who resolved: the arbiter, or the permissionless timeout after ARBITRATION_TIMEOUT
|
||||
if (resolved) ok(motive, low(resolved.from) === low(d.constants.arbiter) /*PAZA:arbitru*/, 'the dispute was resolved by an address that is not the ARBITER');
|
||||
if (timedOut) ok(motive, disputed && t(timedOut) >= j.disputedAt + d.constants.arbitrationTimeout /*PAZA:arbitru*/, 'the dispute was timed out before ARBITRATION_TIMEOUT');
|
||||
if (resolved || timedOut) ok(motive, !!disputed, 'a resolution without a dispute');
|
||||
// R4 the terminal payout matches the state
|
||||
const st = STATUS[j.status]; let terminal = null;
|
||||
if (st === 'Paid') { const okP = paid && low(paid.args.provider) === low(j.provider) && paid.args.reward === posted.args.reward && (!claimed || paid.args.bondReturned === claimed.args.providerBond) && (!resolved || resolved.args.providerWon === true) /*PAZA:plata*/; ok(motive, okP, 'status Paid without a matching JobPaid (provider, reward, bond returned, providerWon)'); terminal = paid && { status: st, payee: paid.args.provider, amount: paid.args.reward, txHash: paid.txHash, blockHash: paid.blockHash }; }
|
||||
else if (st === 'Slashed') { const okS = slashed && disputed && resolved && resolved.args.providerWon === false && low(slashed.args.challenger) === low(disputed.args.challenger) && (!claimed || slashed.args.slashedProviderBond === claimed.args.providerBond) /*PAZA:plata*/; ok(motive, okS, 'status Slashed without a matching JobSlashed (challenger, provider bond) and DisputeResolved(false)'); terminal = slashed && { status: st, payee: slashed.args.challenger, amount: slashed.args.slashedProviderBond, refundedTo: j.requester, txHash: slashed.txHash, blockHash: slashed.blockHash }; }
|
||||
else if (st === 'Refunded') { const okR = refunded && low(refunded.args.requester) === low(j.requester) && refunded.args.reward === posted.args.reward /*PAZA:plata*/; ok(motive, okR, 'status Refunded without a matching JobRefunded (requester, reward)'); terminal = refunded && { status: st, payee: refunded.args.requester, amount: refunded.args.reward, txHash: refunded.txHash, blockHash: refunded.blockHash }; }
|
||||
ok(motive, j.status !== 5 || j.reward === '0', 'a paid job still holds a reward');
|
||||
// R5 the result bytes hash to the on-chain commitment
|
||||
if (d.result && submitted) ok(motive, keccak(Buffer.from(d.result.slice(2), 'hex')) === low(j.resultHash) && low(submitted.args.resultHash) === low(j.resultHash) /*PAZA:rezultat*/, 'the submitted bytes do not hash to the job\'s resultHash');
|
||||
// R6 REPLAY: the public spec recomputed; the arbiter's decision must agree with the recomputation
|
||||
let replay = null;
|
||||
if (d.spec && d.result && submitted) {
|
||||
ok(motive, specHashOf(d.spec) === low(j.specHash), 'the public spec does not hash to the job\'s specHash');
|
||||
try { const r = ruleaza(d.spec); const corect = rezultatHash(r) === low(j.resultHash); replay = { recomputedHash: rezultatHash(r), providerRight: corect };
|
||||
if (disputed) { replay.challengerRecomputedRight = low(disputed.args.recomputedResultHash) === rezultatHash(r); }
|
||||
if (resolved) ok(motive, resolved.args.providerWon === corect /*PAZA:replay*/, `the arbiter sided with the ${resolved.args.providerWon ? 'provider' : 'challenger'} but the replay says the provider is ${corect ? 'right' : 'wrong'}`);
|
||||
if (st === 'Paid' && !resolved) ok(motive, corect /*PAZA:replay*/, 'the provider was paid for a result the replay does not reproduce (no one disputed in time)');
|
||||
} catch (e) { motive.push('the replay could not run: ' + e.message); }
|
||||
}
|
||||
// R7 the provider's envelope names this result and existed before the window closed
|
||||
if (d.envelope) {
|
||||
const e = d.envelope, stmt = e.statement || {};
|
||||
const okE = e.kind === 'aere-proof-of-execution-attestation' && low(stmt.outputHash) === low(j.resultHash) && low(stmt.inputHash) === low(j.specHash) && sha256(Buffer.from(JSON.stringify(stmt), 'utf8')) === low(e.statementHash) /*PAZA:dovada*/;
|
||||
ok(motive, okE, 'the execution envelope does not name this job\'s specHash/resultHash, or its statementHash is not its statement\'s');
|
||||
if (e.notarization && submitted) ok(motive, Number(e.notarization.firstSeen) <= j.submittedAt + j.challengeWindow /*PAZA:dovada*/, 'the envelope was notarized only after the challenge window closed');
|
||||
}
|
||||
const terminalStatus = ['Paid', 'Refunded', 'Slashed'].includes(st);
|
||||
return { verdict: motive.length ? 'INVALID' : terminalStatus ? 'VALID' : 'INCOMPLETE', motive, randuri, status: st, terminal, replay };
|
||||
}
|
||||
/** The AIP-23 settlement receipt of a terminal job: the payout transaction, the parties, the amount, the instruction (market, job, status). */
|
||||
export function chitanta(d, { createdAt = new Date().toISOString() } = {}) {
|
||||
const r = judeca(d); if (r.verdict !== 'VALID' || !r.terminal) throw new Error('no valid terminal settlement to receipt: ' + (r.motive[0] || r.verdict));
|
||||
const t = r.terminal;
|
||||
return buildProof('settlement', { chainId: d.chainId, txHash: t.txHash, blockHash: t.blockHash, from: d.market, to: t.payee, amount: t.amount, asset: 'native', instruction: Buffer.from(canonical({ market: d.market, jobId: d.jobId, status: t.status, resultHash: d.job.resultHash }), 'utf8'), createdAt });
|
||||
}
|
||||
/** The provider's execution envelope for a result (spec in, result out), to notarize before submitting. */
|
||||
export function plicExecutie({ spec, result, programSha256, createdAt = new Date().toISOString() }) {
|
||||
return buildProof('execution', { program: spec.task, inputHash: specHashOf(spec), outputHash: rezultatHash(result), programSha256, exitCode: 0, createdAt });
|
||||
}
|
||||
export { ruleaza, rezultatHash, rezultatOcteti, specHashOf as specHash, canonical };
|
||||
71
market-settlement/proba-judecata.mjs
Normal file
71
market-settlement/proba-judecata.mjs
Normal file
@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env node
|
||||
// proba-judecata.mjs: the judge of the market settlement (piata.mjs) in the laboratory, without a chain, on the timelines the live run
|
||||
// recorded (dovezi/piata-28001-*.json, the newest): the honest timelines are VALID for their named status, and nine forged ones are
|
||||
// INVALID for their named reason. --control-negativ plants every PAZA marker out, one at a time, in a copy of piata.mjs and requires
|
||||
// the forged fixture it guards to be accepted: a check that cannot be made to fail is not a check.
|
||||
// Exit 0 when every row is as named; 1 otherwise; 2 when no evidence file exists (run proba-piata-28001.mjs first).
|
||||
// node proba-judecata.mjs [--control-negativ] env: AERE_PIATA_MODUL (the module under test; the control sets its planted copy)
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
|
||||
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
||||
const MODUL = process.env.AERE_PIATA_MODUL || path.join(AICI, 'piata.mjs');
|
||||
const control = process.argv.includes('--control-negativ');
|
||||
const dovezi = fs.existsSync(path.join(AICI, 'dovezi')) ? fs.readdirSync(path.join(AICI, 'dovezi')).filter((f) => /^piata-28001-.*\.json$/.test(f)).sort() : [];
|
||||
if (!dovezi.length) { console.log('NEMASURAT: no dovezi/piata-28001-*.json (run proba-piata-28001.mjs first)'); process.exit(2); }
|
||||
const D = JSON.parse(fs.readFileSync(path.join(AICI, 'dovezi', dovezi[dovezi.length - 1]), 'utf8'));
|
||||
const P = await import(pathToFileURL(MODUL).href + '?t=' + Date.now());
|
||||
const clona = (x) => JSON.parse(JSON.stringify(x));
|
||||
const date = (s) => { const x = D.scenarii[s]; if (!x || !x.date) throw new Error('scenario ' + s + ' has no timeline in the evidence'); return clona(x.date); };
|
||||
const ev = (d, n) => d.events.find((e) => e.name === n);
|
||||
|
||||
/** The fixtures: honest timelines (VALID, named status) and forged ones (INVALID, named reason); `paza` names the marker that guards each forgery. */
|
||||
export function fixturi(P) {
|
||||
const F = [];
|
||||
for (const [s, st] of [['S1', 'Paid'], ['S2', 'Slashed'], ['S3', 'Paid'], ['S4', 'Refunded'], ['S5', 'Refunded']]) F.push({ nume: `${s} the recorded timeline is VALID with status ${st}`, asteptat: 'VALID', r: () => { const r = P.judeca(date(s)); return { verdict: r.verdict, detail: r.status + (r.replay ? ' replay providerRight=' + r.replay.providerRight : '') + '; ' + r.motive.join('; ') }; } });
|
||||
F.push({ nume: 'N1 S1 finalized INSIDE the window by a third party (timestamp moved back)', asteptat: 'INVALID', paza: 'fereastra', r: () => { const d = date('S1'); ev(d, 'JobPaid').timestamp = d.job.submittedAt + 1; return v(P.judeca(d)); } });
|
||||
F.push({ nume: 'N2 S2 dispute raised AFTER the window (timestamp moved forward)', asteptat: 'INVALID', paza: 'fereastra', r: () => { const d = date('S2'); ev(d, 'JobDisputed').timestamp = d.job.submittedAt + d.job.challengeWindow + 5; return v(P.judeca(d)); } });
|
||||
F.push({ nume: 'N3 S2 resolved by an address that is not the arbiter', asteptat: 'INVALID', paza: 'arbitru', r: () => { const d = date('S2'); ev(d, 'DisputeResolved').from = d.job.requester; return v(P.judeca(d)); } });
|
||||
F.push({ nume: 'N4 S1 claimed after the deadline (deadline moved before the claim)', asteptat: 'INVALID', paza: 'termen', r: () => { const d = date('S1'); d.job.deadline = ev(d, 'JobClaimed').timestamp - 1; ev(d, 'JobPosted').args.deadline = String(d.job.deadline); return v(P.judeca(d)); } });
|
||||
F.push({ nume: 'N5 S1 paid to someone who is not the provider', asteptat: 'INVALID', paza: 'plata', r: () => { const d = date('S1'); ev(d, 'JobPaid').args.provider = d.job.requester; return v(P.judeca(d)); } });
|
||||
F.push({ nume: 'N6 S1 result bytes that do not hash to the commitment', asteptat: 'INVALID', paza: 'rezultat', r: () => { const d = date('S1'); d.result = d.result.slice(0, -2) + (d.result.endsWith('00') ? '01' : '00'); return v(P.judeca(d)); } });
|
||||
F.push({ nume: 'N7 S3 the arbiter sided with the provider although the replay contradicts it (spec swapped to another seed)', asteptat: 'INVALID', paza: 'replay', r: () => { const d = date('S3'); d.spec = { ...d.spec, seed: '0x' + 'ab'.repeat(32) }; d.job.specHash = P.specHash(d.spec); ev(d, 'JobPosted').args.specHash = d.job.specHash; return v(P.judeca(d)); } });
|
||||
F.push({ nume: 'N8 S1 the envelope names another result (outputHash changed, statementHash kept)', asteptat: 'INVALID', paza: 'dovada', r: () => { const d = date('S1'); d.envelope.statement.outputHash = '0x' + 'cc'.repeat(32); return v(P.judeca(d)); } });
|
||||
F.push({ nume: 'N9 S1 the envelope notarized only after the window closed', asteptat: 'INVALID', paza: 'dovada', r: () => { const d = date('S1'); d.envelope.notarization.firstSeen = d.job.submittedAt + d.job.challengeWindow + 1; return v(P.judeca(d)); } });
|
||||
F.push({ nume: 'N10 S4 reclaimed as expired BEFORE the deadline', asteptat: 'INVALID', paza: 'termen', r: () => { const d = date('S4'); ev(d, 'JobRefunded').timestamp = d.job.deadline - 1; return v(P.judeca(d)); } });
|
||||
F.push({ nume: 'R1 the settlement receipt of S1 is an AIP-23 settlement envelope naming the payout tx and the provider', asteptat: 'VALID', r: () => { const d = date('S1'); const c = P.chitanta(d); const t = ev(d, 'JobPaid'); return { verdict: c.kind === 'aere-proof-of-settlement-attestation' && c.statement.txHash === t.txHash && c.statement.to.toLowerCase() === d.job.provider.toLowerCase() && c.statement.chainId === 28001 ? 'VALID' : 'INVALID', detail: c.statementHash.slice(0, 18) + '..' }; } });
|
||||
return F;
|
||||
}
|
||||
const v = (r) => ({ verdict: r.verdict, detail: r.motive.join('; ') });
|
||||
|
||||
if (!control) {
|
||||
let verzi = 0, rosii = 0, stricate = 0;
|
||||
for (const f of fixturi(P)) {
|
||||
let r; try { r = f.r(); } catch (e) { r = { verdict: 'STRICAT', detail: String(e.message).slice(0, 160) }; }
|
||||
const stare = r.verdict === 'STRICAT' ? 'STRICAT' : r.verdict === f.asteptat ? 'VERDE' : 'ROSU'; stare === 'VERDE' ? verzi++ : stare === 'ROSU' ? rosii++ : stricate++;
|
||||
console.log(` [${stare.padEnd(7)}] ${f.nume}: ${r.verdict}${r.detail ? ' (' + r.detail.slice(0, 150) + ')' : ''}`);
|
||||
}
|
||||
console.log(`\nmarket judge (evidence ${dovezi[dovezi.length - 1]}): ${verzi} VERDE, ${rosii} ROSU, ${stricate} STRICAT`);
|
||||
process.exitCode = rosii || stricate ? 1 : 0;
|
||||
} else {
|
||||
const SURSA = fs.readFileSync(MODUL, 'utf8'); const marcaje = ['termen', 'fereastra', 'arbitru', 'plata', 'rezultat', 'replay', 'dovada']; let bune = 0;
|
||||
for (const m of marcaje) {
|
||||
const copie = path.join(AICI, `_plantat-piata-${process.pid}-${m}.mjs`); const n = SURSA.split(`/*PAZA:${m}*/`).length - 1; const tinte = fixturi(P).filter((f) => f.paza === m);
|
||||
let stare, detaliu;
|
||||
try {
|
||||
if (n < 1 || !tinte.length) { stare = 'STRICAT'; detaliu = `marker ${n} times, ${tinte.length} fixtures`; }
|
||||
else {
|
||||
fs.writeFileSync(copie, SURSA.split(`/*PAZA:${m}*/`).join("|| String(1) === '1' /*PLANTAT*/"));
|
||||
const Q = await import(pathToFileURL(copie).href + '?t=' + Date.now()); const fx = fixturi(Q); const rez = [];
|
||||
for (const t of tinte) { const f = fx.find((x) => x.nume === t.nume); const r = f.r(); rez.push(`${t.nume.split(' ')[0]}:${r.verdict}`); }
|
||||
const toate = rez.every((x) => x.endsWith(':VALID')); stare = toate ? 'VERDE' : 'ROSU'; detaliu = (toate ? 'the forged fixture is accepted with the check planted out: ' : 'the planting did not change the verdict: ') + rez.join(' ');
|
||||
}
|
||||
} catch (e) { stare = 'STRICAT'; detaliu = String(e.message).slice(0, 160); } finally { fs.rmSync(copie, { force: true }); }
|
||||
if (stare === 'VERDE') bune++;
|
||||
console.log(` [${stare.padEnd(7)}] PAZA:${m} (${n} marker${n === 1 ? '' : 's'}) ${detaliu}`);
|
||||
}
|
||||
const ramase = fs.readdirSync(AICI).filter((f) => f.startsWith('_plantat-piata-'));
|
||||
console.log(`CONTROL: ${bune} of ${marcaje.length} checks proven load-bearing${ramase.length ? '; copies left: ' + ramase.join(',') : ''}`);
|
||||
process.exitCode = bune === marcaje.length && !ramase.length ? 0 : 1;
|
||||
}
|
||||
170
market-settlement/proba-piata-28001.mjs
Normal file
170
market-settlement/proba-piata-28001.mjs
Normal file
@ -0,0 +1,170 @@
|
||||
#!/usr/bin/env node
|
||||
// proba-piata-28001.mjs: the complete market flow MEASURED on testnet 28001 (roadmap point 28): order -> proof -> dispute with a deadline
|
||||
// -> settlement, on AereComputeMarketV3 deployed from the committed artifact, with test keys only (the requester's key from
|
||||
// AERE_TESTNET_KEY_FILE; the provider's and the arbiter's derived from it, never printed, funded from it). Never chain 2800.
|
||||
// S1 happy path post (escrow) -> claim (bond) -> compute -> execution envelope NOTARIZED -> submit -> finalize refused inside the
|
||||
// window (WindowOpen) -> window passes -> dispute refused (WindowClosed) -> finalize by a third party -> Paid;
|
||||
// the judge VALID; the envelope read back under a post-quantum certified anchor; the AIP-23 settlement receipt
|
||||
// S2 dispute upheld post -> claim -> a WRONG result -> the requester recomputes and disputes (bond) -> timeout refused
|
||||
// (ArbitrationTimeoutNotPassed), a non-arbiter refused (NotArbiter) -> the arbiter rules against the provider -> Slashed
|
||||
// S3 dispute lost post -> claim -> a correct result -> a bad-faith dispute -> the arbiter rules for the provider -> Paid, challenger forfeits
|
||||
// S4 deadline post (short deadline) -> claim -> no result -> reclaim refused before the deadline -> after it, reclaimExpired -> Refunded
|
||||
// S5 open job post (short deadline) -> after it, claim refused (DeadlinePassed) -> cancelJob -> Refunded
|
||||
// Every scenario's timeline is judged by piata.mjs (pure rules) and saved in dovezi/ for the offline laboratory (proba-judecata.mjs).
|
||||
// Exit 0 when every row is as named; 1 otherwise; 2 when the chain, the key or ethers are missing.
|
||||
// AERE_TESTNET_KEY_FILE=<key> AERE_ETHERS_DIR=<dir with node_modules/ethers> node proba-piata-28001.mjs [--fereastra 90] [--piata 0x..]
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import crypto from 'node:crypto';
|
||||
import { createRequire } from 'node:module';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { incarcaArtefact, verificaDesfasurarea, contract, cronologie, judeca, chitanta, plicExecutie, ruleaza, rezultatOcteti, rezultatHash, specHash } from './piata.mjs';
|
||||
import { specNou, programSha256 } from './sarcina.mjs';
|
||||
import { notarizeEnvelope } from '../agents/agent-ancora.mjs';
|
||||
|
||||
const AICI = path.dirname(fileURLToPath(import.meta.url));
|
||||
const RAD = path.resolve(AICI, '..', '..');
|
||||
const a = process.argv.slice(2); const opt = (k, d) => { const i = a.indexOf(k); return i >= 0 ? a[i + 1] : d; };
|
||||
const RPC = process.env.AERE_TESTNET_RPC || 'https://testnet-rpc.aere.network';
|
||||
const KF = process.env.AERE_TESTNET_KEY_FILE || path.join(RAD, 'tools', 'aere-quantum-shield', 'dovezi-l1', '.chei', 'cheie-de-proba-28001.key');
|
||||
const ETHERS_DIR = process.env.AERE_ETHERS_DIR || path.join(RAD, 'contracts');
|
||||
const VERIFY_PROOF = process.env.AERE_VERIFY_PROOF || path.join(RAD, 'verificator-falcon', 'verify-proof.mjs');
|
||||
const FEREASTRA = Number(opt('--fereastra', 90));
|
||||
const NOTAR_28001 = '0x70099E62735500AA2F85B60C518551a57B202d54';
|
||||
const PIN = '0x4388a21c';
|
||||
const DOVEZI = path.join(AICI, 'dovezi'); fs.mkdirSync(DOVEZI, { recursive: true });
|
||||
const DESF = path.join(AICI, 'desfasurare-28001.json');
|
||||
const nem = (m) => { console.log('NEMASURAT: ' + m); process.exit(2); };
|
||||
let verzi = 0, rosii = 0; const cer = (n, c, d = '') => { console.log(` [${c ? 'VERDE ' : 'ROSU '}] ${n}${d ? ' (' + String(d).slice(0, 200) + ')' : ''}`); c ? verzi++ : rosii++; };
|
||||
|
||||
if (!fs.existsSync(KF)) nem('no test key at ' + KF + ' (AERE_TESTNET_KEY_FILE)');
|
||||
let ethers; try { ({ ethers } = createRequire(path.join(ETHERS_DIR, 'package.json'))('ethers')); } catch (e) { nem('ethers not loadable from ' + ETHERS_DIR + ': ' + e.message); }
|
||||
const cheia = (f) => { const l = fs.readFileSync(f, 'utf8').split(/\r?\n/).map((x) => x.trim()).find((x) => /^(d|PRIVATE_KEY)=/.test(x)) || ''; const h = l.replace(/^(d|PRIVATE_KEY)=/, '').replace(/^0x/, ''); if (!/^[0-9a-fA-F]{1,64}$/.test(h)) nem('the key file has no d=<hex> line'); return '0x' + h.padStart(64, '0'); };
|
||||
const provider = new ethers.JsonRpcProvider(RPC, undefined, { staticNetwork: false });
|
||||
const chainId = Number((await provider.getNetwork()).chainId); if (chainId !== 28001) nem(`the RPC serves chain ${chainId}; this flow runs only on testnet 28001`);
|
||||
const req = new ethers.Wallet(cheia(KF), provider);
|
||||
const deriva = (rol) => new ethers.Wallet('0x' + crypto.createHash('sha256').update(req.privateKey + ':' + rol).digest('hex'), provider); // derived, never printed
|
||||
const prov = deriva('provider'), arb = deriva('arbiter');
|
||||
const art = incarcaArtefact();
|
||||
const E = (n) => ethers.parseEther(n);
|
||||
const ora = async () => (await provider.getBlock('latest')).timestamp;
|
||||
const asteapta = async (ts, ce) => { process.stdout.write(` waiting for chain time >= ${ts} (${ce})`); while ((await ora()) < ts) { await new Promise((r) => setTimeout(r, 3000)); process.stdout.write('.'); } console.log(''); };
|
||||
const tx = async (nume, p) => { const r = await (await p).wait(1, 180000); if (!r || r.status !== 1) throw new Error(nume + ' failed'); console.log(` ${nume}: tx ${r.hash.slice(0, 18)}.. block ${r.blockNumber} gas ${r.gasUsed}`); return r; };
|
||||
// a refusal by a NAMED custom error (static call), not just "it reverted"
|
||||
const refuza = async (nume, fn, eroare, c) => { try { await fn(); return cer(nume, false, 'did not revert'); } catch (e) { let n = e.revert && e.revert.name; if (!n && e.data) { try { n = c.interface.parseError(e.data).name; } catch {} } if (!n && e.info && e.info.error && e.info.error.data) { try { n = c.interface.parseError(e.info.error.data).name; } catch {} } cer(nume, n === eroare, 'reverted with ' + (n || String(e.shortMessage || e.message).slice(0, 80))); } };
|
||||
const verifyProof = (env, rpc = null) => { const f = path.join(os.tmpdir(), 'piata-' + crypto.randomUUID() + '.json'); fs.writeFileSync(f, JSON.stringify(env)); try { const r = spawnSync(process.execPath, [VERIFY_PROOF, f, '--json', ...(rpc ? ['--rpc', rpc, '--chain', '28001'] : [])], { encoding: 'utf8', timeout: 300000 }); try { return JSON.parse(r.stdout); } catch { return null; } } finally { fs.rmSync(f, { force: true }); } };
|
||||
const dovada = { la: new Date().toISOString(), chainId, rpc: RPC, fereastra: FEREASTRA, scenarii: {} };
|
||||
|
||||
// ----- deployment (reused when the code at the recorded address is the artifact with our immutables) -----
|
||||
const ARGS = { ZK_VERIFIER: NOTAR_28001, PINNED_PROOF_SELECTOR: PIN, ARBITER: arb.address, GOVERNANCE: req.address };
|
||||
let piata = opt('--piata', null);
|
||||
if (!piata && fs.existsSync(DESF)) { const d = JSON.parse(fs.readFileSync(DESF, 'utf8')); if (d.chainId === chainId) piata = d.market; }
|
||||
if (piata) { const v = await verificaDesfasurarea({ provider, market: piata, artifact: art, args: ARGS }); if (!v.ok) { console.log(' the recorded market does not verify (' + v.motiv + '); deploying anew'); piata = null; } }
|
||||
if (!piata) {
|
||||
console.log(` deploying AereComputeMarketV3 from the artifact (${art.solc}; ZK_VERIFIER = AereNotary 28001: a contract with code, so ZK_VERIFIED jobs can never pay here; pin ${PIN})`);
|
||||
const f = new ethers.ContractFactory(art.abi, art.bytecode, req);
|
||||
const c = await f.deploy(ARGS.ZK_VERIFIER, ARGS.ARBITER, ARGS.GOVERNANCE, ARGS.PINNED_PROOF_SELECTOR); const rc = await c.deploymentTransaction().wait(1, 180000);
|
||||
piata = await c.getAddress();
|
||||
fs.writeFileSync(DESF, JSON.stringify({ chainId, market: piata, deployTx: rc.hash, block: rc.blockNumber, args: ARGS, artifactSourceSha256: art.sourceSha256, at: new Date().toISOString() }, null, 1));
|
||||
console.log(` deployed at ${piata}, tx ${rc.hash.slice(0, 18)}.. block ${rc.blockNumber} gas ${rc.gasUsed}`);
|
||||
}
|
||||
const desf = JSON.parse(fs.readFileSync(DESF, 'utf8'));
|
||||
const vd = await verificaDesfasurarea({ provider, market: piata, artifact: art, args: ARGS });
|
||||
cer('D1 the market\'s runtime code is the committed artifact with ZK_VERIFIER, pin, ARBITER, GOVERNANCE as recorded', vd.ok, vd.motiv);
|
||||
dovada.desfasurare = { ...desf, codeOk: vd.ok };
|
||||
// ----- funding the derived keys from the requester's test key -----
|
||||
for (const [w, n, min] of [[prov, 'provider', '0.06'], [arb, 'arbiter', '0.03']]) { if ((await provider.getBalance(w.address)) < E(min)) await tx('fund ' + n, req.sendTransaction({ to: w.address, value: E(min) })); }
|
||||
const C = (w) => contract(ethers, provider, piata, art, w);
|
||||
const posteaza = async (spec, { deadlineIn = 600, window = FEREASTRA, reward = '0.01', bond = '0.004' } = {}) => {
|
||||
const deadline = (await ora()) + deadlineIn;
|
||||
const r = await tx('postJob', C(req).postJob({ token: ethers.ZeroAddress, reward: E(reward), providerBond: E(bond), specHash: specHash(spec), zkProgramVKey: ethers.ZeroHash, mode: 0, deadline, challengeWindow: window, pqcSettlement: false, offchainResult: false }, { value: E(reward) }));
|
||||
const id = Number(C(req).interface.parseLog(r.logs.find((l) => l.address.toLowerCase() === piata.toLowerCase())).args.id);
|
||||
return { id, deadline, bond: E(bond) };
|
||||
};
|
||||
const scenariu = async (nume, titlu, fn) => { console.log(`\n${nume} ${titlu}`); const spec = specNou({ iters: 20000 }); const s = { spec, pasi: [] }; dovada.scenarii[nume] = s; try { await fn(spec, s); } catch (e) { cer(nume + ' did not break', false, e.shortMessage || e.message); } };
|
||||
const judecaSiSalveaza = async (nume, s, id, extra = {}) => { const d = await cronologie({ ethers, provider, market: piata, artifact: art, jobId: id, fromBlock: desf.block, spec: s.spec, envelope: extra.envelope || null }); const r = judeca(d); s.date = d; s.judecata = r; for (const l of r.randuri) console.log(' ' + l); cer(`${nume} the judge on the timeline: VALID, status ${r.status}${r.replay ? ', replay ' + (r.replay.providerRight ? 'confirms' : 'contradicts') + ' the provider' : ''}`, r.verdict === 'VALID', r.motive.join('; ')); return { d, r }; };
|
||||
|
||||
try {
|
||||
await scenariu('S1', 'happy path: order -> proof notarized -> submit -> window -> finalize -> Paid', async (spec, s) => {
|
||||
const { id } = await posteaza(spec); s.jobId = id;
|
||||
await tx('claimJob (provider, bond)', C(prov).claimJob(id, '0x', { value: E('0.004') }));
|
||||
const t0 = Date.now(); const rez = ruleaza(spec); s.computeMs = Date.now() - t0;
|
||||
const env = plicExecutie({ spec, result: rez, programSha256: programSha256() });
|
||||
// one notarization formula for the whole house (agents/agent-ancora.mjs): it reads the key from a file, so the provider's derived
|
||||
// key goes through a 0600 temp file that lives only for this call
|
||||
const kt = path.join(os.tmpdir(), 'piata-prov-' + process.pid + '.key'); fs.writeFileSync(kt, 'd=' + prov.privateKey.slice(2) + '\n', { mode: 0o600 });
|
||||
let n; try { n = await notarizeEnvelope({ envelope: env, kind: 'aere-proof-of-execution', rpc: RPC, keyFile: kt, ethers }); } finally { fs.rmSync(kt, { force: true }); }
|
||||
if (!n.ok) throw new Error('notarization of the execution envelope: ' + n.motiv);
|
||||
const notarizat = n.envelope; console.log(` notarized the execution envelope (provider): firstSeen ${notarizat.notarization.firstSeen}${notarizat.notarization.txHash ? ', tx ' + notarizat.notarization.txHash.slice(0, 18) + '..' : ''}`);
|
||||
s.envelope = notarizat;
|
||||
await tx('submitResult (provider)', C(prov).submitResult(id, rezultatOcteti(rez)));
|
||||
await refuza('S1 finalize inside the window is refused: WindowOpen', () => C(arb).finalize.staticCall(id, '0x'), 'WindowOpen', C(arb));
|
||||
const j = await C(req).getJob(id); await asteapta(Number(j.submittedAt) + FEREASTRA, 'the challenge window');
|
||||
await refuza('S1 a dispute after the window is refused: WindowClosed', () => C(req).dispute.staticCall(id, rezultatHash(rez), { value: E('0.004') }), 'WindowClosed', C(req));
|
||||
const before = await provider.getBalance(prov.address);
|
||||
await tx('finalize (a third party: the arbiter key as "anyone")', C(arb).finalize(id, '0x'));
|
||||
const after = await provider.getBalance(prov.address);
|
||||
cer('S1 the provider received reward + bond (0.014 tAERE) at finalize', after - before === E('0.014'), ethers.formatEther(after - before) + ' tAERE');
|
||||
const { d, r } = await judecaSiSalveaza('S1', s, id, { envelope: notarizat });
|
||||
let v = null; for (let k = 0; k < 24; k++) { v = verifyProof(notarizat, RPC); const f = v && (v.levels || []).find((l) => l.level === 'finality'); if (f && f.state !== 'PENDING') break; await new Promise((ok) => setTimeout(ok, 5000)); }
|
||||
const fin = v && (v.levels || []).find((l) => l.level === 'finality'); s.verifyProof = v;
|
||||
cer('S1 the provider\'s execution envelope verifies with the reference verifier under a post-quantum certified anchor (finality PASSED)', v && v.verdict === 'VALID' && fin && fin.state === 'PASSED', fin ? fin.state + ': ' + String(fin.detail).slice(0, 120) : '?');
|
||||
cer('S1 the notarized proof existed before the window closed (firstSeen <= submittedAt + window)', notarizat.notarization.firstSeen <= d.job.submittedAt + d.job.challengeWindow, `firstSeen ${notarizat.notarization.firstSeen}, window end ${d.job.submittedAt + d.job.challengeWindow}`);
|
||||
const ch = chitanta(d); s.chitanta = ch; const vc = verifyProof(ch); s.chitantaVerdict = vc;
|
||||
cer('S1 the settlement receipt (AIP-23 settlement) names the payout tx, the provider and 0.01 tAERE, and verifies (VALID, finality ABSENT: not notarized)', ch.statement.txHash === r.terminal.txHash && ch.statement.to.toLowerCase() === prov.address.toLowerCase() && ch.statement.amount === E('0.01').toString() && vc && vc.verdict === 'VALID', vc && (vc.levels || []).map((l) => l.level + ':' + l.state).join(' '));
|
||||
});
|
||||
await scenariu('S2', 'dispute upheld: a wrong result, the requester recomputes and disputes, the arbiter slashes', async (spec, s) => {
|
||||
const { id } = await posteaza(spec); s.jobId = id;
|
||||
await tx('claimJob (provider, bond)', C(prov).claimJob(id, '0x', { value: E('0.004') }));
|
||||
const rez = ruleaza(spec); const gresit = { ...rez, digest: '0x' + 'ee'.repeat(32) };
|
||||
await tx('submitResult (provider, WRONG digest)', C(prov).submitResult(id, rezultatOcteti(gresit)));
|
||||
const recomputat = rezultatHash(ruleaza(spec));
|
||||
await tx('dispute (requester as challenger, recomputed hash, bond)', C(req).dispute(id, recomputat, { value: E('0.004') }));
|
||||
await refuza('S2 resolveByTimeout before ARBITRATION_TIMEOUT is refused: ArbitrationTimeoutNotPassed', () => C(req).resolveByTimeout.staticCall(id), 'ArbitrationTimeoutNotPassed', C(req));
|
||||
await refuza('S2 resolveDispute by a non-arbiter is refused: NotArbiter', () => C(req).resolveDispute.staticCall(id, false), 'NotArbiter', C(req));
|
||||
const before = await provider.getBalance(req.address);
|
||||
const r = await tx('resolveDispute(false) by the arbiter', C(arb).resolveDispute(id, false));
|
||||
const after = await provider.getBalance(req.address);
|
||||
cer('S2 the requester got the reward back + its bond back + the provider\'s slashed bond (0.018 tAERE)', after - before === E('0.018'), ethers.formatEther(after - before) + ' tAERE');
|
||||
await judecaSiSalveaza('S2', s, id);
|
||||
s.gasResolve = String(r.gasUsed);
|
||||
});
|
||||
await scenariu('S3', 'dispute lost: a correct result disputed in bad faith; the arbiter sides with the provider', async (spec, s) => {
|
||||
const { id } = await posteaza(spec); s.jobId = id;
|
||||
await tx('claimJob (provider, bond)', C(prov).claimJob(id, '0x', { value: E('0.004') }));
|
||||
const rez = ruleaza(spec);
|
||||
await tx('submitResult (provider, correct)', C(prov).submitResult(id, rezultatOcteti(rez)));
|
||||
await tx('dispute (requester, a made-up recomputed hash, bond)', C(req).dispute(id, '0x' + 'dd'.repeat(32), { value: E('0.004') }));
|
||||
const before = await provider.getBalance(prov.address);
|
||||
await tx('resolveDispute(true) by the arbiter', C(arb).resolveDispute(id, true));
|
||||
const after = await provider.getBalance(prov.address);
|
||||
cer('S3 the provider received reward + its bond + the challenger\'s forfeited bond (0.018 tAERE)', after - before === E('0.018'), ethers.formatEther(after - before) + ' tAERE');
|
||||
await judecaSiSalveaza('S3', s, id);
|
||||
});
|
||||
await scenariu('S4', 'deadline: claimed, never delivered; the requester reclaims after the deadline, the bond is slashed to it', async (spec, s) => {
|
||||
const { id, deadline } = await posteaza(spec, { deadlineIn: 45 }); s.jobId = id;
|
||||
await tx('claimJob (provider, bond)', C(prov).claimJob(id, '0x', { value: E('0.004') }));
|
||||
await refuza('S4 reclaimExpired before the deadline is refused: DeadlineNotPassed', () => C(req).reclaimExpired.staticCall(id), 'DeadlineNotPassed', C(req));
|
||||
await asteapta(deadline + 1, 'the deadline');
|
||||
await refuza('S4 submitResult after the deadline is refused: DeadlinePassed', () => C(prov).submitResult.staticCall(id, rezultatOcteti(ruleaza(spec))), 'DeadlinePassed', C(prov));
|
||||
const before = await provider.getBalance(req.address);
|
||||
const r = await tx('reclaimExpired (requester)', C(req).reclaimExpired(id));
|
||||
const after = await provider.getBalance(req.address); const gaz = r.gasUsed * (r.gasPrice || r.effectiveGasPrice || 0n);
|
||||
cer('S4 the requester got the reward + the slashed provider bond (0.014 tAERE, minus its own gas)', after - before + gaz === E('0.014'), ethers.formatEther(after - before + gaz) + ' tAERE net of gas');
|
||||
await judecaSiSalveaza('S4', s, id);
|
||||
});
|
||||
await scenariu('S5', 'an open job past its deadline: a claim is refused, the requester cancels', async (spec, s) => {
|
||||
const { id, deadline } = await posteaza(spec, { deadlineIn: 30 }); s.jobId = id;
|
||||
await asteapta(deadline + 1, 'the deadline');
|
||||
await refuza('S5 claimJob after the deadline is refused: DeadlinePassed', () => C(prov).claimJob.staticCall(id, '0x', { value: E('0.004') }), 'DeadlinePassed', C(prov));
|
||||
await tx('cancelJob (requester)', C(req).cancelJob(id));
|
||||
await judecaSiSalveaza('S5', s, id);
|
||||
});
|
||||
} finally {
|
||||
dovada.rezumat = { verzi, rosii }; const out = path.join(DOVEZI, 'piata-28001-' + new Date().toISOString().replace(/[:.]/g, '').slice(0, 15) + 'Z.json');
|
||||
fs.writeFileSync(out, JSON.stringify(dovada, null, 1)); console.log('\n evidence: ' + path.relative(RAD, out));
|
||||
console.log(`market settlement on 28001: ${verzi} VERDE, ${rosii} ROSU`);
|
||||
process.exitCode = rosii ? 1 : 0;
|
||||
}
|
||||
55
market-settlement/sarcina.mjs
Normal file
55
market-settlement/sarcina.mjs
Normal file
@ -0,0 +1,55 @@
|
||||
// sarcina.mjs: the REPLAY workload of the market flow (roadmap point 28): a deterministic task anyone can recompute, so a challenger
|
||||
// can dispute a wrong result with the hash it recomputed and an arbiter (or any reader) can see who is right. keccak-chain: start from a
|
||||
// 32-byte seed and apply keccak256 `iters` times. The spec is public (its keccak256 is the job's specHash); the result is the JSON
|
||||
// bytes { task, seed, iters, digest } whose keccak256 is the job's resultHash. Only node:crypto: keccak-256 is the dependency-free
|
||||
// BigInt implementation of the Cloud gateway (cloud-gateway/gateway.mjs), copied here so the public component has no package to install;
|
||||
// its positive control is the recorded testnet evidence itself (resultHash computed on chain = the replay here, proba-judecata.mjs S1/S3).
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
// ----- keccak-256 (FIPS 202 permutation, keccak padding 0x01..0x80, rate 136) -----
|
||||
const MASK = (1n << 64n) - 1n;
|
||||
const RC = [0x0000000000000001n, 0x0000000000008082n, 0x800000000000808an, 0x8000000080008000n, 0x000000000000808bn, 0x0000000080000001n, 0x8000000080008081n, 0x8000000000008009n,
|
||||
0x000000000000008an, 0x0000000000000088n, 0x0000000080008009n, 0x000000008000000an, 0x000000008000808bn, 0x800000000000008bn, 0x8000000000008089n, 0x8000000000008003n,
|
||||
0x8000000000008002n, 0x8000000000000080n, 0x000000000000800an, 0x800000008000000an, 0x8000000080008081n, 0x8000000000008080n, 0x0000000080000001n, 0x8000000080008008n];
|
||||
const ROT = [[0, 36, 3, 41, 18], [1, 44, 10, 45, 2], [62, 6, 43, 15, 61], [28, 55, 25, 21, 56], [27, 20, 39, 8, 14]];
|
||||
const rotl = (v, n) => (n === 0 ? v : (((v << BigInt(n)) | (v >> BigInt(64 - n))) & MASK));
|
||||
function keccakF(A) {
|
||||
for (let rr = 0; rr < 24; rr++) {
|
||||
const C = new Array(5), D = new Array(5);
|
||||
for (let x = 0; x < 5; x++) C[x] = A[x] ^ A[x + 5] ^ A[x + 10] ^ A[x + 15] ^ A[x + 20];
|
||||
for (let x = 0; x < 5; x++) D[x] = C[(x + 4) % 5] ^ rotl(C[(x + 1) % 5], 1);
|
||||
for (let x = 0; x < 5; x++) for (let y = 0; y < 5; y++) A[x + 5 * y] ^= D[x];
|
||||
const B = new Array(25);
|
||||
for (let x = 0; x < 5; x++) for (let y = 0; y < 5; y++) B[y + 5 * ((2 * x + 3 * y) % 5)] = rotl(A[x + 5 * y], ROT[x][y]);
|
||||
for (let x = 0; x < 5; x++) for (let y = 0; y < 5; y++) A[x + 5 * y] = B[x + 5 * y] ^ ((~B[((x + 1) % 5) + 5 * y] & MASK) & B[((x + 2) % 5) + 5 * y]);
|
||||
A[0] ^= RC[rr];
|
||||
}
|
||||
}
|
||||
export function keccak256(bytes) {
|
||||
const rate = 136; const A = new Array(25).fill(0n); const msg = Buffer.from(bytes);
|
||||
const padded = Buffer.concat([msg, Buffer.alloc(rate - (msg.length % rate))]);
|
||||
padded[msg.length] |= 0x01; padded[padded.length - 1] |= 0x80;
|
||||
for (let off = 0; off < padded.length; off += rate) { for (let i = 0; i < rate / 8; i++) A[i] ^= padded.readBigUInt64LE(off + i * 8); keccakF(A); }
|
||||
const out = Buffer.alloc(32); for (let i = 0; i < 4; i++) out.writeBigUInt64LE(A[i], i * 8); return out;
|
||||
}
|
||||
|
||||
export const TASK = 'keccak-chain';
|
||||
const hex = (b) => '0x' + Buffer.from(b).toString('hex');
|
||||
export const keccak = (b) => hex(keccak256(b));
|
||||
/** Canonical JSON of a spec or a result: keys sorted, no whitespace. */
|
||||
export const canonical = (x) => JSON.stringify(x, Object.keys(x).sort());
|
||||
export function specNou({ iters = 20000, seed = hex(crypto.randomBytes(32)) } = {}) { return { task: TASK, seed, iters }; }
|
||||
export const specHash = (spec) => keccak(Buffer.from(canonical(spec), 'utf8'));
|
||||
/** Runs the task: the digest after `iters` keccak256 applications to the seed. */
|
||||
export function ruleaza(spec) {
|
||||
if (spec.task !== TASK) throw new Error('unknown task ' + spec.task);
|
||||
if (!/^0x[0-9a-f]{64}$/.test(spec.seed) || !Number.isInteger(spec.iters) || spec.iters < 1 || spec.iters > 5e6) throw new Error('bad spec');
|
||||
let d = Buffer.from(spec.seed.slice(2), 'hex');
|
||||
for (let i = 0; i < spec.iters; i++) d = keccak256(d);
|
||||
return { task: TASK, seed: spec.seed, iters: spec.iters, digest: hex(d) };
|
||||
}
|
||||
/** The result bytes a provider submits (and a challenger recomputes), and their on-chain hash. */
|
||||
export const rezultatOcteti = (r) => Buffer.from(canonical(r), 'utf8');
|
||||
export const rezultatHash = (r) => keccak(rezultatOcteti(r));
|
||||
/** The SHA-256 of this program's own source, for the `programSha256` of the execution envelope. */
|
||||
export const programSha256 = () => '0x' + crypto.createHash('sha256').update(Buffer.from(ruleaza.toString() + keccak.toString(), 'utf8')).digest('hex');
|
||||
Loading…
Reference in New Issue
Block a user