From aa932e5107311d1b49b408b168359bd8eed74a4b Mon Sep 17 00:00:00 2001 From: DhananjayPurohit Date: Thu, 15 Aug 2024 14:17:17 +0530 Subject: [PATCH] feat: add test for invoice handling for lightning latch fix: import error fix: verifyInvoice fn fix: add bolt11 as package fix: bolt11 import error --- .../test/tb04-simple-lightning-latch.mjs | 24 +- clients/libs/web/lightning-latch.js | 2 +- clients/libs/web/main.js | 12 +- clients/libs/web/package.json | 3 +- .../test/tb04-simple-lightning-latch.test.js | 223 +++++++++++++++++- 5 files changed, 248 insertions(+), 16 deletions(-) diff --git a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs index 879195b4..693bc204 100644 --- a/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs +++ b/clients/apps/nodejs/test/tb04-simple-lightning-latch.mjs @@ -328,12 +328,16 @@ describe('TB04 - Lightning Latch', function() { expect(transferReceiveResult.isThereBatchLocked).is.false; expect(transferReceiveResult.receivedStatechainIds).is.empty; - const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); - console.log("Preimage: ", preimage); - - const hash = crypto.createHash('sha256') + let hash; + try { + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); + hash = crypto.createHash('sha256') .update(Buffer.from(preimage, 'hex')) .digest('hex') + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } expect(hash).to.equal(paymentHash.hash); }) @@ -394,12 +398,16 @@ describe('TB04 - Lightning Latch', function() { expect(transferReceiveResult.isThereBatchLocked).is.false; expect(transferReceiveResult.receivedStatechainIds).is.empty; - const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); - console.log("Preimage: ", preimage); - - const hash = crypto.createHash('sha256') + let hash; + try { + const { preimage } = await mercurynodejslib.retrievePreImage(clientConfig, wallet_1_name, coin.statechain_id, paymentHash.batchId); + hash = crypto.createHash('sha256') .update(Buffer.from(preimage, 'hex')) .digest('hex') + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } expect(hash).to.equal(paymentHash.hash); }) diff --git a/clients/libs/web/lightning-latch.js b/clients/libs/web/lightning-latch.js index f105890d..1b083209 100644 --- a/clients/libs/web/lightning-latch.js +++ b/clients/libs/web/lightning-latch.js @@ -132,4 +132,4 @@ const getPaymentHash = async (clientConfig, batchId) => { } -export default { createPreImage, confirmPendingInvoice, retrievePreImage, getPaymentHash }; +export default { createPreImage, confirmPendingInvoice, retrievePreImage, getPaymentHash}; diff --git a/clients/libs/web/main.js b/clients/libs/web/main.js index 5da25a5b..dc50464a 100644 --- a/clients/libs/web/main.js +++ b/clients/libs/web/main.js @@ -9,6 +9,7 @@ import transfer_send from './transfer_send.js'; import transfer_receive from './transfer_receive.js'; import lightningLatch from './lightning-latch.js'; import { v4 as uuidv4 } from 'uuid'; +import * as lightningPayReq from 'bolt11'; const greet = async () => { @@ -127,6 +128,14 @@ const getPaymentHash = async (clientConfig, batchId) => { return await lightningLatch.getPaymentHash(clientConfig, batchId); } +const verifyInvoice = async (clientConfig, batchId, paymentRequest) => { + + const decodedInvoice = lightningPayReq.decode(paymentRequest); + let paymentHash = await getPaymentHash(clientConfig, batchId); + + return paymentHash === decodedInvoice.tagsObject.payment_hash; +} + export default { greet, createWallet, @@ -141,5 +150,6 @@ export default { paymentHash, confirmPendingInvoice, retrievePreImage, - getPaymentHash + getPaymentHash, + verifyInvoice } diff --git a/clients/libs/web/package.json b/clients/libs/web/package.json index 66d889a9..bc325938 100644 --- a/clients/libs/web/package.json +++ b/clients/libs/web/package.json @@ -15,6 +15,7 @@ "dependencies": { "axios": "^1.7.2", "mercury-wasm": "file:../../../wasm/web_pkg/debug", - "uuid": "^10.0.0" + "uuid": "^10.0.0", + "bolt11": "^1.4.1" } } diff --git a/clients/tests/web/test/tb04-simple-lightning-latch.test.js b/clients/tests/web/test/tb04-simple-lightning-latch.test.js index d8513fa6..5c03a57c 100644 --- a/clients/tests/web/test/tb04-simple-lightning-latch.test.js +++ b/clients/tests/web/test/tb04-simple-lightning-latch.test.js @@ -203,7 +203,6 @@ describe('TB04 - The sender tries to get the pre-image before the batch is unloc let toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; await mercuryweblib.withdrawCoin(clientConfig, wallet2.name, statechainId1, toAddress, null, null); - await mercuryweblib.withdrawCoin(clientConfig, wallet1.name, statechainId2, toAddress, null, null); const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId1, paymentHash1.batchId); @@ -281,7 +280,7 @@ describe('TB04 - Statecoin sender can recover (resend their coin) after batch ti let transferAddress2 = await mercuryweblib.newTransferAddress(wallet1.name); await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId1, transferAddress1.transfer_receive, false, paymentHash1.batchId ); - await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, transferAddress2.transfer_receive, false, paymentHash2.batchId); + await mercuryweblib.transferSend(clientConfig, wallet2.name, statechainId2, transferAddress2.transfer_receive, false, paymentHash1.batchId); let transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); @@ -314,7 +313,7 @@ describe('TB04 - Statecoin sender can recover (resend their coin) after batch ti await mercuryweblib.confirmPendingInvoice(clientConfig, wallet1.name, statechainId1); await mercuryweblib.confirmPendingInvoice(clientConfig, wallet2.name, statechainId2); - expect(transferReceive.isThereBatchLocked).toBe(false); + expect(transferReceive.isThereBatchLocked).toBe(true); let toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; @@ -461,7 +460,7 @@ describe('TB04 - Receiver tries to transfer invoice amount to another invoice be await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddress.transfer_receive, false, paymentHash.batchId ); - const hashFromServer = await mercurynodejslib.getPaymentHash(clientConfig, paymentHash.batchId); + const hashFromServer = await mercuryweblib.getPaymentHash(clientConfig, paymentHash.batchId); expect(hashFromServer).to.equal(paymentHash.hash); @@ -495,4 +494,218 @@ describe('TB04 - Receiver tries to transfer invoice amount to another invoice be expect(error.message).to.include('failed'); } }); -}, 50000); \ No newline at end of file +}, 50000); + +describe('Statecoin sender sends coin without batch_id (receiver should still be able to receive, but no pre-image revealed)', () => { + test("expected flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb04"); + localStorage.removeItem("mercury-layer:wallet2_tb04"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb04"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb04"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + + const amount = 1000; + + let result = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const statechainId = result.statechain_id; + + let isDepositInMempool = false; + let isDepositConfirmed = false; + let areBlocksGenerated = false; + + await depositCoin(result.deposit_address, amount); + + while (!isDepositConfirmed) { + + const coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool) { + isDepositInMempool = true; + } else if (coin.statechain_id === statechainId && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed = true; + break; + } + } + + if (isDepositInMempool && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + const paymentHash = await mercuryweblib.paymentHash(clientConfig, wallet1.name, statechainId); + + let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddress.transfer_receive, false, null); + + const hashFromServer = await mercuryweblib.getPaymentHash(clientConfig, paymentHash.batchId); + + expect(hashFromServer).to.equal(paymentHash.hash); + + let transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(false); + + await mercuryweblib.confirmPendingInvoice(clientConfig, wallet1.name, statechainId); + + transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(false); + + let toAddress = "bcrt1q805t9k884s5qckkxv7l698hqlz7t6alsfjsqym"; + + await mercuryweblib.withdrawCoin(clientConfig, wallet2.name, statechainId, toAddress, null, null); + + let hashPreImage; + try { + const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId, paymentHash.batchId); + hashPreImage = await sha256(preimage); + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } + + expect(hashPreImage).toEqual(paymentHash.hash); + }); +}, 50000); + +describe('TB04 - Sender sends coin without batch_id, and then resends to a different address (to attempt to steal), and then attempts to retrieve the pre-image, should fail (and LN payment cannot be claimed)', () => { + test("expected flow", async () => { + localStorage.removeItem("mercury-layer:wallet1_tb04"); + localStorage.removeItem("mercury-layer:wallet2_tb04"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb04"); + let wallet2 = await mercuryweblib.createWallet(clientConfig, "wallet2_tb04"); + let wallet3 = await mercuryweblib.createWallet(clientConfig, "wallet3_tb04"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + + const amount = 1000; + + let result = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const statechainId = result.statechain_id; + + let isDepositInMempool = false; + let isDepositConfirmed = false; + let areBlocksGenerated = false; + + await depositCoin(result.deposit_address, amount); + + while (!isDepositConfirmed) { + + const coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool) { + isDepositInMempool = true; + } else if (coin.statechain_id === statechainId && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed = true; + break; + } + } + + if (isDepositInMempool && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + const paymentHash = await mercuryweblib.paymentHash(clientConfig, wallet1.name, statechainId); + + let transferAddress = await mercuryweblib.newTransferAddress(wallet2.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddress.transfer_receive, false, null); + + const transferAddressSecond = await mercuryweblib.newTransferAddress(wallet3.name); + + await mercuryweblib.transferSend(clientConfig, wallet1.name, statechainId, transferAddressSecond.transfer_receive, false, null); + + const hashFromServer = await mercuryweblib.getPaymentHash(clientConfig, paymentHash.batchId); + + expect(hashFromServer).to.equal(paymentHash.hash); + + let transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(false); + + await mercuryweblib.confirmPendingInvoice(clientConfig, wallet1.name, statechainId); + + transferReceive = await mercuryweblib.transferReceive(clientConfig, wallet2.name); + + expect(transferReceive.isThereBatchLocked).toBe(false); + + let hashPreImage; + try { + const { preimage } = await mercuryweblib.retrievePreImage(clientConfig, wallet1.name, statechainId, paymentHash.batchId); + hashPreImage = await sha256(preimage); + } catch (error) { + console.error('Error:', error); + expect(error.message).to.include('failed'); + } + + expect(hashPreImage).toEqual(paymentHash.hash); + }); +}, 50000); + +describe('Coin receiver creates a non hold invoice, and sends to sender (i.e. an invoice with the a different payment hash). Sender should be able to determine this.', () => { + test("expected flow", async () => { + + localStorage.removeItem("mercury-layer:wallet1_tb04"); + + let wallet1 = await mercuryweblib.createWallet(clientConfig, "wallet1_tb04"); + + await mercuryweblib.newToken(clientConfig, wallet1.name); + + const amount = 1000; + + let result = await mercuryweblib.getDepositBitcoinAddress(clientConfig, wallet1.name, amount); + + const statechainId = result.statechain_id; + + let isDepositInMempool = false; + let isDepositConfirmed = false; + let areBlocksGenerated = false; + + await depositCoin(result.deposit_address, amount); + + while (!isDepositConfirmed) { + + const coins = await mercuryweblib.listStatecoins(clientConfig, wallet1.name); + + for (let coin of coins) { + if (coin.statechain_id === statechainId && coin.status === CoinStatus.IN_MEMPOOL && !isDepositInMempool) { + isDepositInMempool = true; + } else if (coin.statechain_id === statechainId && coin.status === CoinStatus.CONFIRMED) { + isDepositConfirmed = true; + break; + } + } + + if (isDepositInMempool && !areBlocksGenerated) { + areBlocksGenerated = true; + await generateBlocks(clientConfig.confirmationTarget); + } + + await new Promise(r => setTimeout(r, 1000)); + } + + const paymentHash = await mercuryweblib.paymentHash(clientConfig, wallet1.name, statechainId); + + const paymentHashSecond = "b4eab5e663aebe5fc645865b27b33c04c4e057e7c844fa61519df6de1398cdb3" + const invoiceSecond = await generateInvoice(paymentHashSecond, amount); + + const isInvoiceValid = await mercuryweblib.verifyInvoice(clientConfig, paymentHash.batchId, invoiceSecond.payment_request); + expect(isInvoiceValid).is.false; + }); +}, 50000);