From 64531e48e08f9e7eb52c66156473b246cb3bbd53 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 29 Sep 2022 21:18:35 -0700 Subject: lowercase 2fa error message --- lib/passport.js | 2 +- lib/upload.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/passport.js b/lib/passport.js index 8dac27c0..ac2949a5 100644 --- a/lib/passport.js +++ b/lib/passport.js @@ -70,7 +70,7 @@ export const adminAuth = { "jwt", { session: false }, function(req, res, err, user, info) { - if (!(user && user.admin)) return res.status(401).send("Unauthorized"); + if (!(user && user.admin)) return res.code(401).send("Unauthorized"); req.user = user; } ) diff --git a/lib/upload.js b/lib/upload.js index ceead928..a91e4eba 100644 --- a/lib/upload.js +++ b/lib/upload.js @@ -43,6 +43,6 @@ app.post("/upload/:type", auth, async function(req, res) { res.send(name); } catch (e) { console.log(e); - return res.status(500).send(e); + return res.code(500).send(e); } }); -- cgit v1.2.3 From 4ee46fee0cbf23f51228c7c4895ee1e0565b074f Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 30 Sep 2022 15:07:30 -0700 Subject: derive address/payreq server side --- lib/send.js | 32 +++++++--------- lib/utils.js | 84 +++++++++++++++++++++++++++++++++++++++++ routes/invoices.js | 86 +++++++++++++++++++++++++++--------------- routes/payments.js | 1 - routes/users.js | 108 ----------------------------------------------------- 5 files changed, 152 insertions(+), 159 deletions(-) (limited to 'lib') diff --git a/lib/send.js b/lib/send.js index f20185e5..e7255219 100644 --- a/lib/send.js +++ b/lib/send.js @@ -66,10 +66,11 @@ export default async (amount, memo, hash, user) => { }); conversionFeeDeduction = Math.min(account.lightning_credits, conversionFee); + total = amount + conversionFee - conversionFeeDeduction; if ( !account || - account.balance < amount + conversionFee - conversionFeeDeduction + account.balance < total ) { warn( "user attempting to send more than they have", @@ -81,12 +82,12 @@ export default async (amount, memo, hash, user) => { } await account.decrement( - { balance: amount, lightning_credits: conversionFeeDeduction }, + { balance: total, lightning_credits: conversionFeeDeduction }, { transaction } ); await account.reload({ transaction }); - l("debited account", account.id); + l("debited account", account.id, total); let tokens; if (!payreq.satoshis) tokens = amount; @@ -100,26 +101,22 @@ export default async (amount, memo, hash, user) => { if (!m) throw new Error(`lightning payment failed ${amount} ${hash}`); if (m.payment_error) throw new Error(m.payment_error); - ({ fee, secret: preimage, tokens: total } = m); + ({ fee, secret, tokens: total } = m); + preimage = secret.toString("hex"); } else { - m = await ln.pay(hash, tokens ? `${tokens}sats` : undefined); - let sent; - ({ - msatoshi: total, - msatoshi_sent: sent, - payment_preimage: preimage - } = m); - - fee = parseInt((sent - total) / 1000); - total = parseInt(total / 1000); + let { msatoshi, msatoshi_sent, payment_preimage } = await ln.pay(hash, tokens ? `${tokens}sats` : undefined); + preimage = payment_preimage; + fee = parseInt((msatoshi_sent - msatoshi) / 1000); + total = parseInt(msatoshi / 1000); } - preimage = preimage.toString("hex"); - if (store.seen.includes(preimage)) { warn("duplicate payment detected", preimage); throw new Error("Duplicate payment detected"); } + + store.seen.push(preimage); + store.seen.push(hash); }); await db.transaction(async transaction => { @@ -226,9 +223,6 @@ export default async (amount, memo, hash, user) => { emit(user.username, "account", payment.account); emit(user.username, "payment", payment); - store.seen.push(preimage); - store.seen.push(hash); - l("sent lightning", user.username, total - fee, fee); }); diff --git a/lib/utils.js b/lib/utils.js index 1ff24c67..036ccd6d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -1,5 +1,13 @@ +import config from "$config"; import db from "$db"; import sequelize from "@sequelize/core"; +import { fromBase58 } from "bip32"; +import { payments as bcPayments, networks as bcNetworks } from "bitcoinjs-lib"; +import { payments as lqPayments, networks as lqNetworks } from "liquidjs-lib"; +import { createInvoice } from "lightning"; +import lnd from "$lib/lnd"; +import ln from "$lib/ln"; + const { Op } = sequelize; export const getUser = async (username, transaction) => { @@ -90,3 +98,79 @@ export const getBlockHeight = message => { readUInt32(); return readUInt32(); }; + +export const deriveAddress = async (account, type) => { + let { index, pubkey, path, network } = account; + let n = { bitcoin: bcNetworks, liquid: lqNetworks }[network][ + prod ? "bitcoin" : "regtest" + ]; + let p = { bitcoin: bcPayments, liquid: lqPayments }[network]; + let root = fromBase58(pubkey, n); + let parts = path.split("/"); + let hd = root.derive(parseInt(parts[parts.length - 1])); + + type = { + bech32: "p2wpkh", + "p2sh-segwit": "p2sh", + legacy: "p2pkh" + }[type]; + + console.log("TYPE", type); + + let r; + if (type !== "p2sh") { + r = p[type]({ + pubkey: hd.publicKey, + network: n + }); + } else { + r = p[type]({ + redeem: p.p2wpkh({ + pubkey: hd.publicKey, + network: n + }), + network: n + }); + } + + index++; + account.index = index; + account.address = r.address; + await account.save(); + + return r; +}; + +export const bip21 = ({ address, amount, memo, tip, network }, { asset }) => { + if (network === "liquid") network = "liquidnetwork"; + + let url = amount || memo ? `${network}:${address}?` : address; + if (amount) + url += `amount=${((amount + tip) / SATS).toFixed(8)}${memo ? "&" : ""}`; + if (memo) url += `message=${memo}`; + + if (network === "liquidnetwork" && amount) { + url += `&asset=${asset}`; + } + + return url; +}; + +export const derivePayRequest = async ({ amount, memo, tip }) => { + if (!tip) tip = 0; + let value = amount + tip; + + if (config.lna.clightning) { + if (!memo) memo = "coinos"; + return (await ln.invoice(value ? `${value}sat` : "any", new Date(), memo, 360)) + .bolt11; + } else { + return ( + await createInvoice({ + lnd, + tokens: value, + description: memo + }) + ).request; + } +}; diff --git a/routes/invoices.js b/routes/invoices.js index b5926fec..9f93180d 100644 --- a/routes/invoices.js +++ b/routes/invoices.js @@ -5,6 +5,9 @@ import { optionalAuth } from "$lib/passport"; import { Op } from "@sequelize/core"; import axios from "axios"; import { err, l } from "$lib/logging"; +import bc from "$lib/bitcoin"; +import lq from "$lib/liquid"; +import { SATS, bip21, deriveAddress, derivePayRequest } from "$lib/utils"; app.get("/invoice", async (req, res, next) => { try { @@ -27,15 +30,12 @@ app.get("/invoice", async (req, res, next) => { app.post("/invoice", optionalAuth, async (req, res, next) => { try { - let { liquidAddress, id, invoice, user, tx } = req.body; - let { blindkey } = invoice; + let { type = "bech32", liquidAddress, id, invoice, user, tx } = req.body; + let { blindkey, currency, tip, amount, rate, network } = invoice; + let address, unconfidential, text; - if (invoice.amount < 0) throw new Error("amount out of range"); - if ( - invoice.tip > invoice.amount || - invoice.tip > 1000000 || - invoice.tip < 0 - ) + if (amount < 0) throw new Error("amount out of range"); + if (tip > amount || tip > 1000000 || tip < 0) throw new Error("tip amount out of range"); if (liquidAddress) { @@ -48,18 +48,56 @@ app.post("/invoice", optionalAuth, async (req, res, next) => { user = await db.User.findOne({ where: { username: user.username + }, + include: { + model: db.Account, + as: "account" } }); } if (!user) throw new Error("user not provided"); - if (!invoice.currency) invoice.currency = user.currency; - if (!invoice.rate) invoice.rate = store.rates[invoice.currency]; - if (invoice.tip > invoice.amount || invoice.tip > 1000000) - throw new Error("tip is too large"); - if (invoice.tip < 0 || invoice.amount < 0) - throw new Error("invalid amount"); - invoice.user_id = user.id; - invoice.account_id = user.account_id; + if (!currency) currency = user.currency; + if (!rate) rate = store.rates[currency]; + if (tip > amount || tip > 1000000) throw new Error("tip is too large"); + if (tip < 0 || amount < 0) throw new Error("invalid amount"); + + if (user.account.pubkey) { + let { address, confidentialAddress } = await deriveAddress( + user.account, + type + ); + if (confidentialAddress) { + address = confidentialAddress; + unconfidential = address; + } else { + address = address; + } + } else if (network !== "lightning") { + console.log(network, { bitcoin: bc, liquid: lq }[network]) + address = await { bitcoin: bc, liquid: lq }[network].getNewAddress(); + if (network === "liquid") + ({ unconfidential } = await lq.getAddressInfo(address)); + } + + invoice = { + ...invoice, + account_id: user.account.id, + address, + amount, + currency, + rate, + tip, + unconfidential, + user_id: user.id + }; + + if (network === "lightning") { + invoice.text = await derivePayRequest(invoice); + } else { + invoice.text = bip21(invoice, user.account); + } + + console.log("TEXT", invoice.text) l( "creating invoice", @@ -73,21 +111,7 @@ app.post("/invoice", optionalAuth, async (req, res, next) => { if (!invoice.tip) invoice.tip = 0; - const exists = - invoice.text && - (await db.Invoice.findOne({ - where: { - [Op.or]: { - address: invoice.address || "", - unconfidential: invoice.unconfidential || "", - text: invoice.text - } - } - })); - - invoice = exists - ? await exists.update(invoice) - : await db.Invoice.create(invoice); + invoice = await db.Invoice.create(invoice); store.addresses[invoice.address] = user.username; if (invoice.unconfidential) { store.addresses[invoice.unconfidential] = user.username; diff --git a/routes/payments.js b/routes/payments.js index 298309e0..8b91d9fb 100644 --- a/routes/payments.js +++ b/routes/payments.js @@ -58,7 +58,6 @@ app.get("/except", adminAuth, (req, res) => { }); app.post("/lightning/channel", lnRoutes.channel); -app.post("/lightning/invoice", lnRoutes.invoice); app.post("/lightning/query", auth, lnRoutes.query); app.post("/lightning/send", auth, lnRoutes.send); diff --git a/routes/users.js b/routes/users.js index 61d5d33a..3063758d 100644 --- a/routes/users.js +++ b/routes/users.js @@ -360,114 +360,6 @@ app.post("/logout", optionalAuth, async (req, res) => { res.send({}); }); -app.get("/address", async (req, res) => { - let { network, type } = req.query; - let address, confidentialAddress; - - let paymentType = { - bech32: "p2wpkh", - "p2sh-segwit": "p2sh", - legacy: "p2pkh", - }[type]; - if (!paymentType) paymentType = "p2wpkh"; - - let hd, i, p, n; - const mutex = new Mutex(); - const release = await mutex.acquire(); - try { - if (network === "bitcoin") { - if (!config.bitcoin) return res.code(500).send("Bitcoin not configured"); - - address = await bc.getNewAddress("", type); - store.bcAddressIndex = i + 1; - return res.send({ address }); - - p = bitcoin.payments; - n = prod ? bitcoin.networks["bitcoin"] : bitcoin.networks["regtest"]; - - i = parseInt(store.bcAddressIndex); - hd = fromBase58(config.bitcoin.masterkey, n).derivePath(`m/0'/0'/${i}'`); - - // async request to node to bump its internal index but don't use result - bc.getNewAddress().catch(console.error); - res.send({ address, confidentialAddress }); - - store.bcAddressIndex = i + 1; - } else if (network === "liquid") { - p = liquid.payments; - n = - liquid.networks[ - config.liquid.network === "mainnet" ? "liquid" : config.liquid.network - ]; - paymentType = "p2sh"; - - // async request to node to bump its internal index but don't use result - lq.getNewAddress().catch((e) => - warn("Problem bumping liquid address index", e.message) - ); - - i = parseInt(store.lqAddressIndex); - - if (!i) { - const { hdkeypath } = await lq.getAddressInfo(await lq.getNewAddress()); - const parts = hdkeypath.split("/"); - i = parseInt(parts[parts.length - 1].slice(0, -1)); - } - - l("liquid address index", i); - if (!i) throw new Error("Problem generating address"); - - hd = fromBase58(config.liquid.masterkey, n).derivePath(`m/0'/0'/${i}'`); - store.lqAddressIndex = i + 1; - } else { - throw new Error("Unsupported network"); - } - } finally { - release(); - } - - if (paymentType !== "p2sh") { - ({ address } = p[paymentType]({ - pubkey: hd.publicKey, - network: n, - })); - } else { - if (network === "liquid") { - const p2wpkh = p.p2wpkh({ - pubkey: hd.publicKey, - network: n, - }); - - const blindkey = fromPrivateKey( - Buffer.from(config.liquid.blindkey, "hex"), - hd.chainCode, - n - ); - - ({ address, confidentialAddress } = p[paymentType]({ - redeem: p2wpkh, - network: n, - blindkey: blindkey.publicKey, - })); - - lq.importBlindingKey( - confidentialAddress, - blindkey.privateKey.toString("hex") - ); - } else { - ({ address } = p[paymentType]({ - redeem: p.p2wpkh({ - pubkey: hd.publicKey, - network: n, - }), - network: n, - })); - } - } - - res.send({ address, confidentialAddress }); -}); - app.post("/account", auth, async (req, res) => { const { user } = req; const { -- cgit v1.2.3 From 4e58ebca11ca68bfe08e3187e6cf13c01982bd4f Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Sat, 1 Oct 2022 16:40:24 -0700 Subject: increment invoice received --- docker-compose.yml | 16 --------------- lib/store.js | 1 + lib/sync.js | 50 ++++++++++++++++++++++++++++++++++++--------- routes/bitcoin/receive.js | 3 ++- routes/lightning/index.js | 4 +++- routes/lightning/parse.js | 20 ++++++++++++++++++ routes/lightning/receive.js | 2 +- routes/liquid/receive.js | 1 + routes/payments.js | 1 + 9 files changed, 69 insertions(+), 29 deletions(-) create mode 100644 routes/lightning/parse.js (limited to 'lib') diff --git a/docker-compose.yml b/docker-compose.yml index d492d56d..0c2f4f2b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,9 +23,6 @@ services: container_name: app image: gcr.io/coinos-326717/github.com/coinos/coinos-server:prod command: "pnpm dev" - ports: - - '3118:3118' - - '3119:3119' environment: NODE_OPTIONS: "--es-module-specifier-resolution=node --experimental-modules --loader ./loader.js --openssl-legacy-provider" NODE_ENV: development @@ -40,9 +37,6 @@ services: bitcoin: image: ghcr.io/vulpemventures/bitcoin container_name: bitcoin - ports: - - ${BITCOIN_PEER_PORT}:18442 - - ${BITCOIN_NODE_PORT}:18443 volumes: - ./config/bitcoin:/home/bitcoin/.bitcoin restart: unless-stopped @@ -52,11 +46,6 @@ services: liquid: image: ghcr.io/vulpemventures/elements container_name: liquid - ports: - - ${LIQUID_NODE_PORT}:7040 - - ${LIQUID_PEER_PORT}:18444 - - ${LIQUID_BLOCK_PORT}:18606 - - ${LIQUID_TX_PORT}:18607 volumes: - ./config/liquid:/home/elements/.elements restart: unless-stopped @@ -123,8 +112,6 @@ services: volumes: - ./config/bitcoin/:/config restart: unless-stopped - ports: - - 3095:3095 networks: default: ipv4_address: 172.18.0.28 @@ -154,9 +141,6 @@ services: - 0.0.0.0:60401 - --cors - "*" - ports: - - ${LIQUID_ELECTRS_RPC_PORT}:60401 - - ${LIQUID_ELECTRS_PORT}:3002 volumes: - ./config/liquid/:/config restart: unless-stopped diff --git a/lib/store.js b/lib/store.js index 1d7e4eef..6f2e47ce 100644 --- a/lib/store.js +++ b/lib/store.js @@ -7,6 +7,7 @@ export default { exceptions: [], issuances: {}, logins: {}, + nodes: [], seen: [], sessions: {}, sockets: {}, diff --git a/lib/sync.js b/lib/sync.js index 625d128d..d4900c58 100644 --- a/lib/sync.js +++ b/lib/sync.js @@ -9,6 +9,7 @@ import read from "./read"; import { Op } from "@sequelize/core"; import store from "./store"; import { l, err, warn } from "./logging"; +import ln from "./ln"; import lnd from "./lnd"; import { getInvoices } from "lightning"; import bc from "./bitcoin"; @@ -75,11 +76,11 @@ const sync = async () => { ] }); - let missed, invoices; + let missed, invoices, recent; if (config.lnd) { ({ invoices } = await getInvoices({ lnd, limit: 1000 })); - let recent = invoices + recent = invoices .filter( i => i.is_confirmed && @@ -91,13 +92,34 @@ const sync = async () => { pr: i.request, createdAt: new Date(i.created_at) })); + } else { + ({ invoices } = await ln.listinvoices()); + recent = invoices + .filter( + i => i.status === "paid" && Date.now() - i.paid_at < 60 * 60 * 1000 + ) + .map(i => ({ + amount: parseInt(Math.round(i.msatoshi_received / 1000)), + preimage: i.payment_preimage, + pr: i.bolt11, + createdAt: new Date(i.paid_at * 1000) + })); + } + + await db.transaction(async transaction => { const twoDaysAgo = new Date(new Date().setDate(new Date().getDate() - 2)); let settled = ( - await db.Payment.findAll({ - where: { network: "lightning", createdAt: { [Op.gt]: twoDaysAgo } }, - attributes: ["preimage"] - }) + await db.Payment.findAll( + { + where: { + network: "lightning", + createdAt: { [Op.gt]: twoDaysAgo } + }, + attributes: ["preimage"] + }, + transaction + ) ).map(p => p.preimage); missed = recent.filter(i => !settled.includes(i.preimage)); @@ -111,7 +133,8 @@ const sync = async () => { include: { model: db.User, as: "user" - } + }, + transaction }); if (invoice && invoice.user_id) { @@ -120,7 +143,8 @@ const sync = async () => { user_id: invoice.user_id, asset: config.liquid.btcasset, pubkey: null - } + }, + transaction }); if (account) { @@ -141,7 +165,8 @@ const sync = async () => { updatedAt: createdAt }); - await account.increment({ balance: amount }); + await invoice.increment({ received: amount }, { transaction }); + await account.increment({ balance: amount }, { transaction }); l("lightning missing from account", account.id, amount); } @@ -155,7 +180,7 @@ const sync = async () => { ); } } - } + }); const unconfirmed = await db.Payment.findAll({ where: { @@ -166,6 +191,10 @@ const sync = async () => { model: db.Account, as: "account" }, + { + model: db.Invoice, + as: "invoice" + }, { model: db.User, as: "user" @@ -200,6 +229,7 @@ const sync = async () => { l("confirming payment", p.id, p.user_id, p.hash, p.address); await p.save({ transaction }); + await p.invoice.increment({ received: p.amount }, { transaction }); await p.account.increment({ balance: p.amount }, { transaction }); await p.account.decrement({ pending: p.amount }, { transaction }); }); diff --git a/routes/bitcoin/receive.js b/routes/bitcoin/receive.js index 7451e868..3bc1750d 100644 --- a/routes/bitcoin/receive.js +++ b/routes/bitcoin/receive.js @@ -213,12 +213,13 @@ setInterval(async () => { if (p && p.user) user = p.user; if (p && p.account) { - ({ account } = p); + ({ account, invoice } = p); total = p.amount + p.tip; p.confirmed = 1; await account.save({ transaction }); + await invoice.increment({ received: total }, { transaction }); await account.increment({ balance: total }, { transaction }); await account.decrement( { pending: Math.min(account.pending, total) }, diff --git a/routes/lightning/index.js b/routes/lightning/index.js index 52a94398..b4611ef5 100644 --- a/routes/lightning/index.js +++ b/routes/lightning/index.js @@ -2,10 +2,12 @@ import channel from "./channel"; import invoice from "./invoice"; import query from "./query"; import send from "./send"; +import parse from "./parse"; export default { channel, invoice, query, - send + send, + parse }; diff --git a/routes/lightning/parse.js b/routes/lightning/parse.js new file mode 100644 index 00000000..1d960689 --- /dev/null +++ b/routes/lightning/parse.js @@ -0,0 +1,20 @@ +import ln from "$lib/ln"; +import store from "$lib/store"; + +export default async (req, res) => { + let { payreq } = req.body; + let hour = 1000 * 60 * 60; + let { last } = store.nodes; + let { nodes } = store; + + if (!last || last > Date.now() - hour) ({ nodes } = await ln.listnodes()); + store.nodes = nodes; + + + let twoWeeksAgo = new Date(new Date().setDate(new Date().getDate() - 14)); + let { msatoshi, payee } = await ln.decodepay(payreq); + let { alias } = nodes.find((n) => n.nodeid === payee); + let route = await ln.getroute(payee, msatoshi, 5); + + res.send({ alias, route }); +}; diff --git a/routes/lightning/receive.js b/routes/lightning/receive.js index 084161b5..898152fe 100644 --- a/routes/lightning/receive.js +++ b/routes/lightning/receive.js @@ -70,11 +70,11 @@ const handlePayment = async msg => { ); total = amount + tip; - invoice.received += total; invoice.status = "paid"; await invoice.save({ transaction }); + await invoice.increment({ received: total }, { transaction }); await account.increment({ balance: total }, { transaction }); // get the # of fee credits you would need to pay off this amount of bitcoin await account.increment( diff --git a/routes/liquid/receive.js b/routes/liquid/receive.js index 480677de..4ad4c7dc 100644 --- a/routes/liquid/receive.js +++ b/routes/liquid/receive.js @@ -344,6 +344,7 @@ setInterval(async () => { p.confirmed = 1; await p.account.save({ transaction }); + await p.invoice.increment({ received: total }, { transaction }); await p.account.increment({ balance: total }, { transaction }); await p.account.decrement( { pending: Math.min(p.account.pending, total) }, diff --git a/routes/payments.js b/routes/payments.js index 8b91d9fb..6c489372 100644 --- a/routes/payments.js +++ b/routes/payments.js @@ -57,6 +57,7 @@ app.get("/except", adminAuth, (req, res) => { res.send("updated exceptions"); }); +app.post("/lightning/parse", lnRoutes.parse); app.post("/lightning/channel", lnRoutes.channel); app.post("/lightning/query", auth, lnRoutes.query); app.post("/lightning/send", auth, lnRoutes.send); -- cgit v1.2.3