From eca70d1c29bea954e958d6b2415f12e2ddd24407 Mon Sep 17 00:00:00 2001 From: michael1011 Date: Mon, 17 Feb 2025 22:27:26 +0100 Subject: [PATCH] refactor: migrate slip77 --- lib/Core.ts | 65 ++- lib/service/ElementsService.ts | 11 +- lib/service/TransactionFetcher.ts | 2 +- lib/swap/SwapManager.ts | 13 +- lib/swap/UtxoNursery.ts | 17 +- lib/wallet/Wallet.ts | 16 +- lib/wallet/WalletLiquid.ts | 57 ++- lib/wallet/WalletManager.ts | 14 +- test/integration/Core.spec.ts | 82 ++-- .../service/ElementsService.spec.ts | 79 ++-- .../service/TransactionFetcher.spec.ts | 11 +- .../cooperative/ChainSwapSigner.spec.ts | 10 +- .../cooperative/CoopSignerBase.spec.ts | 5 +- .../cooperative/DeferredClaimer.spec.ts | 2 +- test/unit/swap/SwapManager.spec.ts | 12 +- test/unit/swap/UtxoNursery.spec.ts | 2 +- test/unit/wallet/Wallet.spec.ts | 35 +- test/unit/wallet/WalletLiquid.spec.ts | 46 +- .../__snapshots__/WalletLiquid.spec.ts.snap | 397 +++++++++++++----- 19 files changed, 603 insertions(+), 273 deletions(-) diff --git a/lib/Core.ts b/lib/Core.ts index 9074cf64c..f464aa5bb 100644 --- a/lib/Core.ts +++ b/lib/Core.ts @@ -50,6 +50,7 @@ import { reverseBuffer, } from './Utils'; import { IChainClient } from './chain/ChainClient'; +import { SomeTransaction } from './chain/ZmqClient'; import { CurrencyType, SwapType, @@ -61,7 +62,7 @@ import ChainSwapData from './db/models/ChainSwapData'; import Swap from './db/models/Swap'; import SwapOutputType from './swap/SwapOutputType'; import Wallet from './wallet/Wallet'; -import WalletLiquid from './wallet/WalletLiquid'; +import WalletLiquid, { Slip77s } from './wallet/WalletLiquid'; import { Currency } from './wallet/WalletManager'; type UnblindedOutput = Omit & { @@ -159,14 +160,24 @@ export const getOutputValue = ( return output.value as number; } - const unblinded = unblindOutput( - wallet, - output as LiquidTxOutput, - (wallet as WalletLiquid).deriveBlindingKeyFromScript(output.script) - .privateKey!, + const walletKeys = (wallet as WalletLiquid).deriveBlindingKeyFromScript( + output.script, ); - return unblinded.isLbtc ? unblinded.value : 0; + for (const key of [walletKeys.new, walletKeys.legacy] + .map((k) => k.privateKey) + .filter((k) => k !== undefined)) { + try { + const unblinded = unblindOutput(wallet, output as LiquidTxOutput, key); + + return unblinded.isLbtc ? unblinded.value : 0; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + /* empty */ + } + } + + return 0; }; export const constructClaimDetails = ( @@ -230,7 +241,8 @@ export const constructClaimTransaction = ( claimDetails: ClaimDetails[] | LiquidClaimDetails[], destinationAddress: string, feePerVbyte: number, -) => { + blindingKeyType: keyof Slip77s = 'new', +): SomeTransaction => { const span = Tracing.tracer.startSpan('constructClaimTransaction', { kind: SpanKind.INTERNAL, attributes: { @@ -257,6 +269,7 @@ export const constructClaimTransaction = ( const liquidDetails = populateBlindingKeys( walletLiquid, claimDetails as LiquidClaimDetails[], + blindingKeyType, ); const decodedAddress = liquidAddress.fromConfidential(destinationAddress); @@ -274,6 +287,18 @@ export const constructClaimTransaction = ( walletLiquid.supportsDiscountCT, ); }); + } catch (e) { + if (blindingKeyType === 'new') { + return constructClaimTransaction( + wallet, + claimDetails, + destinationAddress, + feePerVbyte, + 'legacy', + ); + } + + throw e; } finally { span.end(); } @@ -285,7 +310,8 @@ export const constructRefundTransaction = ( destinationAddress: string, timeoutBlockHeight: number, feePerVbyte: number, -) => { + blindingKeyType: keyof Slip77s = 'new', +): SomeTransaction => { const span = Tracing.tracer.startSpan('constructRefundTransaction', { kind: SpanKind.INTERNAL, attributes: { @@ -313,6 +339,7 @@ export const constructRefundTransaction = ( const liquidDetails = populateBlindingKeys( walletLiquid, refundDetails as LiquidRefundDetails[], + blindingKeyType, ); const decodedAddress = liquidAddress.fromConfidential(destinationAddress); @@ -331,6 +358,19 @@ export const constructRefundTransaction = ( walletLiquid.supportsDiscountCT, ); }); + } catch (e) { + if (blindingKeyType === 'new') { + return constructRefundTransaction( + wallet, + refundDetails, + destinationAddress, + timeoutBlockHeight, + feePerVbyte, + 'legacy', + ); + } + + throw e; } finally { span.end(); } @@ -413,11 +453,12 @@ const populateBlindingKeys = < >( wallet: WalletLiquid, utxos: T[], + blindingKeyType: keyof Slip77s, ): T[] => { for (const utxo of utxos) { - utxo.blindingPrivateKey = wallet.deriveBlindingKeyFromScript( - utxo.script, - ).privateKey!; + utxo.blindingPrivateKey = wallet.deriveBlindingKeyFromScript(utxo.script)[ + blindingKeyType + ].privateKey!; } return utxos; diff --git a/lib/service/ElementsService.ts b/lib/service/ElementsService.ts index 648f53ca0..94962c9ae 100644 --- a/lib/service/ElementsService.ts +++ b/lib/service/ElementsService.ts @@ -27,16 +27,19 @@ class ElementsService { return await Promise.all( tx.outs.map(async (out) => { if (out.rangeProof !== undefined && out.rangeProof.length > 0) { + const walletKeys = wallet.deriveBlindingKeyFromScript(out.script)!; const keys = [ - wallet.deriveBlindingKeyFromScript(out.script).privateKey!, + walletKeys.new.privateKey, + walletKeys.legacy.privateKey, getHexBuffer( await chainClient.dumpBlindingKey( - wallet.encodeAddress(out.script, false), + // Does not matter which one we take because we don't blind + wallet.encodeAddress(out.script, false).new, ), ), ]; - for (const blindingKey of keys) { + for (const blindingKey of keys.filter((k) => k !== undefined)) { try { return unblindOutput(wallet, out, blindingKey); // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -56,7 +59,7 @@ class ElementsService { const { publicKey, privateKey } = wallet.deriveBlindingKeyFromScript( wallet.decodeAddress(address), - ); + ).new; return { publicKey: publicKey!, privateKey: privateKey!, diff --git a/lib/service/TransactionFetcher.ts b/lib/service/TransactionFetcher.ts index 0edf8d764..20ae783d4 100644 --- a/lib/service/TransactionFetcher.ts +++ b/lib/service/TransactionFetcher.ts @@ -193,7 +193,7 @@ class TransactionFetcher { ? transaction.outs // Filter Liquid fee outputs .filter((out) => out.script.length > 0) - .map((out) => wallet.encodeAddress(out.script)) + .flatMap((out) => Object.values(wallet.encodeAddress(out.script))) // The wallet returns empty strings for addresses it cannot encode .filter((addr) => addr !== '') : []; diff --git a/lib/swap/SwapManager.ts b/lib/swap/SwapManager.ts index e0fab8cea..337b166d5 100644 --- a/lib/swap/SwapManager.ts +++ b/lib/swap/SwapManager.ts @@ -416,14 +416,14 @@ class SwapManager { } } - result.address = receivingCurrency.wallet.encodeAddress(outputScript); + result.address = receivingCurrency.wallet.encodeAddress(outputScript).new; receivingCurrency.chainClient!.addOutputFilter(outputScript); if (receivingCurrency.type === CurrencyType.Liquid) { result.blindingKey = getHexString( ( receivingCurrency.wallet as WalletLiquid - ).deriveBlindingKeyFromScript(outputScript).privateKey!, + ).deriveBlindingKeyFromScript(outputScript).new.privateKey!, ); } @@ -872,13 +872,14 @@ class SwapManager { } } - result.lockupAddress = sendingCurrency.wallet.encodeAddress(outputScript); + result.lockupAddress = + sendingCurrency.wallet.encodeAddress(outputScript).new; if (sendingCurrency.type === CurrencyType.Liquid) { result.blindingKey = getHexString( (sendingCurrency.wallet as WalletLiquid).deriveBlindingKeyFromScript( outputScript, - ).privateKey!, + ).new.privateKey!, ); } @@ -1066,13 +1067,13 @@ class SwapManager { currency.chainClient!.addOutputFilter(outputScript); } - res.lockupAddress = currency.wallet.encodeAddress(outputScript); + res.lockupAddress = currency.wallet.encodeAddress(outputScript).new; if (currency.type === CurrencyType.Liquid) { blindingKey = getHexString( (currency.wallet as WalletLiquid).deriveBlindingKeyFromScript( outputScript, - ).privateKey!, + ).new.privateKey!, ); } } else { diff --git a/lib/swap/UtxoNursery.ts b/lib/swap/UtxoNursery.ts index dd1a3ef77..deb355eb6 100644 --- a/lib/swap/UtxoNursery.ts +++ b/lib/swap/UtxoNursery.ts @@ -198,7 +198,9 @@ class UtxoNursery extends TypedEventEmitter<{ chainClient, wallet, ); - if (prevAddresses.some(this.blocks.isBlocked)) { + if ( + prevAddresses.flatMap((a) => Object.values(a)).some(this.blocks.isBlocked) + ) { chainClient.removeOutputFilter(swapOutput.script); this.emit('chainSwap.lockup.failed', { swap, @@ -326,9 +328,14 @@ class UtxoNursery extends TypedEventEmitter<{ await this.lock.acquire(UtxoNursery.lockupLock, async () => { for (let vout = 0; vout < transaction.outs.length; vout += 1) { const output = transaction.outs[vout]; - const address = wallet.encodeAddress(output.script); + const encoded = wallet.encodeAddress(output.script); - await Promise.all([checkSwap(address), checkChainSwap(address)]); + await Promise.all( + [...new Set([encoded.new, encoded.legacy])].flatMap((a) => [ + checkSwap(a), + checkChainSwap(a), + ]), + ); } }); }; @@ -794,7 +801,9 @@ class UtxoNursery extends TypedEventEmitter<{ chainClient, wallet, ); - if (prevAddresses.some(this.blocks.isBlocked)) { + if ( + prevAddresses.flatMap((a) => Object.values(a)).some(this.blocks.isBlocked) + ) { this.emit('swap.lockup.failed', { swap: updatedSwap, reason: Errors.BLOCKED_ADDRESS().message, diff --git a/lib/wallet/Wallet.ts b/lib/wallet/Wallet.ts index 828f0cd3d..d40ec3ac8 100644 --- a/lib/wallet/Wallet.ts +++ b/lib/wallet/Wallet.ts @@ -5,6 +5,7 @@ import Logger from '../Logger'; import { CurrencyType } from '../consts/Enums'; import KeyRepository from '../db/repositories/KeyRepository'; import Errors from './Errors'; +import type { Slip77s } from './WalletLiquid'; import WalletProviderInterface, { BalancerFetcher, SentTransaction, @@ -86,17 +87,26 @@ class Wallet implements BalancerFetcher { * * @param outputScript the output script to encode */ - public encodeAddress = (outputScript: Buffer): string => { + public encodeAddress = ( + outputScript: Buffer, + ): Record => { if (this.network === undefined) { throw Errors.NOT_SUPPORTED_BY_WALLET(this.symbol, 'encodeAddress'); } try { - return fromOutputScript(this.type, outputScript, this.network); + const address = fromOutputScript(this.type, outputScript, this.network); + return { + new: address, + legacy: address, + }; // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // Ignore invalid addresses - return ''; + return { + new: '', + legacy: '', + }; } }; diff --git a/lib/wallet/WalletLiquid.ts b/lib/wallet/WalletLiquid.ts index 4b5a60fa8..1356d18e5 100644 --- a/lib/wallet/WalletLiquid.ts +++ b/lib/wallet/WalletLiquid.ts @@ -7,11 +7,16 @@ import { CurrencyType } from '../consts/Enums'; import Wallet from './Wallet'; import WalletProviderInterface from './providers/WalletProviderInterface'; +export type Slip77s = { + new: Slip77Interface; + legacy: Slip77Interface; +}; + class WalletLiquid extends Wallet { constructor( logger: Logger, walletProvider: WalletProviderInterface, - private readonly slip77: Slip77Interface, + private readonly slip77s: Slip77s, network: Network, ) { super(logger, CurrencyType.Liquid, walletProvider, network); @@ -27,33 +32,63 @@ class WalletLiquid extends Wallet { ); } - public deriveBlindingKeyFromScript = (outputScript: Buffer) => { - return this.slip77.derive(outputScript); + public deriveBlindingKeyFromScript = ( + outputScript: Buffer, + ): Record => { + return { + new: this.slip77s.new.derive(outputScript), + legacy: this.slip77s.legacy.derive(outputScript), + }; }; public override encodeAddress = ( outputScript: Buffer, shouldBlind = true, - ): string => { + ): Record => { try { // Fee output of Liquid if (outputScript.length == 0) { - return ''; + return { + new: '', + legacy: '', + }; + } + + if (!shouldBlind) { + const res = this.getPaymentFunc(outputScript)({ + output: outputScript, + network: this.network as networks.Network, + }); + return { + new: res.address!, + legacy: res.address!, + }; } - const res = this.getPaymentFunc(outputScript)({ + const walletKeys = this.deriveBlindingKeyFromScript(outputScript); + + const resNew = this.getPaymentFunc(outputScript)({ + output: outputScript, + network: this.network as networks.Network, + blindkey: walletKeys.new.publicKey, + }); + const resLegacy = this.getPaymentFunc(outputScript)({ output: outputScript, network: this.network as networks.Network, - blindkey: shouldBlind - ? this.deriveBlindingKeyFromScript(outputScript).publicKey! - : undefined, + blindkey: walletKeys.legacy.publicKey, }); - return shouldBlind ? res.confidentialAddress! : res.address!; + return { + new: resNew.confidentialAddress!, + legacy: resLegacy.confidentialAddress!, + }; // eslint-disable-next-line @typescript-eslint/no-unused-vars } catch (error) { // Ignore invalid addresses - return ''; + return { + new: '', + legacy: '', + }; } }; diff --git a/lib/wallet/WalletManager.ts b/lib/wallet/WalletManager.ts index e6e5bc37c..34ec8f6be 100644 --- a/lib/wallet/WalletManager.ts +++ b/lib/wallet/WalletManager.ts @@ -5,7 +5,7 @@ import { Provider } from 'ethers'; import fs from 'fs'; import { IElementsClient } from 'lib/chain/ElementsClient'; import type { Network as LiquidNetwork } from 'liquidjs-lib/src/networks'; -import { SLIP77Factory, Slip77Interface } from 'slip77'; +import { SLIP77Factory } from 'slip77'; import * as ecc from 'tiny-secp256k1'; import { CurrencyConfig } from '../Config'; import Logger from '../Logger'; @@ -18,7 +18,7 @@ import LndClient from '../lightning/LndClient'; import ClnClient from '../lightning/cln/ClnClient'; import Errors from './Errors'; import Wallet from './Wallet'; -import WalletLiquid from './WalletLiquid'; +import WalletLiquid, { Slip77s } from './WalletLiquid'; import EthereumManager from './ethereum/EthereumManager'; import CoreWalletProvider from './providers/CoreWalletProvider'; import ElementsWalletProvider from './providers/ElementsWalletProvider'; @@ -62,7 +62,7 @@ class WalletManager { private readonly mnemonic: string; private readonly mnemonicEvm: string; - private readonly slip77: Slip77Interface; + private readonly slip77s: Slip77s; private readonly masterNode: BIP32Interface; private readonly derivationPath = 'm/0'; @@ -82,8 +82,10 @@ class WalletManager { const seed = mnemonicToSeedSync(this.mnemonic); this.masterNode = bip32.fromSeed(seed); - // TODO: this is a breaking change. not sure how to handle that - this.slip77 = slip77.fromSeed(seed); + this.slip77s = { + new: slip77.fromSeed(seed), + legacy: slip77.fromSeed(this.mnemonic), + }; } public init = async (configCurrencies: CurrencyConfig[]): Promise => { @@ -168,7 +170,7 @@ class WalletManager { : new WalletLiquid( this.logger, walletProvider, - this.slip77, + this.slip77s, currency.network! as LiquidNetwork, ); diff --git a/test/integration/Core.spec.ts b/test/integration/Core.spec.ts index 5145224a1..4a559974b 100644 --- a/test/integration/Core.spec.ts +++ b/test/integration/Core.spec.ts @@ -89,7 +89,10 @@ describe('Core', () => { walletLiquid = new WalletLiquid( Logger.disabledLogger, new ElementsWalletProvider(Logger.disabledLogger, elementsClient), - slip77.fromSeed(generateMnemonic()), + { + new: slip77.fromSeed(mnemonicToSeedSync(generateMnemonic())), + legacy: slip77.fromSeed(generateMnemonic()), + }, networks.regtest, ); initWallet(walletLiquid); @@ -171,44 +174,51 @@ describe('Core', () => { walletLiquid['network'] = networks.regtest; }); - test('should get output value of blinded Liquid transactions', async () => { - const script = toOutputScript( - CurrencyType.Liquid, - await walletLiquid.getAddress(''), - walletLiquid.network!, - ); + test.each` + blindingType + ${'new'} + ${'legacy'} + `( + 'should get output value of blinded Liquid transactions blinded with type $blindingType', + async ({ blindingType }) => { + const script = toOutputScript( + CurrencyType.Liquid, + await walletLiquid.getAddress(''), + walletLiquid.network!, + ); - const outputAmount = 1245412; - const { transaction, vout } = await walletLiquid.sendToAddress( - walletLiquid.encodeAddress(script), - outputAmount, - undefined, - '', - ); + const outputAmount = 1245412; + const { transaction, vout } = await walletLiquid.sendToAddress( + walletLiquid.encodeAddress(script)[blindingType], + outputAmount, + undefined, + '', + ); - expect( - getOutputValue( - walletLiquid, - (transaction as LiquidTransaction).outs[vout!], - ), - ).toEqual(outputAmount); + expect( + getOutputValue( + walletLiquid, + (transaction as LiquidTransaction).outs[vout!], + ), + ).toEqual(outputAmount); - // Wrong asset hash + // Wrong asset hash - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - walletLiquid['network'] = networks.liquid; - expect( - getOutputValue( - walletLiquid, - (transaction as LiquidTransaction).outs[vout!], - ), - ).toEqual(0); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + walletLiquid['network'] = networks.liquid; + expect( + getOutputValue( + walletLiquid, + (transaction as LiquidTransaction).outs[vout!], + ), + ).toEqual(0); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - walletLiquid['network'] = networks.regtest; - }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + walletLiquid['network'] = networks.regtest; + }, + ); test('should construct legacy claim details', async () => { const preimage = randomBytes(32); @@ -226,7 +236,7 @@ describe('Core', () => { const tx = Transaction.fromHex( await bitcoinClient.getRawTransaction( await bitcoinClient.sendToAddress( - wallet.encodeAddress(outputScript), + wallet.encodeAddress(outputScript).new, 100_00, undefined, false, @@ -280,7 +290,7 @@ describe('Core', () => { const tx = Transaction.fromHex( await bitcoinClient.getRawTransaction( await bitcoinClient.sendToAddress( - wallet.encodeAddress(outputScript), + wallet.encodeAddress(outputScript).new, 100_00, undefined, false, diff --git a/test/integration/service/ElementsService.spec.ts b/test/integration/service/ElementsService.spec.ts index 6c7455d10..001a4e647 100644 --- a/test/integration/service/ElementsService.spec.ts +++ b/test/integration/service/ElementsService.spec.ts @@ -16,15 +16,16 @@ import { bitcoinClient, elementsClient } from '../Nodes'; jest.mock('../../../lib/db/repositories/ChainTipRepository'); -const slip77 = SLIP77Factory(ecc).fromSeed( - mnemonicToSeedSync(generateMnemonic()), -); +const slip77 = SLIP77Factory(ecc); describe('ElementsService', () => { const wallet = new WalletLiquid( Logger.disabledLogger, new ElementsWalletProvider(Logger.disabledLogger, elementsClient), - slip77, + { + new: slip77.fromSeed(mnemonicToSeedSync(generateMnemonic())), + legacy: slip77.fromSeed(generateMnemonic()), + }, networks.regtest, ); @@ -54,50 +55,60 @@ describe('ElementsService', () => { [bitcoinClient, elementsClient].map((client) => client.disconnect()); }); - test('should unblind outputs that were blinded by known keys', async () => { - const script = wallet.decodeAddress(await wallet.getAddress('')); - const address = wallet.encodeAddress(script); - const amount = 100_000; - - const tx = Transaction.fromHex( - await elementsClient.getRawTransaction( - await elementsClient.sendToAddress( - address, - amount, - undefined, - false, - '', + test.each` + blindingType + ${'new'} + ${'legacy'} + `( + 'should unblind outputs that were blinded by known keys of type $blindingType', + async ({ blindingType }) => { + const script = wallet.decodeAddress(await wallet.getAddress('')); + const address = wallet.encodeAddress(script)[blindingType]; + const amount = 100_000; + + const tx = Transaction.fromHex( + await elementsClient.getRawTransaction( + await elementsClient.sendToAddress( + address, + amount, + undefined, + false, + '', + ), ), - ), - ); + ); - const unblinded = await es.unblindOutputs(tx); + const unblinded = await es.unblindOutputs(tx); - expect(unblinded.every((out) => out.isLbtc)).toEqual(true); - expect(unblinded.every((out) => out.value > 0)).toEqual(true); - expect( - unblinded.every((out) => - out.asset.equals(getHexBuffer(networks.regtest.assetHash)), - ), - ).toEqual(true); + expect(unblinded.every((out) => out.isLbtc)).toEqual(true); + expect(unblinded.every((out) => out.value > 0)).toEqual(true); + expect( + unblinded.every((out) => + out.asset.equals(getHexBuffer(networks.regtest.assetHash)), + ), + ).toEqual(true); - const output = unblinded.find((out) => out.script.equals(script))!; - expect(output).not.toBeUndefined(); - expect(output.value).toEqual(amount); - }); + const output = unblinded.find((out) => out.script.equals(script))!; + expect(output).not.toBeUndefined(); + expect(output.value).toEqual(amount); + }, + ); test('should unblind outputs that were blinded by unknown keys', async () => { const secondWallet = new WalletLiquid( Logger.disabledLogger, new ElementsWalletProvider(Logger.disabledLogger, elementsClient), - SLIP77Factory(ecc).fromSeed(mnemonicToSeedSync(generateMnemonic())), + { + new: slip77.fromSeed(mnemonicToSeedSync(generateMnemonic())), + legacy: slip77.fromSeed(generateMnemonic()), + }, networks.regtest, ); const script = secondWallet.decodeAddress( await secondWallet.getAddress(''), ); - const address = secondWallet.encodeAddress(script); + const address = secondWallet.encodeAddress(script).new; const tx = Transaction.fromHex( await elementsClient.getRawTransaction( @@ -159,7 +170,7 @@ describe('ElementsService', () => { ${'el1qqwts7wxqn32rv9jdp86dr06q5y6keuxkjgdjmpn8x50jfepgspvwlw7hzcr33r9mf0yees30g2r8mrvpnn73qya0jqg3za6hu'} `('should derive blinding keys for $address', ({ address }) => { const script = wallet.decodeAddress(address); - const { publicKey, privateKey } = slip77.derive(script); + const { publicKey, privateKey } = wallet['slip77s'].new.derive(script); expect(es.deriveBlindingKeys(address)).toEqual({ publicKey, diff --git a/test/integration/service/TransactionFetcher.spec.ts b/test/integration/service/TransactionFetcher.spec.ts index 09c59a2e3..53c7dec76 100644 --- a/test/integration/service/TransactionFetcher.spec.ts +++ b/test/integration/service/TransactionFetcher.spec.ts @@ -329,8 +329,10 @@ describe('TransactionFetcher', () => { let transaction: Transaction; const wallet = { - encodeAddress: (outputScript) => - address.fromOutputScript(outputScript, Networks.bitcoinRegtest), + encodeAddress: (outputScript) => ({ + new: address.fromOutputScript(outputScript, Networks.bitcoinRegtest), + legacy: address.fromOutputScript(outputScript, Networks.bitcoinRegtest), + }), } as Wallet; beforeAll(async () => { @@ -357,9 +359,10 @@ describe('TransactionFetcher', () => { expect(swaps.swapLockups).toEqual([{ id: 'swap' }]); expect(swaps.chainSwapLockups).toEqual([{ id: 'chain' }]); - const outputAddresses = transaction.outs.map((output) => + const outputAddresses = transaction.outs.flatMap((output) => [ address.fromOutputScript(output.script, Networks.bitcoinRegtest), - ); + address.fromOutputScript(output.script, Networks.bitcoinRegtest), + ]); expect(SwapRepository.getSwaps).toHaveBeenCalledTimes(1); expect(SwapRepository.getSwaps).toHaveBeenCalledWith({ diff --git a/test/integration/service/cooperative/ChainSwapSigner.spec.ts b/test/integration/service/cooperative/ChainSwapSigner.spec.ts index 650af4997..645c1ec5f 100644 --- a/test/integration/service/cooperative/ChainSwapSigner.spec.ts +++ b/test/integration/service/cooperative/ChainSwapSigner.spec.ts @@ -82,7 +82,10 @@ describe('ChainSwapSigner', () => { const liquidWallet = new WalletLiquid( Logger.disabledLogger, new ElementsWalletProvider(Logger.disabledLogger, elementsClient), - slip77.fromSeed(mnemonic), + { + legacy: slip77.fromSeed(mnemonic), + new: slip77.fromSeed(mnemonicToSeedSync(mnemonic)), + }, LiquidNetworks.liquidRegtest, ); @@ -162,7 +165,7 @@ describe('ChainSwapSigner', () => { currency.type, await currency.chainClient!.getRawTransaction( await currency.chainClient!.sendToAddress( - wallet.encodeAddress(lockupScript), + wallet.encodeAddress(lockupScript).new, 100_000, undefined, false, @@ -183,7 +186,8 @@ describe('ChainSwapSigner', () => { timeoutBlockHeight, blindingPrivateKey: currency.type === CurrencyType.Liquid - ? liquidWallet.deriveBlindingKeyFromScript(lockupScript).privateKey! + ? liquidWallet.deriveBlindingKeyFromScript(lockupScript).new + .privateKey! : undefined, }; }; diff --git a/test/integration/service/cooperative/CoopSignerBase.spec.ts b/test/integration/service/cooperative/CoopSignerBase.spec.ts index c28590be1..08cb6fde8 100644 --- a/test/integration/service/cooperative/CoopSignerBase.spec.ts +++ b/test/integration/service/cooperative/CoopSignerBase.spec.ts @@ -89,7 +89,7 @@ describe('CoopSignerBase', () => { ); const txId = await bitcoinClient.sendToAddress( - wallet.encodeAddress(p2trOutput(tweakedKey)), + wallet.encodeAddress(p2trOutput(tweakedKey)).new, 100_000, undefined, false, @@ -169,7 +169,8 @@ describe('CoopSignerBase', () => { expect(toClaim.cooperative!.transaction.outs).toHaveLength(1); expect( - wallet.encodeAddress(toClaim.cooperative!.transaction.outs[0].script), + wallet.encodeAddress(toClaim.cooperative!.transaction.outs[0].script) + .new, ).toEqual(toClaim.cooperative!.sweepAddress); }); diff --git a/test/integration/service/cooperative/DeferredClaimer.spec.ts b/test/integration/service/cooperative/DeferredClaimer.spec.ts index 099e782c2..d7235578e 100644 --- a/test/integration/service/cooperative/DeferredClaimer.spec.ts +++ b/test/integration/service/cooperative/DeferredClaimer.spec.ts @@ -178,7 +178,7 @@ describe('DeferredClaimer', () => { const tx = Transaction.fromHex( await bitcoinClient.getRawTransaction( await bitcoinClient.sendToAddress( - btcWallet.encodeAddress(p2trOutput(tweakedKey)), + btcWallet.encodeAddress(p2trOutput(tweakedKey)).new, 100_000, undefined, false, diff --git a/test/unit/swap/SwapManager.spec.ts b/test/unit/swap/SwapManager.spec.ts index fa59c2d76..2c865ab8e 100644 --- a/test/unit/swap/SwapManager.spec.ts +++ b/test/unit/swap/SwapManager.spec.ts @@ -131,7 +131,9 @@ const mockDecodeAddress = jest.fn().mockImplementation((toDecode: string) => { const mockEncodeAddress = jest .fn() .mockImplementation((outputScript: Buffer) => { - return address.fromOutputScript(outputScript, Networks.bitcoinRegtest); + return { + new: address.fromOutputScript(outputScript, Networks.bitcoinRegtest), + }; }); jest.mock('../../../lib/wallet/Wallet', () => { @@ -160,9 +162,11 @@ const mockWallets = new Map([ addressLiquid.toOutputScript(address, LiquidNetworks.liquidRegtest), ), deriveBlindingKeyFromScript: jest.fn().mockReturnValue({ - privateKey: getHexBuffer( - '4e09bc9895ccef1eab4e2e67adcff67be2af26110ffb35f26592688c0e88dc76', - ), + new: { + privateKey: getHexBuffer( + '4e09bc9895ccef1eab4e2e67adcff67be2af26110ffb35f26592688c0e88dc76', + ), + }, }), } as any, ], diff --git a/test/unit/swap/UtxoNursery.spec.ts b/test/unit/swap/UtxoNursery.spec.ts index ce65eb619..ccf9a47a9 100644 --- a/test/unit/swap/UtxoNursery.spec.ts +++ b/test/unit/swap/UtxoNursery.spec.ts @@ -113,7 +113,7 @@ const mockDecodeAddress = jest.fn().mockImplementation((toDecode: string) => { const encodeAddress = (script: Buffer) => address.fromOutputScript(script, Networks.bitcoinMainnet); const mockEncodeAddress = jest.fn().mockImplementation((script: Buffer) => { - return encodeAddress(script); + return { new: encodeAddress(script), legacy: encodeAddress(script) }; }); let mockGetKeysByIndexResult: ECPairInterface | undefined = undefined; diff --git a/test/unit/wallet/Wallet.spec.ts b/test/unit/wallet/Wallet.spec.ts index f1754a86e..5447e1362 100644 --- a/test/unit/wallet/Wallet.spec.ts +++ b/test/unit/wallet/Wallet.spec.ts @@ -101,7 +101,10 @@ describe('Wallet', () => { const walletLiquid = new WalletLiquid( Logger.disabledLogger, walletProvider, - slip77.fromSeed(mnemonic), + { + new: slip77.fromSeed(mnemonicToSeedSync(mnemonic)), + legacy: slip77.fromSeed(mnemonic), + }, networkLiquid.liquid, ); @@ -144,7 +147,9 @@ describe('Wallet', () => { }); test('should encode addresses', () => { - expect(wallet.encodeAddress(encodeOutput)).toEqual(encodedAddress); + const enc = wallet.encodeAddress(encodeOutput); + expect(enc.new).toEqual(encodedAddress); + expect(enc.legacy).toEqual(encodedAddress); }); test('should ignore OP_RETURN outputs', () => { @@ -153,7 +158,10 @@ describe('Wallet', () => { crypto.sha256(randomBytes(64)), ]); - expect(wallet.encodeAddress(outputScript)).toEqual(''); + expect(wallet.encodeAddress(outputScript)).toEqual({ + new: '', + legacy: '', + }); }); test('should ignore all invalid addresses', () => { @@ -171,7 +179,10 @@ describe('Wallet', () => { ]; for (const script of invalidScripts) { - expect(wallet.encodeAddress(script)).toEqual(''); + expect(wallet.encodeAddress(script)).toEqual({ + new: '', + legacy: '', + }); } }); @@ -233,14 +244,18 @@ describe('Wallet', () => { test('should blind Liquid addresses', () => { expect(walletLiquid.type).toEqual(CurrencyType.Liquid); - expect( - walletLiquid.encodeAddress(encodeOutput).startsWith('lq1qq'), - ).toBeTruthy(); + const enc = walletLiquid.encodeAddress(encodeOutput); + expect(enc.new.startsWith('lq1qq')).toBeTruthy(); + expect(enc.legacy.startsWith('lq1qq')).toBeTruthy(); + + expect(enc.new).not.toEqual(enc.legacy); }); test('should encode unblinded Liquid addresses', () => { - expect( - walletLiquid.encodeAddress(encodeOutput, false).startsWith('ex'), - ).toBeTruthy(); + const enc = walletLiquid.encodeAddress(encodeOutput, false); + expect(enc.new.startsWith('ex')).toBeTruthy(); + expect(enc.legacy.startsWith('ex')).toBeTruthy(); + + expect(enc.new).toEqual(enc.legacy); }); }); diff --git a/test/unit/wallet/WalletLiquid.spec.ts b/test/unit/wallet/WalletLiquid.spec.ts index 7441a5994..b8b1bca81 100644 --- a/test/unit/wallet/WalletLiquid.spec.ts +++ b/test/unit/wallet/WalletLiquid.spec.ts @@ -1,4 +1,5 @@ import ops from '@boltz/bitcoin-ops'; +import { mnemonicToSeedSync } from 'bip39'; import { crypto } from 'bitcoinjs-lib'; import { toXOnly } from 'bitcoinjs-lib/src/psbt/bip371'; import { Scripts } from 'boltz-core'; @@ -12,9 +13,10 @@ import WalletLiquid from '../../../lib/wallet/WalletLiquid'; import WalletProviderInterface from '../../../lib/wallet/providers/WalletProviderInterface'; describe('WalletLiquid', () => { - const slip77 = SLIP77Factory(ecc).fromSeed( - 'test test test test test test test test test test test junk', - ); + const slip77 = SLIP77Factory(ecc); + const mnemonic = + 'test test test test test test test test test test test junk'; + const provider = { serviceName: () => 'Elements', } as WalletProviderInterface; @@ -22,7 +24,10 @@ describe('WalletLiquid', () => { const wallet = new WalletLiquid( Logger.disabledLogger, provider, - slip77, + { + new: slip77.fromSeed(mnemonicToSeedSync(mnemonic)), + legacy: slip77.fromSeed(mnemonic), + }, Networks.liquidRegtest, ); wallet.initKeyProvider('', 0, {} as any); @@ -38,8 +43,12 @@ describe('WalletLiquid', () => { ${'mainnet'} | ${Networks.liquidMainnet} | ${false} `('should check if $name supports discount CT', ({ network, support }) => { expect( - new WalletLiquid(Logger.disabledLogger, provider, slip77, network) - .supportsDiscountCT, + new WalletLiquid( + Logger.disabledLogger, + provider, + wallet['slip77s'], + network, + ).supportsDiscountCT, ).toEqual(support); }); @@ -52,7 +61,10 @@ describe('WalletLiquid', () => { const blindingKey = wallet.deriveBlindingKeyFromScript( address.toOutputScript(addr, Networks.liquidRegtest), ); - expect(blindingKey.privateKey).toMatchSnapshot(); + expect({ + new: blindingKey.new.privateKey, + legacy: blindingKey.new.privateKey, + }).toMatchSnapshot(); }); test.each` @@ -75,21 +87,23 @@ describe('WalletLiquid', () => { ); test('should return empty string as address for an empty script', () => { - expect(wallet.encodeAddress(Buffer.alloc(0), true)).toEqual(''); - expect(wallet.encodeAddress(Buffer.alloc(0), false)).toEqual(''); + expect(wallet.encodeAddress(Buffer.alloc(0), true).new).toEqual(''); + expect(wallet.encodeAddress(Buffer.alloc(0), true).legacy).toEqual(''); + expect(wallet.encodeAddress(Buffer.alloc(0), false).new).toEqual(''); + expect(wallet.encodeAddress(Buffer.alloc(0), false).legacy).toEqual(''); }); test('should return empty string as address for scripts that cannot be encoded', () => { const outputScript = Buffer.from([ops.OP_RETURN, 1, 2, 3]); - expect(wallet.encodeAddress(outputScript, true)).toEqual(''); - expect(wallet.encodeAddress(outputScript, false)).toEqual(''); + expect(wallet.encodeAddress(outputScript, true).new).toEqual(''); + expect(wallet.encodeAddress(outputScript, true).legacy).toEqual(''); + expect(wallet.encodeAddress(outputScript, false).new).toEqual(''); + expect(wallet.encodeAddress(outputScript, false).legacy).toEqual(''); }); test('should blind by default', () => { - expect( - wallet - .encodeAddress(Scripts.p2trOutput(toXOnly(publicKey))) - .startsWith(Networks.liquidRegtest.blech32), - ).toEqual(true); + const res = wallet.encodeAddress(Scripts.p2trOutput(toXOnly(publicKey))); + expect(res.new.startsWith(Networks.liquidRegtest.blech32)).toEqual(true); + expect(res.legacy.startsWith(Networks.liquidRegtest.blech32)).toEqual(true); }); }); diff --git a/test/unit/wallet/__snapshots__/WalletLiquid.spec.ts.snap b/test/unit/wallet/__snapshots__/WalletLiquid.spec.ts.snap index f05242dcc..12f8c4151 100644 --- a/test/unit/wallet/__snapshots__/WalletLiquid.spec.ts.snap +++ b/test/unit/wallet/__snapshots__/WalletLiquid.spec.ts.snap @@ -2,140 +2,307 @@ exports[`WalletLiquid should derive blinding keys from script for address AzpvTi6t8GTVxhg6tZ4vxNCdQKS9YYMMgJmxQAFYfhVvaogb6iVgTixvtv246tSbeM3zdgG1Z2ToreMt 1`] = ` { - "data": [ - 106, - 224, - 188, - 246, - 172, - 239, - 156, - 229, - 189, - 135, - 73, - 11, - 203, - 152, - 88, - 222, - 36, - 241, - 201, - 64, - 47, - 31, - 102, - 79, - 140, - 247, - 159, - 225, - 105, - 38, - 82, - 74, - ], - "type": "Buffer", + "legacy": { + "data": [ + 29, + 186, + 236, + 127, + 196, + 39, + 209, + 91, + 243, + 244, + 186, + 10, + 168, + 220, + 228, + 192, + 229, + 167, + 48, + 114, + 37, + 168, + 224, + 59, + 183, + 247, + 186, + 49, + 38, + 215, + 131, + 96, + ], + "type": "Buffer", + }, + "new": { + "data": [ + 29, + 186, + 236, + 127, + 196, + 39, + 209, + 91, + 243, + 244, + 186, + 10, + 168, + 220, + 228, + 192, + 229, + 167, + 48, + 114, + 37, + 168, + 224, + 59, + 183, + 247, + 186, + 49, + 38, + 215, + 131, + 96, + ], + "type": "Buffer", + }, } `; exports[`WalletLiquid should derive blinding keys from script for address CTEvqk9mbKSWnkPhF7DwqHf5X5Jx1Q25LXh4sprdqj4KRgMZTZaiGrhCCDfWrDyVqBbxUrhyCtLwgB7J 1`] = ` { - "data": [ - 9, - 92, - 208, - 181, - 55, - 69, - 139, - 221, - 246, - 80, - 157, - 160, - 170, - 232, - 140, - 154, - 1, - 116, - 134, - 90, - 46, - 200, - 30, - 67, - 0, - 44, - 174, - 107, - 164, - 253, - 26, - 160, - ], - "type": "Buffer", + "legacy": { + "data": [ + 76, + 102, + 175, + 87, + 146, + 230, + 75, + 33, + 136, + 123, + 191, + 76, + 90, + 254, + 173, + 205, + 88, + 159, + 82, + 220, + 249, + 103, + 108, + 7, + 219, + 120, + 133, + 184, + 23, + 47, + 48, + 59, + ], + "type": "Buffer", + }, + "new": { + "data": [ + 76, + 102, + 175, + 87, + 146, + 230, + 75, + 33, + 136, + 123, + 191, + 76, + 90, + 254, + 173, + 205, + 88, + 159, + 82, + 220, + 249, + 103, + 108, + 7, + 219, + 120, + 133, + 184, + 23, + 47, + 48, + 59, + ], + "type": "Buffer", + }, } `; exports[`WalletLiquid should derive blinding keys from script for address el1qqwxdjljvzdukcfxeammq6jktrvcurh329k7j208pm4rwe7anu2luhh7255kde06j2et2d0g5ym6yy949rx6rkp04xeav62vjp 1`] = ` { - "data": [ - 171, - 75, - 129, - 24, - 207, - 84, - 235, - 215, - 147, - 250, - 37, - 130, - 147, - 233, - 213, - 160, - 19, - 41, - 33, - 225, - 54, - 79, - 71, - 225, - 253, - 123, - 142, - 144, - 95, - 102, - 250, - 29, - ], - "type": "Buffer", + "legacy": { + "data": [ + 15, + 226, + 144, + 9, + 189, + 45, + 141, + 155, + 16, + 252, + 77, + 160, + 140, + 56, + 254, + 130, + 223, + 223, + 209, + 15, + 178, + 48, + 78, + 243, + 96, + 11, + 218, + 176, + 210, + 117, + 27, + 32, + ], + "type": "Buffer", + }, + "new": { + "data": [ + 15, + 226, + 144, + 9, + 189, + 45, + 141, + 155, + 16, + 252, + 77, + 160, + 140, + 56, + 254, + 130, + 223, + 223, + 209, + 15, + 178, + 48, + 78, + 243, + 96, + 11, + 218, + 176, + 210, + 117, + 27, + 32, + ], + "type": "Buffer", + }, } `; -exports[`WalletLiquid should encode P2PKH address (confidential: false) 1`] = `"2df2n73X6jTPddy31rjMfgZhxC6bkcEU9XT"`; +exports[`WalletLiquid should encode P2PKH address (confidential: false) 1`] = ` +{ + "legacy": "2df2n73X6jTPddy31rjMfgZhxC6bkcEU9XT", + "new": "2df2n73X6jTPddy31rjMfgZhxC6bkcEU9XT", +} +`; -exports[`WalletLiquid should encode P2PKH address (confidential: true) 1`] = `"CTEnqWfNvTGTtoYYQL6WiAvNcHDWBWa1rSYBfph2zsUGHBafMs2P8wetoiQSwzvyzQjZK12EzBgZpkLW"`; +exports[`WalletLiquid should encode P2PKH address (confidential: true) 1`] = ` +{ + "legacy": "CTEnqWfNvTGTtoYYQL6WiAvNcHDWBWa1rSYBfph2zsUGHBafMs2P8wetoiQSwzvyzQjZK12EzBgZpkLW", + "new": "CTEmC4Fuy5mW1WadkUtGrNRMbzVjdhvV7XpsmQdQpoouzgeb4Cj1bTAyiyLSNayRtWhvn8aowyGDYsGQ", +} +`; -exports[`WalletLiquid should encode P2SH address (confidential: false) 1`] = `"XGxGZfq18bFSncj9wc4g6WA5VPNprJdAmg"`; +exports[`WalletLiquid should encode P2SH address (confidential: false) 1`] = ` +{ + "legacy": "XGxGZfq18bFSncj9wc4g6WA5VPNprJdAmg", + "new": "XGxGZfq18bFSncj9wc4g6WA5VPNprJdAmg", +} +`; -exports[`WalletLiquid should encode P2SH address (confidential: true) 1`] = `"Azppnmg47wJVTyJvBWUR4vkgZQ6gkK2VkJJrwsUbiLWGmFMWZDScTd6NmrX3LND5yM2UE9SGesLs5KfX"`; +exports[`WalletLiquid should encode P2SH address (confidential: true) 1`] = ` +{ + "legacy": "Azppnmg47wJVTyJvBWUR4vkgZQ6gkK2VkJJrwsUbiLWGmFMWZDScTd6NmrX3LND5yM2UE9SGesLs5KfX", + "new": "Azpnzu3T6kDTbKLcQW7GnTqB5gK4jRCzsuVVjhFZbxr8MBAcjrRRFgK2ZAq8N2Nqu5zr8g1cQFHmTsTD", +} +`; -exports[`WalletLiquid should encode P2TR address (confidential: false) 1`] = `"ert1p7qypc2gpr437ws0yhl3yvk57rweq8pfdyw04g8vjmjxeus9ak0nqwf79lm"`; +exports[`WalletLiquid should encode P2TR address (confidential: false) 1`] = ` +{ + "legacy": "ert1p7qypc2gpr437ws0yhl3yvk57rweq8pfdyw04g8vjmjxeus9ak0nqwf79lm", + "new": "ert1p7qypc2gpr437ws0yhl3yvk57rweq8pfdyw04g8vjmjxeus9ak0nqwf79lm", +} +`; -exports[`WalletLiquid should encode P2TR address (confidential: true) 1`] = `"el1pq2w6dcmt9smmce3ear69m9yuk328gm6hx05vyk93uutv0ugzx7mp0uqgrs5sz8truaq7f0lzgedfuxajqwzj6gul2swe9hydneqtmvlxvadnu96fepd2"`; +exports[`WalletLiquid should encode P2TR address (confidential: true) 1`] = ` +{ + "legacy": "el1pq2w6dcmt9smmce3ear69m9yuk328gm6hx05vyk93uutv0ugzx7mp0uqgrs5sz8truaq7f0lzgedfuxajqwzj6gul2swe9hydneqtmvlxvadnu96fepd2", + "new": "el1pq2zv23w6zq3hhe9tp6e4huzs353pjzg7he4vz72c7t8ul7en9mun9uqgrs5sz8truaq7f0lzgedfuxajqwzj6gul2swe9hydneqtmvlx6qsuxc6rp3e5", +} +`; -exports[`WalletLiquid should encode P2WPKH address (confidential: false) 1`] = `"ert1q84ufadu3juwu6zjcdcweqhe9ewhwxrvtj3lmzj"`; +exports[`WalletLiquid should encode P2WPKH address (confidential: false) 1`] = ` +{ + "legacy": "ert1q84ufadu3juwu6zjcdcweqhe9ewhwxrvtj3lmzj", + "new": "ert1q84ufadu3juwu6zjcdcweqhe9ewhwxrvtj3lmzj", +} +`; -exports[`WalletLiquid should encode P2WPKH address (confidential: true) 1`] = `"el1qqdx8446sv2hzmnphuzkt2ct848n05zt6r6ue72nl56l6xjf2p0exk0tcn6mer9cae599smsajp0jtjawuvxcklqsf6r8tdrqz"`; +exports[`WalletLiquid should encode P2WPKH address (confidential: true) 1`] = ` +{ + "legacy": "el1qqdx8446sv2hzmnphuzkt2ct848n05zt6r6ue72nl56l6xjf2p0exk0tcn6mer9cae599smsajp0jtjawuvxcklqsf6r8tdrqz", + "new": "el1qqgvxf2pj4v384v5ju9t8ru6avh3c3xqwez8nlnvuq8jvyataykygy0tcn6mer9cae599smsajp0jtjawuvxckk33f057ud4s8", +} +`; -exports[`WalletLiquid should encode P2WSH address (confidential: false) 1`] = `"ert1qxxywq89mszpuv6t4puyfypf8qafcdg4zcwed69ku4sr6tlg0tteq7kr8c6"`; +exports[`WalletLiquid should encode P2WSH address (confidential: false) 1`] = ` +{ + "legacy": "ert1qxxywq89mszpuv6t4puyfypf8qafcdg4zcwed69ku4sr6tlg0tteq7kr8c6", + "new": "ert1qxxywq89mszpuv6t4puyfypf8qafcdg4zcwed69ku4sr6tlg0tteq7kr8c6", +} +`; -exports[`WalletLiquid should encode P2WSH address (confidential: true) 1`] = `"el1qqfq4vetpg6ly3dapjcltgyg0475nqcq4592tcvuc25t2t0y84s692vvguqwthqyrce5h2rcgjgzjwp6ns6329sajm5tdetq85h7s7khj388xz2mq4qgg"`; +exports[`WalletLiquid should encode P2WSH address (confidential: true) 1`] = ` +{ + "legacy": "el1qqfq4vetpg6ly3dapjcltgyg0475nqcq4592tcvuc25t2t0y84s692vvguqwthqyrce5h2rcgjgzjwp6ns6329sajm5tdetq85h7s7khj388xz2mq4qgg", + "new": "el1qqt3q2gtj0zxadv57n5e3q3rry4w8nsek92clvx30zxcwuf828n2qxvvguqwthqyrce5h2rcgjgzjwp6ns6329sajm5tdetq85h7s7khj7vkrq76xq9tg", +} +`;