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)
110 lines
4.4 KiB
TypeScript
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);
|
|
});
|
|
});
|