From e4def398885e5a92ecfe6f4ee54561243c15cac3 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 +- routes/users.js | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) 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); } }); diff --git a/routes/users.js b/routes/users.js index fecb5dff..1b2717e3 100644 --- a/routes/users.js +++ b/routes/users.js @@ -34,7 +34,7 @@ const twofa = (req, res, next) => { (typeof token === "undefined" || !authenticator.check(token, user.otpsecret)) ) { - return res.status(401).send("2FA required"); + return res.code(401).send("2fa required"); } else next(); }; @@ -345,7 +345,7 @@ let login = async (req, res) => { res.send({ user, token }); } catch (e) { err("login error", e.message, req.connection.remoteAddress); - res.status(401).send({}); + res.code(401).send({}); } }; -- cgit v1.2.3 From 50a853da91cdd651369277f53dcf48c04152f7cb Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 29 Sep 2022 21:20:15 -0700 Subject: lowercase 2fa error message --- routes/users.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/users.js b/routes/users.js index 1b2717e3..7db4b86e 100644 --- a/routes/users.js +++ b/routes/users.js @@ -327,7 +327,7 @@ let login = async (req, res) => { (typeof twofa === "undefined" || !authenticator.check(twofa, user.otpsecret)) ) { - return res.code(401).send("2FA required"); + return res.code(401).send("2fa required"); } l( -- cgit v1.2.3 From 37df429de9cedaf6be376c2eb8646ffecaa87f36 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 30 Sep 2022 04:28:07 +0000 Subject: fix twofa middleware --- routes/users.js | 192 ++++++++++++++++++++++++++------------------------------ 1 file changed, 89 insertions(+), 103 deletions(-) diff --git a/routes/users.js b/routes/users.js index 7db4b86e..61d5d33a 100644 --- a/routes/users.js +++ b/routes/users.js @@ -24,43 +24,29 @@ import register from "$lib/register"; const pick = (O, ...K) => K.reduce((o, k) => ((o[k] = O[k]), o), {}); -const twofa = (req, res, next) => { - let { - user, - body: { token } - } = req; - if ( - user.twofa && - (typeof token === "undefined" || - !authenticator.check(token, user.otpsecret)) - ) { - return res.code(401).send("2fa required"); - } else next(); -}; - app.get("/me", auth, async (req, res) => { try { - let user = req.user.get({ plain: true }); - let payments = await req.user.getPayments({ - where: { - account_id: user.account_id - }, - order: [["id", "DESC"]], - limit: 12, - include: [ - { - model: db.Account, - as: "account" + let user = req.user.get({ plain: true }); + let payments = await req.user.getPayments({ + where: { + account_id: user.account_id, }, - { - model: db.Payment, - as: "fee_payment" - } - ] - }); - user.accounts = await req.user.getAccounts(); - user.payments = payments; - res.send(user); + order: [["id", "DESC"]], + limit: 12, + include: [ + { + model: db.Account, + as: "account", + }, + { + model: db.Payment, + as: "fee_payment", + }, + ], + }); + user.accounts = await req.user.getAccounts(); + user.payments = payments; + res.send(user); } catch (e) { res.code(500).send(e.message); } @@ -74,7 +60,7 @@ app.get("/users/:username", async (req, res) => { where: Sequelize.where( Sequelize.fn("lower", Sequelize.col("username")), username.toLowerCase() - ) + ), }); if (user) res.send(user); @@ -91,8 +77,20 @@ app.post("/register", async (req, res) => { } }); -app.post("/disable2fa", auth, twofa, async (req, res) => { - let { user } = req; +app.post("/disable2fa", auth, async (req, res) => { + let { + user, + body: { token }, + } = req; + + if ( + user.twofa && + (typeof token === "undefined" || + !authenticator.check(token, user.otpsecret)) + ) { + return res.code(401).send("2fa required"); + } + user.twofa = false; await user.save(); emit(user.username, "user", user); @@ -101,12 +99,6 @@ app.post("/disable2fa", auth, twofa, async (req, res) => { res.send({}); }); -app.get("/otpsecret", auth, twofa, async (req, res) => { - let { user } = req; - emit(user.username, "otpsecret", user.otpsecret); - res.send({}); -}); - app.post("/2fa", auth, async (req, res) => { let { user } = req; try { @@ -128,7 +120,7 @@ app.post("/2fa", auth, async (req, res) => { app.get("/exists", async (req, res) => { let exists = await db.User.findOne({ - where: { username: req.query.username } + where: { username: req.query.username }, }); res.send(!!exists); @@ -150,11 +142,11 @@ app.post("/user", auth, async (req, res) => { tokens, twofa, unit, - username + username, } = req.body; let exists = await db.User.findOne({ - where: { username } + where: { username }, }); let token; @@ -169,7 +161,7 @@ app.post("/user", auth, async (req, res) => { token = jwt.sign({ username }, config.jwt); res.cookie("token", token, { - expires: new Date(Date.now() + 432000000) + expires: new Date(Date.now() + 432000000), }); } @@ -201,7 +193,7 @@ app.post("/keys", auth, async (req, res) => { const { key: hex } = req.body; const key = await db.Key.create({ user_id: req.user.id, - hex + hex, }); emit(req.user.username, "key", key); res.send(key); @@ -213,8 +205,8 @@ app.post("/keys/delete", auth, async (req, res) => { await db.Key.findOne({ where: { user_id: req.user.id, - hex - } + hex, + }, }) ).destroy(); }); @@ -230,12 +222,12 @@ app.post("/updateSeeds", auth, async (req, res) => { await db.Account.update( { seed }, { - where: { id, user_id: user.id } + where: { id, user_id: user.id }, } ); const account = await db.Account.findOne({ - where: { id } + where: { id }, }); emit(user.username, "account", account); @@ -252,16 +244,8 @@ app.post("/accounts/delete", auth, async (req, res) => { }); app.post("/accounts", auth, async (req, res) => { - const { - name, - seed, - pubkey, - ticker, - precision, - path, - privkey, - network - } = req.body; + const { name, seed, pubkey, ticker, precision, path, privkey, network } = + req.body; const { user } = req; let account = await db.Account.create({ @@ -276,7 +260,7 @@ app.post("/accounts", auth, async (req, res) => { privkey, seed, path, - network + network, }); emit(user.username, "account", account); @@ -362,14 +346,14 @@ app.post("/logout", optionalAuth, async (req, res) => { if (username) { l("logging out", username); let i = req.user.subscriptions.findIndex( - s => JSON.stringify(s) === subscription + (s) => JSON.stringify(s) === subscription ); if (i > -1) { req.user.subscriptions.splice(i, 1); } await req.user.save(); Object.keys(logins).map( - k => logins[k]["username"] === username && delete logins[k] + (k) => logins[k]["username"] === username && delete logins[k] ); } @@ -383,7 +367,7 @@ app.get("/address", async (req, res) => { let paymentType = { bech32: "p2wpkh", "p2sh-segwit": "p2sh", - legacy: "p2pkh" + legacy: "p2pkh", }[type]; if (!paymentType) paymentType = "p2wpkh"; @@ -418,7 +402,7 @@ app.get("/address", async (req, res) => { paymentType = "p2sh"; // async request to node to bump its internal index but don't use result - lq.getNewAddress().catch(e => + lq.getNewAddress().catch((e) => warn("Problem bumping liquid address index", e.message) ); @@ -445,13 +429,13 @@ app.get("/address", async (req, res) => { if (paymentType !== "p2sh") { ({ address } = p[paymentType]({ pubkey: hd.publicKey, - network: n + network: n, })); } else { if (network === "liquid") { const p2wpkh = p.p2wpkh({ pubkey: hd.publicKey, - network: n + network: n, }); const blindkey = fromPrivateKey( @@ -463,7 +447,7 @@ app.get("/address", async (req, res) => { ({ address, confidentialAddress } = p[paymentType]({ redeem: p2wpkh, network: n, - blindkey: blindkey.publicKey + blindkey: blindkey.publicKey, })); lq.importBlindingKey( @@ -474,9 +458,9 @@ app.get("/address", async (req, res) => { ({ address } = p[paymentType]({ redeem: p.p2wpkh({ pubkey: hd.publicKey, - network: n + network: n, }), - network: n + network: n, })); } } @@ -497,15 +481,15 @@ app.post("/account", auth, async (req, res) => { privkey, path, hide, - index + index, } = req.body; try { - await db.transaction(async transaction => { + await db.transaction(async (transaction) => { let account = await db.Account.findOne({ where: { id, user_id: user.id }, lock: transaction.LOCK.UPDATE, - transaction + transaction, }); let params = { @@ -518,14 +502,14 @@ app.post("/account", auth, async (req, res) => { path, hide, index, - privkey + privkey, }; if (!account || (account.pubkey && !pubkey)) delete params.pubkey; await db.Account.update(params, { where: { id, user_id: user.id }, - transaction + transaction, }); await account.reload({ transaction }); @@ -544,7 +528,7 @@ app.post("/shiftAccount", auth, async (req, res) => { try { const account = await db.Account.findOne({ - where: { id } + where: { id }, }); if (account.user_id !== user.id) @@ -554,14 +538,14 @@ app.post("/shiftAccount", auth, async (req, res) => { await user.save(); let payments = await db.Payment.findAll({ where: { - account_id: id + account_id: id, }, order: [["id", "DESC"]], limit: 12, include: { model: db.Account, - as: "account" - } + as: "account", + }, }); user = user.get({ plain: true }); @@ -578,16 +562,18 @@ app.post("/shiftAccount", auth, async (req, res) => { } }); -app.get("/vapidPublicKey", function(req, res) { +app.get("/vapidPublicKey", function (req, res) { res.send(config.vapid.publicKey); }); -app.post("/subscribe", auth, async function(req, res) { +app.post("/subscribe", auth, async function (req, res) { let { subscriptions } = req.user; let { subscription } = req.body; if (!subscriptions) subscriptions = []; if ( - !subscriptions.find(s => JSON.stringify(s) === JSON.stringify(subscription)) + !subscriptions.find( + (s) => JSON.stringify(s) === JSON.stringify(subscription) + ) ) subscriptions.push(subscription); req.user.subscriptions = subscriptions; @@ -597,10 +583,10 @@ app.post("/subscribe", auth, async function(req, res) { }); let redeeming = {}; -app.post("/redeem", optionalAuth, async function(req, res) { +app.post("/redeem", optionalAuth, async function (req, res) { const { redeemcode } = req.body; try { - await db.transaction(async transaction => { + await db.transaction(async (transaction) => { if (redeeming[redeemcode]) fail("redemption in progress"); redeeming[redeemcode] = true; if (!redeemcode) fail("no code provided"); @@ -609,14 +595,14 @@ app.post("/redeem", optionalAuth, async function(req, res) { const source = await db.Payment.findOne({ where: { - redeemcode: req.body.redeemcode + redeemcode: req.body.redeemcode, }, include: { model: db.Account, - as: "account" + as: "account", }, lock: transaction.LOCK.UPDATE, - transaction + transaction, }); l("redeeming", redeemcode); @@ -633,7 +619,7 @@ app.post("/redeem", optionalAuth, async function(req, res) { user = await register( { username: redeemcode.substr(0, 8), - password: "" + password: "", }, ip, false @@ -642,7 +628,7 @@ app.post("/redeem", optionalAuth, async function(req, res) { let payload = { username: user.username }; let token = jwt.sign(payload, config.jwt); res.cookie("token", token, { - expires: new Date(Date.now() + 432000000) + expires: new Date(Date.now() + 432000000), }); delete redeeming[redeemcode]; @@ -667,7 +653,7 @@ app.post("/redeem", optionalAuth, async function(req, res) { confirmed, network, received: true, - fee + fee, }, { transaction } ); @@ -684,20 +670,20 @@ app.post("/redeem", optionalAuth, async function(req, res) { }); } catch (e) { delete redeeming[redeemcode]; - console.log(e) + console.log(e); err("problem redeeming", e.message); return res.code(500).send("There was a problem redeeming the voucher"); } }); -app.post("/checkRedeemCode", auth, async function(req, res) { +app.post("/checkRedeemCode", auth, async function (req, res) { const { redeemcode } = req.body; const payment = await db.Payment.findOne({ where: { redeemcode } }); res.send(payment); }); -app.post("/password", auth, async function(req, res) { +app.post("/password", auth, async function (req, res) { const { user } = req; const { password } = req.body; @@ -705,7 +691,7 @@ app.post("/password", auth, async function(req, res) { res.send(await bcrypt.compare(password, user.password)); }); -app.get("/isInternal", auth, async function(req, res) { +app.get("/isInternal", auth, async function (req, res) { let { user } = req; let { address } = req.query; if (!address) throw new Error("Address not provided"); @@ -715,8 +701,8 @@ app.get("/isInternal", auth, async function(req, res) { include: { attributes: ["username"], model: db.User, - as: "user" - } + as: "user", + }, }); if (invoice) { @@ -735,21 +721,21 @@ app.get("/isInternal", auth, async function(req, res) { res.send(false); }); -app.get("/invoices", auth, async function(req, res) { +app.get("/invoices", auth, async function (req, res) { let invoices = await db.Invoice.findAll({ - where: { user_id: req.user.id } + where: { user_id: req.user.id }, }); res.send(invoices); }); -app.post("/signMessage", auth, async function(req, res) { +app.post("/signMessage", auth, async function (req, res) { let { address, message } = req.body; let invoices = await db.Invoice.findAll({ - where: { user_id: req.user.id } + where: { user_id: req.user.id }, }); - if (invoices.find(i => i.address === address)) { + if (invoices.find((i) => i.address === address)) { if (config.bitcoin.walletpass) await bc.walletPassphrase(config.bitcoin.walletpass, 300); return res.send(await bc.signMessage(address, message)); -- cgit v1.2.3 From 6dee51ad1c2a74bfb0b57417dae9bce6931cd9f1 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Fri, 30 Sep 2022 22:09:24 +0000 Subject: add log to bitcoin send --- routes/bitcoin/send.js | 87 ++++++++++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/routes/bitcoin/send.js b/routes/bitcoin/send.js index bde3ce00..5bf21f1a 100644 --- a/routes/bitcoin/send.js +++ b/routes/bitcoin/send.js @@ -9,63 +9,56 @@ import { l, warn, err } from "$lib/logging"; const btc = config.liquid.btcasset; import { computeConversionFee, - conversionFeeReceiver + conversionFeeReceiver, } from "./conversionFee.js"; export default async (req, res) => { - let { user } = req; - let { address, memo, tx } = req.body; - let { hex } = tx; + try { + let { user } = req; + let { address, memo, tx } = req.body; + let { hex } = tx; - let fee = toSats(tx.fee); - if (fee < 0) throw new Error("fee cannot be negative"); + let fee = toSats(tx.fee); + if (fee < 0) throw new Error("fee cannot be negative"); - const isChange = async ({ address }) => - (await bc.getAddressInfo(address)).ismine && - !Object.keys(store.addresses).includes(address); + const isChange = async ({ address }) => + (await bc.getAddressInfo(address)).ismine && + !Object.keys(store.addresses).includes(address); - tx = await bc.decodeRawTransaction(hex); + tx = await bc.decodeRawTransaction(hex); - let total = 0; - let change = 0; + let total = 0; + let change = 0; - for (let i = 0; i < tx.vout.length; i++) { - let o = tx.vout[i]; - total += toSats(o.value); + for (let i = 0; i < tx.vout.length; i++) { + let o = tx.vout[i]; + total += toSats(o.value); - if (await isChange(o.scriptPubKey)) { - change += toSats(o.value); + if (await isChange(o.scriptPubKey)) { + change += toSats(o.value); + } } - } - total = total - change + fee; - let amount = total - fee; + total = total - change + fee; + let amount = total - fee; - // get conversion fee - // 'total' refers to the total before the conversion fee - // (i.e. the total bitcoin that leaves this server) - let conversionFee = computeConversionFee(amount); + // get conversion fee + // 'total' refers to the total before the conversion fee + // (i.e. the total bitcoin that leaves this server) + let conversionFee = computeConversionFee(amount); - try { // withdraw bitcoin - await db.transaction(async transaction => { - let account = await db.Account.findOne({ - where: { - id: user.account_id - }, - lock: transaction.LOCK.UPDATE, - transaction - }); - + await db.transaction(async (transaction) => { + let { account } = user; if (account.asset !== btc) { account = await db.Account.findOne({ where: { user_id: user.id, asset: btc, - pubkey: null + pubkey: null, }, lock: transaction.LOCK.UPDATE, - transaction + transaction, }); } @@ -80,6 +73,16 @@ export default async (req, res) => { conversionFee -= conversionFeeDeduction; } + l( + "attempting to send bitcoin", + user.username, + account.id, + address, + total, + conversionFee, + conversionFeeDeduction + ); + if (total > account.balance) { err("amount exceeds balance", amount, fee, account.balance); throw new Error("low balance"); @@ -101,16 +104,16 @@ export default async (req, res) => { let receiverAccount = await db.Account.findOne({ where: { - "$user.username$": conversionFeeReceiver + "$user.username$": conversionFeeReceiver, }, include: [ { model: db.User, - as: "user" - } + as: "user", + }, ], lock: transaction.LOCK.UPDATE, - transaction + transaction, }); let fee_payment; @@ -132,7 +135,7 @@ export default async (req, res) => { currency: receiverAccount.user.currency, confirmed: true, received: true, - network: "COINOS" + network: "COINOS", }, { transaction } ); @@ -152,7 +155,7 @@ export default async (req, res) => { confirmed: true, received: false, network: "bitcoin", - fee_payment_id: fee_payment_id + fee_payment_id: fee_payment_id, }; if (config.bitcoin.walletpass) -- cgit v1.2.3 From c3efae9d1720c6f9eb6f826c4f1c6d5f3e259407 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(-) 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