OverviewFilesCommitsIssuesDocsReleasesPeers

pearbrowser-desktop / test / anongpt-buyer.test.js

184 lines · javascript ref:
import test from 'node:test'
import assert from 'node:assert/strict'
import { EventEmitter } from 'node:events'
import { readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import b4a from 'b4a'
import receiptMod from '../backend/anongpt-receipt.cjs'
import secp from '../backend/secp256k1-bundle.cjs'
import buyerMod from '../backend/anongpt-buyer.js'

const {
  RECEIPT_SCHEMA,
  hcjson,
  sha256Hex,
  signingMessage,
  receiptCore,
  verifyReceipt
} = receiptMod
const { AnongptBuyer } = buyerMod

const SELLER_SK = '11'.repeat(32)
const SELLER_PUB = secp.schnorrGetPublicKey(SELLER_SK)
const DIAL_KEY = '22'.repeat(32)
const INPUT = 'What is a peer-to-peer network?'
const TEXT = 'A network where peers exchange data directly.'
const RATE_CARD = { perCall: 50, perInputToken: 1, perOutputToken: 4 }

function wordCount (s) {
  return String(s).trim().split(/\s+/).filter(Boolean).length
}

function mintReceipt (o = {}) {
  const promptTokens = o.promptTokens ?? wordCount(o.input ?? INPUT)
  const outputTokens = o.outputTokens ?? wordCount(o.text ?? TEXT)
  const breakdown = o.breakdown ?? {
    base: RATE_CARD.perCall,
    input: promptTokens * RATE_CARD.perInputToken,
    output: outputTokens * RATE_CARD.perOutputToken
  }
  const core = {
    schema: RECEIPT_SCHEMA,
    requestId: 'req-1',
    sellerPubkey: SELLER_PUB,
    modelId: 'qvac-smollm2',
    modelDigest: sha256Hex('synthetic-model'),
    inputHash: sha256Hex(hcjson(o.input ?? INPUT)),
    outputHash: sha256Hex(String(o.text ?? TEXT)),
    promptTokens,
    outputTokens,
    rateCardId: 'hivemind/usdt-rate-card@1',
    breakdown,
    cost: breakdown.base + breakdown.input + breakdown.output,
    asset: 'USDT',
    payment: { rHash: 'b'.repeat(64), preimage: 'c'.repeat(64) },
    timestamp: 1700000000000
  }
  return {
    ...core,
    sellerSig: secp.schnorrSign(signingMessage(receiptCore(core)), SELLER_SK, '00'.repeat(32))
  }
}

async function runBuyerWith (serverResult, req = {}) {
  let activeProtocol = null

  class FakeServiceRegistry {}
  class FakeServiceProtocol extends EventEmitter {
    constructor () {
      super()
      activeProtocol = this
    }

    attach () {}
    destroy () {}

    async request (remotePubkey, service, method, payload) {
      assert.equal(remotePubkey, DIAL_KEY)
      assert.equal(service, 'ai')
      assert.equal(method, 'infer')
      assert.equal(payload.input, req.input || INPUT)
      return serverResult
    }
  }
  class FakeHyperswarm extends EventEmitter {
    join (key, opts) {
      assert.equal(b4a.toString(key, 'hex'), DIAL_KEY)
      assert.equal(opts.client, true)
      assert.equal(opts.server, false)
      setImmediate(() => activeProtocol.emit('channel-open', { remotePubkey: DIAL_KEY }))
    }

    async destroy () {}
  }

  const buyer = new AnongptBuyer({
    services: {
      ServiceRegistry: FakeServiceRegistry,
      ServiceProtocol: FakeServiceProtocol
    },
    dialTimeoutMs: 100,
    inferTimeoutMs: 100
  })
  buyer._resolveModules = () => ({
    ServiceRegistry: FakeServiceRegistry,
    ServiceProtocol: FakeServiceProtocol,
    Hyperswarm: FakeHyperswarm,
    b4a
  })
  return buyer.infer({
    input: INPUT,
    sellerPubkey: DIAL_KEY,
    rateCard: RATE_CARD,
    ...req
  })
}

test('HCJSON golden vector stays byte-stable for receipt signatures', () => {
  const core = {
    schema: 'hivemind/receipt@1.0',
    requestId: 'req-1',
    sellerPubkey: 'ab12',
    modelId: 'qvac-smollm2',
    promptTokens: 11,
    outputTokens: 22,
    breakdown: { base: 50, input: 11, output: 88 },
    cost: 149,
    asset: 'USDT',
    payment: { rHash: 'bb22', preimage: 'cc33' },
    timestamp: 1700000000000
  }
  const expected = '{"asset":"USDT","breakdown":{"base":50,"input":11,"output":88},"cost":149,"modelId":"qvac-smollm2","outputTokens":22,"payment":{"preimage":"cc33","rHash":"bb22"},"promptTokens":11,"requestId":"req-1","schema":"hivemind/receipt@1.0","sellerPubkey":"ab12","timestamp":1700000000000}'
  assert.equal(hcjson(core), expected)
  assert.equal(signingMessage(core), sha256Hex(expected))
})

test('verifyReceipt accepts an honest receipt and rejects output rebinding', async () => {
  const receipt = mintReceipt()
  assert.equal((await verifyReceipt(receipt, { input: INPUT, output: TEXT, rateCard: RATE_CARD })).ok, true)

  const tampered = await verifyReceipt(receipt, { input: INPUT, output: TEXT + ' extra', rateCard: RATE_CARD })
  assert.equal(tampered.ok, false)
  assert.equal(tampered.failedStep, 6)
})

test('AnongptBuyer returns locally verified success for a valid seller receipt', async () => {
  const receipt = mintReceipt()
  const result = await runBuyerWith({
    text: TEXT,
    promptTokens: receipt.promptTokens,
    tokens: receipt.outputTokens,
    tokensPerSecond: 12,
    backendDevice: 'qvac/cpu',
    verify: { ok: false, reason: 'seller-supplied-value-must-be-ignored' },
    receipt
  })

  assert.equal(result.ok, true)
  assert.equal(result.verify.ok, true, result.verify.detail)
  assert.equal(result.result.receipt, receipt)
})

test('AnongptBuyer keeps a successful but forged answer untrusted', async () => {
  const receipt = mintReceipt({ text: TEXT })
  const result = await runBuyerWith({
    text: TEXT + ' swapped',
    promptTokens: receipt.promptTokens,
    tokens: receipt.outputTokens,
    tokensPerSecond: 12,
    backendDevice: 'qvac/cpu',
    receipt
  })

  assert.equal(result.ok, true)
  assert.equal(result.verify.ok, false)
  assert.equal(result.verify.failedStep, 6)
})

test('browser anonGPT shim fails closed if local verify metadata is missing', () => {
  const bridgePath = fileURLToPath(new URL('../backend/pear-bridge.js', import.meta.url))
  const bridgeSource = readFileSync(bridgePath, 'utf8')
  assert.match(bridgeSource, /missing-local-verify/)
  assert.match(bridgeSource, /json\.verify\.ok = false/)
})