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)
This commit is contained in:
109
tests/unit/buyer-wallet-fixture-ed25519.test.ts
Normal file
109
tests/unit/buyer-wallet-fixture-ed25519.test.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user