// BOLT 12 decode + signature verification against a REAL offer/invoice pair // (fetched from a live CLN via fetchinvoice) plus tamper checks. // Run: bun tools/bolt12-test.js [offer-file invoice-file] // Default fixtures: regtest clc offer, invoice fetched by cln-ark for 12345 msat. import { readFileSync } from 'node:fs'; import { decodeOffer, decodeBolt12Invoice, verifyOfferInvoice, encodeBech32Raw, decodeBech32Raw, } from '../src/bolt12.js'; let fails = 0; const check = (name, ok, detail = '') => { console.log(` ${ok ? '✓' : '✗'} ${name}${detail ? ' — ' + String(detail).slice(0, 140) : ''}`); if (!ok) fails++; }; const throws = (fn) => { try { fn(); return null; } catch (e) { return e.message; } }; const [offerFile, invoiceFile] = process.argv.slice(2); const fixture = (name) => new URL(`fixtures/${name}`, import.meta.url).pathname; const offerStr = readFileSync(offerFile || fixture('bolt12-offer.txt'), 'utf8').trim(); const invStr = readFileSync(invoiceFile || fixture('bolt12-invoice.txt'), 'utf8').trim(); console.log('[codec]'); const rt = decodeBech32Raw(invStr); check('lni hrp', rt.hrp === 'lni'); check('bech32 roundtrips', encodeBech32Raw('lni', rt.bytes) === invStr.toLowerCase()); console.log('\n[offer]'); const offer = decodeOffer(offerStr); check('decodes', !!offer.records.length, `${offer.records.length} records`); check('network known', ['mainnet', 'testnet', 'signet', 'regtest'].includes(offer.network), offer.network); check('describes itself', typeof offer.description === 'string', offer.description); console.log('\n[invoice]'); const inv = decodeBolt12Invoice(invStr); check('payment hash present', /^[0-9a-f]{64}$/.test(inv.paymentHash || ''), inv.paymentHash); check('amount = 12345 msat', inv.amountMsat === 12345n, String(inv.amountMsat)); check('amount rounds up to 13 sat', inv.amountSat === 13); check('signer named', /^[0-9a-f]{66}$/.test(inv.nodeId || ''), inv.nodeId); check('same network as the offer', inv.network === offer.network, inv.network); check('expiry sane', inv.expiresAt > inv.createdAt * 1000, new Date(inv.expiresAt).toISOString()); console.log('\n[verification]'); check('real invoice verifies', throws(() => verifyOfferInvoice(offer, inv)) === null, throws(() => verifyOfferInvoice(offer, inv)) || 'ok'); // tamper with the amount TLV: signature must fail const tampered = decodeBolt12Invoice(invStr); const amtRec = tampered.records.find((r) => r.type === 170); amtRec.raw[amtRec.raw.length - 1] ^= 1; check('tampered amount rejected', /signature/.test(throws(() => verifyOfferInvoice(offer, tampered)) || '')); // swap in a different offer description: mirror check must fail const wrongOffer = decodeOffer(offerStr); const descRec = wrongOffer.records.find((r) => r.type === 10); if (descRec) { descRec.raw[descRec.raw.length - 1] ^= 1; check('mismatched offer rejected', /match/.test(throws(() => verifyOfferInvoice(wrongOffer, inv)) || '')); } // forged signer: issuer pin must fail const forged = decodeBolt12Invoice(invStr); forged.nodeId = '02' + '11'.repeat(32); check('wrong signer rejected', /wrong node|signature/.test(throws(() => verifyOfferInvoice(offer, forged)) || '')); console.log(fails ? `\n❌ ${fails} failure(s)` : '\n✅ bolt12 verifies'); process.exit(fails ? 1 : 0); h=staging&id=80fb493092de48dcfd9358c1e5cc6752665dc5ad'>diffstats
path: root/compose.yml.sample
blob: 2fc90b1bb4b626bf8308a3295e39e41a9a973921 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
version: '3.6'
services:
  app:
    image: asoltys/coinos-server
    container_name: app
    depends_on:
      - db
      - bc
      - cl
    command: "bun run dev"
    environment:
      NODE_OPTIONS: "--import ./hooks.js"
      NODE_ENV: development
      URL: http://localhost:5173
    ports:
      - '3119:3119'
    volumes:
      - .aws/:/root/.aws
      - ./:/app
      - ./logs:/logs
    restart: always
  bc:
    image: asoltys/bitcoin
    container_name: bc
    ports:
      - '18443:18443'
    volumes:
      - ./data/bitcoin:/home/bitcoin/.bitcoin
    restart: always
  lq:
    image: asoltys/liquid:23.x
    container_name: lq
    volumes:
      - ./data/liquid:/home/elements/.elements
    restart: always
    ports:
      - 7043:7040
  cl:
    image: asoltys/cln
    container_name: cl
    environment:
      LIGHTNINGD_NETWORK: regtest
    volumes:
      - ./data/lightning:/root/.lightning
    restart: always
  clb:
    image: elementsproject/lightningd:v23.11.1-amd64
    container_name: clb
    environment:
      LIGHTNINGD_NETWORK: regtest
    volumes:
      - ./data/lightningb:/root/.lightning
    restart: always
  clc:
    image: elementsproject/lightningd:v23.11.1-amd64
    container_name: clc
    environment:
      LIGHTNINGD_NETWORK: regtest
    volumes:
      - ./data/lightningc:/root/.lightning
    restart: always
  db:
    image: eqalpha/keydb:alpine_x86_64_v6.3.4
    container_name: db
    ports:
      - '6379:6379'
    volumes:
      - ./data/db:/data
      - ./data/db/keydb.conf:/etc/keydb/keydb.conf
    restart: always
  archive:
    image: eqalpha/keydb:alpine_x86_64_v6.3.4
    container_name: archive
    ports:
      - '6380:6379'
    volumes:
      - ./data/archive:/data
    restart: always
  nostr:
    container_name: nostr
    image: asoltys/nostr-rs-relay
    restart: always
    user: 100:100
    volumes:
      - "./data/nostr/data:/usr/src/app/db" 
      - "./data/nostr/config.toml:/usr/src/app/config.toml" 
    ports:
      - '8082:8080'