Files
agents-marketplace/tests/unit/buyer-wallet-fixture-ed25519.test.ts
jelisaveta-talbot cbab81efbd M1-F: end-to-end LLM marketplace lifecycle on Vector testnet
Bonded-escrow marketplace for prompt → response between buyers and
local-LLM suppliers, settling on Vector (UTxO) via Aiken Plutus V3
validators. Lifecycle PostEscrow → Claim → Submit → Accept proven on
Vector testnet 2026-05-06; AcceptEscrow tx 125e3cfe…7dc100d distributes
payment + bonds per the validator's distribution rule.

Workspace layout (pnpm + TypeScript):
  contracts/marketplace/      Aiken validators (advert + escrow) + plutus.json
  packages/shared/            Tx-builders (lucid-evolution), CBOR codecs,
                              chain providers (mock / read-only / live Ogmios)
  supplier/                   OpenAI-compatible HTTP node (Ollama by default;
                              vLLM / llama.cpp / OpenRouter / OpenClaw all
                              swap-in via OLLAMA_URL); 202+poll chat handler
  buyer/                      Express + React SPA; server-side runs the full
                              chain lifecycle via /v1/submit-prompt and
                              /v1/accept; SPA never holds the buyer key
  indexer/                    Chain-follower (extends apex-dashboard pattern),
                              SSE event stream + REST queries
  indexer-ui/                 Operator dashboard (events + suppliers + escrow
                              lookup)
  tests/                      1098 vitest unit + integration tests (10 skipped
                              under M1-F-D-defer for UPLC test plumbing)
  deploy/testnet/             Per-service docker-compose for the Hetzner stack
  docs/EXECUTIVE_SUMMARY.*    Marp deck (PDF + PPTX) — 11 slides, mermaid
                              diagrams pre-rendered for portable export
  docs/CLOUDFLARE_WAF_BYPASS.md  CF Skip-rule runbook for /v1/* paths
  docs/ARCHITECTURE.md        canonical spec

Live demo (testnet, 2026-05-06):
  https://mp-buyers.vector.testnet.apexfusion.org      (buyer SPA)
  https://mp-suppliers.vector.testnet.apexfusion.org   (supplier API)
  https://mp-indexer.vector.testnet.apexfusion.org     (indexer + UI)
2026-05-06 13:27:48 +02:00

110 lines
4.4 KiB
TypeScript

/**
* buyer-wallet-fixture-ed25519.test.ts — RED phase (M1-E)
*
* Asserts that the buyer-side wallet fixture has a valid 32-byte Ed25519 key pair
* where BUYER_PUB_KEY_HEX is correctly derived from BUYER_PRIVATE_KEY_HEX.
*
* Per ARCHITECTURE.md §9 open follow-up #7:
* "Buyer-side wallet fixture has invalid Ed25519 priv (62 hex chars / 31 bytes;
* tests/fixtures/buyer-side/wallet-keys.ts:32)."
*
* These tests FAIL until Catherine applies the same priv → pub → pkh → bech32
* derivation she used for the supplier in M1-C and adds "// SPEC FIX 2026-04-25"
* comments to wallet-keys.ts.
*
* NOTE: Do NOT edit wallet-keys.ts in M1-E-red. These tests drive Catherine's fix.
*
* We use the signReceipt / verifyReceipt round-trip from @marketplace/shared
* (which already has @noble/ed25519 wired) rather than importing @noble/ed25519
* directly (the package is a transitive dep, not a root devDep).
*/
import { describe, it, expect } from "vitest";
import { signReceipt } from "../../packages/shared/src/receipt/sign.js";
import { verifyReceipt } from "../../packages/shared/src/receipt/verify.js";
import { buildReceipt } from "../../packages/shared/src/receipt/build.js";
import {
BUYER_PRIVATE_KEY_HEX,
BUYER_PUB_KEY_HEX,
BUYER_PKH,
BUYER_ADDRESS_TESTNET,
buildBuyerWalletKey,
} from "../fixtures/buyer-side/wallet-keys.js";
// A minimal receipt for sign/verify round-trip testing.
function makeTestReceipt(escrowRef: string) {
return buildReceipt({
prompt_hash: "a".repeat(64),
response_hash: "b".repeat(64),
model: "qwen2.5:0.5b",
prompt_tokens: 4,
completion_tokens: 4,
wallclock_ms: 100,
supplier_pkh: "abcdef0123456789abcdef0123456789abcdef0123456789abcdef01",
escrow_ref: escrowRef,
});
}
describe("buyer-side wallet fixture — Ed25519 key derivation validity", () => {
it("BUYER_PRIVATE_KEY_HEX is exactly 64 hex chars (32 bytes)", () => {
// This test PASSES even on the unfixed fixture IF the value is already 64 chars.
// The real RED test below validates the priv → pub derivation.
expect(BUYER_PRIVATE_KEY_HEX).toHaveLength(64);
expect(BUYER_PRIVATE_KEY_HEX).toMatch(/^[0-9a-fA-F]{64}$/);
});
it("BUYER_PUB_KEY_HEX is exactly 64 hex chars (32 bytes)", () => {
expect(BUYER_PUB_KEY_HEX).toHaveLength(64);
expect(BUYER_PUB_KEY_HEX).toMatch(/^[0-9a-fA-F]{64}$/);
});
/**
* RED until Catherine fixes the buyer priv key.
* The buyer private key (BUYER_PRIVATE_KEY_HEX) is currently 64 hex chars
* but is NOT a valid Ed25519 key — signReceipt will throw because
* @noble/ed25519 v2 rejects keys whose scalar is not in the valid range,
* or because the derived public key won't match BUYER_PUB_KEY_HEX.
*
* Fix: derive a real Ed25519 key pair using:
* priv = sha256("buyer-test-seed-m1b") padded to 32 bytes
* pub = ed.getPublicKey(priv)
* then update BUYER_PRIVATE_KEY_HEX, BUYER_PUB_KEY_HEX, BUYER_PKH, BUYER_ADDRESS_TESTNET
* with "// SPEC FIX 2026-04-25" comments.
*/
it("BUYER_PUB_KEY_HEX corresponds to BUYER_PRIVATE_KEY_HEX (sign/verify round-trip)", () => {
const escrowRef = "c".repeat(64) + "#0";
const receipt = makeTestReceipt(escrowRef);
// signReceipt uses BUYER_PRIVATE_KEY_HEX to sign
const signed = signReceipt(receipt, BUYER_PRIVATE_KEY_HEX);
// verifyReceipt uses BUYER_PUB_KEY_HEX — must return true iff priv → pub is valid
const valid = verifyReceipt(signed, BUYER_PUB_KEY_HEX);
expect(valid).toBe(true);
});
it("BUYER_PKH is 56 hex chars (28 bytes)", () => {
expect(BUYER_PKH).toHaveLength(56);
expect(BUYER_PKH).toMatch(/^[0-9a-fA-F]{56}$/);
});
it("BUYER_ADDRESS_TESTNET starts with addr_test1", () => {
expect(BUYER_ADDRESS_TESTNET).toMatch(/^addr_test1/);
});
it("buildBuyerWalletKey() returns a WalletKey with all four required fields", () => {
const key = buildBuyerWalletKey();
expect(key).toHaveProperty("pubKeyHash");
expect(key).toHaveProperty("pubKeyHex");
expect(key).toHaveProperty("privateKeyHex");
expect(key).toHaveProperty("address");
});
it("buyer WalletKey sign/verify round-trip via signReceipt/verifyReceipt", () => {
const key = buildBuyerWalletKey();
const escrowRef = "d".repeat(64) + "#0";
const receipt = makeTestReceipt(escrowRef);
const signed = signReceipt(receipt, key.privateKeyHex);
// Must pass iff pubKeyHex is derived from privateKeyHex
expect(verifyReceipt(signed, key.pubKeyHex)).toBe(true);
});
});