UNPKG

balanceofsatoshis

Version:
179 lines (143 loc) 5.42 kB
const asyncAuto = require('async/auto'); const {bech32} = require('bech32'); const {returnResult} = require('asyncjs-util'); const {signMessage} = require('ln-service'); const tinysecp = require('tiny-secp256k1'); const signAuthChallenge = require('./sign_auth_challenge'); const actionKey = 'action'; const {decode} = bech32; const defaultAction = 'authenticate'; const asLnurl = n => n.substring(n.startsWith('lightning:') ? 10 : 0); const bech32CharLimit = 2000; const challengeKey = 'k1'; const errorStatus = 'ERROR'; const knownActions = ['auth', 'link', 'login', 'register']; const okStatus = 'OK'; const prefix = 'lnurl'; const tlsProtocol = 'https:'; const lud13AuthPhrase = 'DO NOT EVER SIGN THIS TEXT WITH YOUR PRIVATE KEYS! IT IS ONLY USED FOR DERIVATION OF LNURL-AUTH HASHING-KEY, DISCLOSING ITS SIGNATURE WILL COMPROMISE YOUR LNURL-AUTH IDENTITY AND MAY LEAD TO LOSS OF FUNDS!'; const wordsAsUtf8 = n => Buffer.from(bech32.fromWords(n)).toString('utf8'); /** Authenticate using lnurl { ask: <Ask Function> request: <Request Function> lnd: <Authenticated LND API Object> lnurl: <Lnurl String> logger: <Winston Logger Object> } @returns via cbk or Promise */ module.exports = (args, cbk) => { return new Promise((resolve, reject) => { return asyncAuto({ // Import the ECPair library ecp: async () => (await import('ecpair')).ECPairFactory(tinysecp), // Check arguments validate: cbk => { if (!args.ask) { return cbk([400, 'ExpectedAskFunctionToAuthenticateUsingLnurl']); } if (!args.lnurl) { return cbk([400, 'ExpectedUrlToAuthenticateToLnurl']); } try { decode(asLnurl(args.lnurl), bech32CharLimit); } catch (err) { return cbk([400, 'FailedToDecodeLnurlToAuthenticate', {err}]); } if (decode(asLnurl(args.lnurl), bech32CharLimit).prefix !== prefix) { return cbk([400, 'ExpectedLnUrlPrefixToAuthenticate']); } if (!args.lnd) { return cbk([400, 'ExpectedLndToAuthenticateUsingLnurl']); } if (!args.logger) { return cbk([400, 'ExpectedLoggerToAuthenticateUsingLnurl']); } if (!args.request) { return cbk([400, 'ExpectedRequestFunctionToGetLnurlAuthentication']); } return cbk(); }, // Parse the encoded Lnurl parse: ['validate', ({}, cbk) => { const {words} = decode(asLnurl(args.lnurl), bech32CharLimit); const url = wordsAsUtf8(words); try { new URL(url); } catch (err) { return cbk([400, 'ExpectedValidCallbackUrlInDecodedLnurlForAuth']); } const {hostname, protocol, searchParams} = new URL(url); if (protocol !== tlsProtocol) { return cbk([501, 'UnsupportedUrlProtocolForLnurlAuthentication']); } const action = searchParams.get(actionKey); if (!!action && !knownActions.includes(action)) { return cbk([503, 'UnknownAuthenticationActionForLnurlAuth']); } const k1 = searchParams.get(challengeKey); if (!k1) { return cbk([503, 'ExpectedChallengeK1ValueInDecodedLnurlForAuth']); } return cbk(null, {hostname, k1, url, action: action || defaultAction}); }], // Sign the canonical phrase for LUD-13 signMessage based seed generation seed: ['parse', ({}, cbk) => { return signMessage({lnd: args.lnd, message: lud13AuthPhrase}, cbk); }], // Derive keys and get signatures sign: ['ecp', 'parse', 'seed', ({ecp, parse, seed}, cbk) => { const sign = signAuthChallenge({ ecp, hostname: parse.hostname, k1: parse.k1, seed: seed.signature, }); return cbk(null, { public_key: sign.public_key, signature: sign.signature, }); }], // Display confirmation dialog with domain name and action ok: ['parse', 'sign', ({parse, sign}, cbk) => { return args.ask({ default: true, message: `Do you want to ${parse.action} with ${parse.hostname}?`, name: 'ok', type: 'confirm', }, ({ok}) => cbk(null, ok)); }], // Transmit authenticating signature and key to the host send: ['ok', 'parse', 'sign', ({ok, parse, sign}, cbk) => { if (!ok) { return cbk([400, 'AuthenticationCanceled']); } args.logger.info({sending_authentication: sign.public_key}); return args.request({ json: true, qs: {key: sign.public_key, sig: sign.signature}, url: parse.url, }, (err, r, json) => { if (!!err) { return cbk([503, 'FailedToGetLnurlAuthenticationData', {err}]); } if (!json) { return cbk([503, 'ExpectedJsonReturnedInLnurlResponseForAuth']); } if (json.status === errorStatus) { return cbk([503, 'LnurlAuthenticationFail', {err: json.reason}]); } if (json.status !== okStatus) { return cbk([503, 'ExpectedOkStatusInLnurlResponseJsonForAuth']); } args.logger.info({is_authenticated: true}); return cbk(); }); }], }, returnResult({reject, resolve}, cbk)); }); };