Skip to content

Commit

Permalink
first pass of LUD-18 support
Browse files Browse the repository at this point in the history
  • Loading branch information
SatsAllDay committed Sep 24, 2023
1 parent 9f33351 commit 0478007
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 3 deletions.
22 changes: 22 additions & 0 deletions lib/validate.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { secp256k1 } from '@noble/curves/secp256k1'
import { string, ValidationError, number, object, array, addMethod, boolean } from 'yup'
import { BOOST_MIN, MAX_POLL_CHOICE_LENGTH, MAX_TITLE_LENGTH, MAX_POLL_NUM_CHOICES, MIN_POLL_NUM_CHOICES, SUBS_NO_JOBS, MAX_FORWARDS } from './constants'
import { NAME_QUERY } from '../fragments/users'
Expand Down Expand Up @@ -260,3 +261,24 @@ export const pushSubscriptionSchema = object({
p256dh: string().required('required').trim(),
auth: string().required('required').trim()
})

export const lud18PayerDataSchema = (k1) => object({
name: string(),
pubkey: string(),
auth: object({
key: string().required('auth key required'),
k1: string().required('auth k1 required').equals(k1, 'must equal original k1 value'),
sig: string().required('auth sig required')
})
.test('verify auth signature', auth => {
const { key, k1, sig } = auth
try {
return secp256k1.verify(sig, k1, key)
} catch (err) {
console.log('error caught validating auth signature', err)
return false
}
}),
email: string(),
identifier: string()
})
17 changes: 16 additions & 1 deletion pages/api/lnurlp/[username]/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,35 @@
import { randomBytes } from 'crypto'
import { getPublicKey } from 'nostr'
import models from '../../../../api/models'
import { lnurlPayMetadataString } from '../../../../lib/lnurl'
import { LNURLP_COMMENT_MAX_LENGTH } from '../../../../lib/constants'

const generateK1 = () => randomBytes(32).toString('hex')

export default async ({ query: { username } }, res) => {
const user = await models.user.findUnique({ where: { name: username } })
if (!user) {
return res.status(400).json({ status: 'ERROR', reason: `user @${username} does not exist` })
}

const k1 = generateK1()

return res.status(200).json({
callback: `${process.env.PUBLIC_URL}/api/lnurlp/${username}/pay`, // The URL from LN SERVICE which will accept the pay request parameters
callback: `${process.env.PUBLIC_URL}/api/lnurlp/${username}/pay?k1=${k1}`, // The URL from LN SERVICE which will accept the pay request parameters
minSendable: 1000, // Min amount LN SERVICE is willing to receive, can not be less than 1 or more than `maxSendable`
maxSendable: 1000000000,
metadata: lnurlPayMetadataString(username), // Metadata json which must be presented as raw string here, this is required to pass signature verification at a later step
commentAllowed: LNURLP_COMMENT_MAX_LENGTH, // LUD-12 Comments for payRequests https://github.com/lnurl/luds/blob/luds/12.md
payerData: { // LUD-18 payer data for payRequests https://github.com/lnurl/luds/blob/luds/18.md
name: { mandatory: false },
pubkey: { mandatory: false },
identifier: { mandatory: false },
email: { mandatory: false },
auth: {
mandatory: false,
k1
}
},
tag: 'payRequest', // Type of LNURL
nostrPubkey: process.env.NOSTR_PRIVATE_KEY ? getPublicKey(process.env.NOSTR_PRIVATE_KEY) : undefined,
allowsNostr: !!process.env.NOSTR_PRIVATE_KEY
Expand Down
25 changes: 23 additions & 2 deletions pages/api/lnurlp/[username]/pay.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import models from '../../../../api/models'
import lnd from '../../../../api/lnd'
import { createInvoice } from 'ln-service'
import { lnurlPayDescriptionHashForUser } from '../../../../lib/lnurl'
import { lnurlPayDescriptionHashForUser, lnurlPayMetadataString, lnurlPayDescriptionHash } from '../../../../lib/lnurl'
import serialize from '../../../../api/resolvers/serial'
import { schnorr } from '@noble/curves/secp256k1'
import { createHash } from 'crypto'
import { datePivot } from '../../../../lib/time'
import { BALANCE_LIMIT_MSATS, INV_PENDING_LIMIT, LNURLP_COMMENT_MAX_LENGTH } from '../../../../lib/constants'
import { ssValidate, lud18PayerDataSchema } from '../../../../lib/validate'

export default async ({ query: { username, amount, nostr, comment } }, res) => {
export default async ({ query: { username, amount, nostr, comment, payerdata: payerData, k1 } }, res) => {
const user = await models.user.findUnique({ where: { name: username } })
if (!user) {
return res.status(400).json({ status: 'ERROR', reason: `user @${username} does not exist` })
Expand Down Expand Up @@ -41,6 +42,26 @@ export default async ({ query: { username, amount, nostr, comment } }, res) => {
return res.status(400).json({ status: 'ERROR', reason: `comment cannot exceed ${LNURLP_COMMENT_MAX_LENGTH} characters in length` })
}

if (payerData) {
let parsedPayerData
try {
parsedPayerData = JSON.parse(payerData)
} catch (err) {
console.error('failed to parse payerdata', err)
return res.status(400).json({ status: 'ERROR', reason: 'Invalid JSON supplied for payerdata parameter' })
}

try {
await ssValidate(lud18PayerDataSchema, parsedPayerData, k1)
} catch (err) {
return res.status(400).json({ status: 'ERROR', reason: err.toString() })
}

// Update description hash to include the passed payer data
const metadataStr = `${lnurlPayMetadataString(username)}${payerData}`
descriptionHash = lnurlPayDescriptionHash(metadataStr)
}

// generate invoice
const expiresAt = datePivot(new Date(), { minutes: 1 })
const invoice = await createInvoice({
Expand Down

0 comments on commit 0478007

Please sign in to comment.