mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-06 03:38:24 +00:00
Validate that invoice belongs to LNURL pay
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user