Validate that invoice belongs to LNURL pay

This commit is contained in:
minibits-cash
2023-10-31 23:35:09 +01:00
parent 9e19d48a56
commit 77112092f5
2 changed files with 51 additions and 24 deletions
+26 -4
View File
@@ -10,6 +10,7 @@ import {
FlatList,
TextInput,
} from 'react-native'
import QuickCrypto from 'react-native-quick-crypto'
import {spacing, useThemeColor, colors, typography} from '../theme'
import {WalletStackScreenProps} from '../navigation'
import {
@@ -268,7 +269,7 @@ const onMintBalanceSelect = function (balance: MintBalance) {
setMintBalanceToTransferFrom(balance) // this triggers effect to get estimated fees
}
// Amount is editable only in case of LNURL Pay, while invoice is not yet retrieved
const onAmountEndEditing = async function () {
try {
const amount = parseInt(amountToTransfer)
@@ -295,7 +296,27 @@ const onAmountEndEditing = async function () {
setIsLoading(true)
const encoded = await LnurlClient.getInvoice(lnurlPayParams, amount * 1000)
// TODO validate h
const invoice = LightningUtils.decodeInvoice(encoded)
const {description_hash} = LightningUtils.getInvoiceData(invoice)
if(!description_hash || description_hash.length === 0) {
throw new AppError(Err.VALIDATION_ERROR, `Invoice from ${lnurlPayParams.domain} is invalid, missing description_hash`)
}
// check that retrieved invoice matches the previous LNURL pay link
const hashedMetadata = QuickCrypto.createHash('sha256')
.update(lnurlPayParams.metadata)
.digest('hex')
log.trace('hashedMetadata', hashedMetadata)
log.trace('description_hash', description_hash)
if(hashedMetadata !== description_hash) {
throw new AppError(
Err.VALIDATION_ERROR,
`Invoice from ${lnurlPayParams.domain} has invalid description_hash ${description_hash}, expected ${hashedMetadata}`
)
}
setIsLoading(false)
if(encoded) {
return onEncodedInvoice(encoded)
@@ -314,11 +335,12 @@ const onEncodedInvoice = async function (encoded: string, paymentRequestDesc: st
navigation.setParams({encodedInvoice: undefined})
navigation.setParams({paymentRequest: undefined})
navigation.setParams({lnurlParams: undefined})
navigation.setParams({paymentOption: undefined})
setEncodedInvoice(encoded)
const invoice = LightningUtils.decodeInvoice(encoded)
const {amount, expiry, description, timestamp} = LightningUtils.getInvoiceData(invoice)
const {amount, expiry, description, description_hash, timestamp} = LightningUtils.getInvoiceData(invoice)
// log.trace('Decoded invoice', invoice, 'onEncodedInvoice')
log.trace('Invoice data', {amount, expiry, description}, 'onEncodedInvoice')
@@ -491,7 +513,7 @@ const satsColor = colors.palette.primary200
LeftComponent={
<Icon
containerStyle={$iconContainer}
icon="faPencil"
icon="faInfoCircle"
size={spacing.medium}
color={iconColor}
/>
+25 -20
View File
@@ -80,30 +80,35 @@ const getInvoiceExpiresAt = function (timestamp: number, expiry: number): Date {
const getInvoiceData = function (decoded: DecodedLightningInvoice) {
let result: {amount?: number; description?: string; expiry?: number, payment_hash?: string, timestamp?: number} = {}
let result: {amount?: number; description?: string; expiry?: number, payment_hash?: string, description_hash?: string, timestamp?: number} = {}
for (const item of decoded.sections) {
switch (item.name) {
case 'amount':
result.amount = parseInt(item.value) / 1000 //sats
break
case 'description':
result.description = (item.value as string) || ''
break
case 'payment_hash':
result.payment_hash = (Buffer.from(item.value).toString('hex') as string) || ''
break
case 'timestamp':
result.timestamp = (item.value as number) || Math.floor(Date.now() / 1000)
break
// log.trace('decoded invoice', decoded)
for (const item of decoded.sections) {
switch (item.name) {
case 'amount':
result.amount = parseInt(item.value) / 1000 //sats
break
case 'description':
result.description = (item.value as string) || ''
break
case 'payment_hash':
result.payment_hash = (Buffer.from(item.value).toString('hex') as string) || ''
break
case 'description_hash':
result.description_hash = (Buffer.from(item.value).toString('hex') as string) || ''
break
case 'timestamp':
result.timestamp = (item.value as number) || Math.floor(Date.now() / 1000)
break
}
}
}
result.expiry = decoded.expiry || 600
result.expiry = decoded.expiry || 600
log.trace('Invoice data', result, 'getInvoiceData')
return result
log.trace('Invoice data', result, 'getInvoiceData')
return result
}
export const LightningUtils = {