From 42bc3fb6906fdf67016ee5ba134939e981382f69 Mon Sep 17 00:00:00 2001 From: Polybius93 Date: Fri, 9 Aug 2024 13:02:17 +0200 Subject: [PATCH] feat: add dust limit when using selectUTXO function --- package.json | 2 +- src/functions/bitcoin/psbt-functions.ts | 4 + tests/mocks/attestor.test.constants.ts | 3 + tests/mocks/bitcoin-account.test.constants.ts | 41 ++++ .../bitcoin-transaction.test.constants.ts | 218 ++++++++++++++++++ tests/mocks/bitcoin.test.constants.ts | 10 +- tests/unit/psbt-functions.test.ts | 87 +++++++ 7 files changed, 361 insertions(+), 4 deletions(-) create mode 100644 tests/mocks/bitcoin-account.test.constants.ts create mode 100644 tests/unit/psbt-functions.test.ts diff --git a/package.json b/package.json index ab52d7b..a7aa947 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "dlc-btc-lib", - "version": "2.1.2", + "version": "2.1.3", "description": "This library provides a comprehensive set of interfaces and functions for minting dlcBTC tokens on supported blockchains.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/functions/bitcoin/psbt-functions.ts b/src/functions/bitcoin/psbt-functions.ts index 66d1b3f..5cf6c67 100644 --- a/src/functions/bitcoin/psbt-functions.ts +++ b/src/functions/bitcoin/psbt-functions.ts @@ -70,6 +70,7 @@ export async function createFundingTransaction( bip69: false, createTx: true, network: bitcoinNetwork, + dust: 546n as unknown as number, }); if (!selected) { @@ -165,6 +166,7 @@ export async function createDepositTransaction( bip69: false, createTx: false, network: bitcoinNetwork, + dust: 546n as unknown as number, }); if (!additionalDepositSelected) { @@ -213,6 +215,7 @@ export async function createDepositTransaction( bip69: false, createTx: true, network: bitcoinNetwork, + dust: 546n as unknown as number, }); if (!depositSelected) { @@ -330,6 +333,7 @@ export async function createWithdrawTransaction( bip69: false, createTx: true, network: bitcoinNetwork, + dust: 546n as unknown as number, }); if (!selected) { diff --git a/tests/mocks/attestor.test.constants.ts b/tests/mocks/attestor.test.constants.ts index eadc1cb..4f5e5dd 100644 --- a/tests/mocks/attestor.test.constants.ts +++ b/tests/mocks/attestor.test.constants.ts @@ -4,5 +4,8 @@ export const TEST_REGTEST_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1 = export const TEST_TESTNET_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY_1 = 'tpubDDRekL64eJJav32TLhNhG59qra7wAMaei8YMGXNiJE8ksdYrKgvaFM1XG6JrSt31W97XryScrX37RUEujjZT4qScNf8Zu1JxWj4VYkwz4rU'; +export const TEST_REGTEST_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1 = + '02532fc9ebdb559f900e8b469e2e1caebbf5cf84c22a2376a47105cf5003e0ca08'; + export const TEST_TESTNET_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1 = '027eda4d625f781dcc98bf68901360fdaaacce8ed466096c1dfe4865209b28c058'; diff --git a/tests/mocks/bitcoin-account.test.constants.ts b/tests/mocks/bitcoin-account.test.constants.ts new file mode 100644 index 0000000..38c3ff6 --- /dev/null +++ b/tests/mocks/bitcoin-account.test.constants.ts @@ -0,0 +1,41 @@ +// Test Account 1 [Native Segwit] +export const TEST_BITCOIN_REGTEST_NATIVE_SEGWIT_ADDRESS_1 = + 'bcrt1qd7c30f36p9wauhnxyvvsglsmhveydlpaqrz6md'; +export const TEST_BITCOIN_REGTEST_NATIVE_SEGWIT_FINGERPRINT_1 = '658a4dd0'; +export const TEST_BITCOIN_REGTEST_NATIVE_SEGWIT_MNEMONIC_1 = + 'eternal unusual lava army roast joy pact knife book boring flame wrap retreat false dizzy peanut giraffe purpose border pride oblige possible surround grit'; +export const TEST_BITCOIN_REGTEST_NATIVE_SEGWIT_XPRIV_1 = + 'tprv8ZgxMBicQKsPeLTYcChqE6UbJWgchiXXdMHrmbuBQyrS74cQeWde7w6C7htZJBuFBxXpppvRyjPHZD17GHGiY82mGhf5yMDAoX1cqNkLxiP'; +export const TEST_BITCOIN_REGTEST_NATIVE_SEGWIT_PUBLIC_KEY_1 = + '0246136ae941f2e810032f01f3545b56b9590cf4a29b67125d3e35916ecce34824'; + +export const TEST_BITCOIN_REGTEST_NATIVE_SEGWIT_UTXOS_1 = [ + { + type: 'wpkh', + script: Uint8Array.from([ + 0, 20, 111, 177, 23, 166, 58, 9, 93, 222, 94, 102, 35, 25, 4, 126, 27, 187, 50, 70, 252, 61, + ]), + address: 'bcrt1qd7c30f36p9wauhnxyvvsglsmhveydlpaqrz6md', + txid: 'abb220f97c47289175af1ca1d199c19c9c37190bc6ea651e69e04cbd09f19501', + index: 0, + value: 100000000, + witnessUtxo: { + script: Uint8Array.from([ + 0, 20, 111, 177, 23, 166, 58, 9, 93, 222, 94, 102, 35, 25, 4, 126, 27, 187, 50, 70, 252, 61, + ]), + amount: 100000000n, + }, + redeemScript: undefined, + }, +]; + +// Test Account 2 [Taproot] +export const TEST_BITCOIN_REGTEST_TAPROOT_ADDRESS_1 = + 'bcrt1parwpen3d4qy20r958p73dh34963628wmhmyynpewuh4jxtazjyms07xejy'; +export const TEST_BITCOIN_REGTEST_TAPROOT_FINGERPRINT_1 = '4fc36ddd'; +export const TEST_BITCOIN_REGTEST_TAPROOT_MNEMONIC_1 = + 'stuff swim subway brush rice charge female express pride shoot coil canyon gorilla agent waste much limit kiss settle employ extend high swallow ghost'; +export const TEST_BITCOIN_REGTEST_TAPROOT_XPRIV_1 = + 'tprv8ZgxMBicQKsPdTyjFcdrTeCPBWtCi7rkhgGJmRL6FFvuoVs64nBgwVwaMYekqJZScKCYXVfaq9g3t8CjNauFScbfruRrnub2BD2qxULPwqQ'; +export const TEST_BITCOIN_REGTEST_TAPROOT_PUBLIC_KEY_1 = + '03f5963cfb8abe2b2673ed20831d921e4403b9e5cef66b4d54c5cfb91e8f6bc3c4'; diff --git a/tests/mocks/bitcoin-transaction.test.constants.ts b/tests/mocks/bitcoin-transaction.test.constants.ts index cbf7124..72e824d 100644 --- a/tests/mocks/bitcoin-transaction.test.constants.ts +++ b/tests/mocks/bitcoin-transaction.test.constants.ts @@ -1098,3 +1098,221 @@ export const TEST_TESTNET_FUNDING_TRANSACTION_3: BitcoinTransaction = { block_time: 1720620175, }, }; + +export const TEST_REGTEST_FUNDING_TRANSACTION_1: BitcoinTransaction = { + txid: 'e0e1ddc044ba87ad769039f7af507556724defe4576c872dd1271e5740422e37', + version: 2, + locktime: 0, + vin: [ + { + txid: '1b78a88cea3871f0404eae7f94789d3fde0828d73f91ec68fb85d8b0f940650d', + vout: 2, + prevout: { + scriptpubkey: '0014b500f5f6ce6b3aa7f5a871c49ab08ff38b451501', + scriptpubkey_asm: 'OP_0 OP_PUSHBYTES_20 b500f5f6ce6b3aa7f5a871c49ab08ff38b451501', + scriptpubkey_type: 'v0_p2wpkh', + scriptpubkey_address: 'bcrt1qk5q0takwdva20adgw8zf4vy07w9529gpfkrv6v', + value: 21848076, + }, + scriptsig: '', + scriptsig_asm: '', + witness: [ + '3044022059f91dfec1d446de68d16b3841158fe059409feec8e5871a4251b93fc94f10e102202dfd021e88f96454eb472850a41464bcbb50ce7c6f7a831f196ecf043f342abc01', + '02ab6fdbc88fb3d52dc83e5e92f27d26bfc3d161d24b50fa299f3c571f0d5a3220', + ], + is_coinbase: false, + sequence: 4294967280, + }, + ], + vout: [ + { + scriptpubkey: '5120742398e7f09cf7e3516d9ddee465f51f5945efed82df5ee0491da2b900db4d63', + scriptpubkey_asm: + 'OP_PUSHNUM_1 OP_PUSHBYTES_32 742398e7f09cf7e3516d9ddee465f51f5945efed82df5ee0491da2b900db4d63', + scriptpubkey_type: 'v1_p2tr', + scriptpubkey_address: 'bcrt1pws3e3elsnnm7x5tdnh0wge04rav5tmldst04aczfrk3tjqxmf43sq7ep3c', + value: 5000000, + }, + { + scriptpubkey: '0014622c23eebbf46df254d7da8e1c4d95d4f5c7d69f', + scriptpubkey_asm: 'OP_0 OP_PUSHBYTES_20 622c23eebbf46df254d7da8e1c4d95d4f5c7d69f', + scriptpubkey_type: 'v0_p2wpkh', + scriptpubkey_address: 'bcrt1qvgkz8m4m73kly4xhm28pcnv46n6u045lfq9ta3', + value: 6000, + }, + { + scriptpubkey: '0014b500f5f6ce6b3aa7f5a871c49ab08ff38b451501', + scriptpubkey_asm: 'OP_0 OP_PUSHBYTES_20 b500f5f6ce6b3aa7f5a871c49ab08ff38b451501', + scriptpubkey_type: 'v0_p2wpkh', + scriptpubkey_address: 'bcrt1qk5q0takwdva20adgw8zf4vy07w9529gpfkrv6v', + value: 16841708, + }, + ], + size: 265, + weight: 733, + fee: 368, + status: { + confirmed: true, + block_height: 425893, + block_hash: '4c08cbb2509350b5673ebf0baed1f4b501ee8f61436e9e3ddf153a0250e2614a', + block_time: 1722953216, + }, +}; + +export const TEST_ALICE_UTXOS_REGTEST_1 = [ + { + type: 'wpkh', + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + address: 'bcrt1qk5q0takwdva20adgw8zf4vy07w9529gpfkrv6v', + txid: '9d019c793eab883ffdc4f897e2103c325b83a597ebcd131a411d56e757b85287', + index: 2, + value: 987654, + witnessUtxo: { + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + amount: 987654n, + }, + redeemScript: undefined, + }, + { + type: 'wpkh', + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + address: 'bcrt1qk5q0takwdva20adgw8zf4vy07w9529gpfkrv6v', + txid: '7ba3319248110169f45e7ae9015826f342f9f32ccf1354c4c0806e74555367d8', + index: 2, + value: 2963654, + witnessUtxo: { + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + amount: 2963654n, + }, + redeemScript: undefined, + }, + { + type: 'wpkh', + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + address: 'bcrt1qk5q0takwdva20adgw8zf4vy07w9529gpfkrv6v', + txid: 'e3bf8b78346265ab3a69329fb006a0febdf1e75682d2c9321ee546174765e804', + index: 2, + value: 987654, + witnessUtxo: { + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + amount: 987654n, + }, + redeemScript: undefined, + }, + { + type: 'wpkh', + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + address: 'bcrt1qk5q0takwdva20adgw8zf4vy07w9529gpfkrv6v', + txid: '8c741d8fae3b71ee27fb19e86ed066673090726da4d5972d4b5119e548fb94a0', + index: 2, + value: 987654, + witnessUtxo: { + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + amount: 987654n, + }, + redeemScript: undefined, + }, + { + type: 'wpkh', + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + address: 'bcrt1qk5q0takwdva20adgw8zf4vy07w9529gpfkrv6v', + txid: 'deaeafeadc87e0277e07ccecc733a3524515b1aa412de3f418d9976ba70ef450', + index: 1, + value: 987740, + witnessUtxo: { + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + amount: 987740n, + }, + redeemScript: undefined, + }, + { + type: 'wpkh', + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + address: 'bcrt1qk5q0takwdva20adgw8zf4vy07w9529gpfkrv6v', + txid: 'e0e1ddc044ba87ad769039f7af507556724defe4576c872dd1271e5740422e37', + index: 2, + value: 16841708, + witnessUtxo: { + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + amount: 16841708n, + }, + redeemScript: undefined, + }, + { + type: 'wpkh', + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + address: 'bcrt1qk5q0takwdva20adgw8zf4vy07w9529gpfkrv6v', + txid: '48893733ae068391cfcf688a345a152595b63ac56f5b8c3228bb1f0adf8e0591', + index: 2, + value: 987654, + witnessUtxo: { + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + amount: 987654n, + }, + redeemScript: undefined, + }, +]; + +export const TEST_ALICE_UTXOS_REGTEST_2 = [ + { + type: 'wpkh', + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + address: 'bcrt1qk5q0takwdva20adgw8zf4vy07w9529gpfkrv6v', + txid: '9d019c793eab883ffdc4f897e2103c325b83a597ebcd131a411d56e757b85287', + index: 2, + value: 500000, + witnessUtxo: { + script: Uint8Array.from([ + 0, 20, 181, 0, 245, 246, 206, 107, 58, 167, 245, 168, 113, 196, 154, 176, 143, 243, 139, 69, + 21, 1, + ]), + amount: 500000n, + }, + redeemScript: undefined, + }, +]; diff --git a/tests/mocks/bitcoin.test.constants.ts b/tests/mocks/bitcoin.test.constants.ts index 400b348..7be1c88 100644 --- a/tests/mocks/bitcoin.test.constants.ts +++ b/tests/mocks/bitcoin.test.constants.ts @@ -10,9 +10,6 @@ export const TEST_UNSPENDABLE_KEY_COMMITED_TO_UUID_1 = export const TEST_UNHARDENED_DERIVED_UNSPENDABLE_KEY_COMMITED_TO_UUID_1 = '02b733c776dd7776657c20a58f1f009567afc75db226965bce83d5d0afc29e46c9'; -export const TEST_REGTEST_ATTESTOR_EXTENDED_GROUP_PUBLIC_KEY = - 'tpubDDqN2CmTDKaGeqXMayfCZEvjZqntifi4r1ztmRWsGuE1VE4bosR3mBKQwVaCxZcmg8R1nHDMDzDmzjoccBMgwZV1hhz51tAXVnhjABCQcwA'; - export const TEST_ALICE_NATIVE_SEGWIT_PAYMENT_SCRIPT_1 = '0014add70fb03578d3ac85aab80897395bb046223c92'; @@ -41,3 +38,10 @@ export const TEST_BITCOIN_EXTENDED_PRIVATE_KEY = 'tprv8ZgxMBicQKsPeJ7iQfVEb34R3JoSyJ1J9z6wv1yXJkd1NMTRbmQiLkcZXgqQ277LMtszhnp2L2VmmHhFzoLD12fjyXcAvfnvs6qTJMMcKFq'; export const TEST_BITCOIN_WALLET_ACCOUNT_INDEX = 0; + +export const TEST_FEE_RECIPIENT_PUBLIC_KEY_1 = + '031131cd88bcea8c1d84da8e034bb24c2f6e748c571922dc363e7e088f5df0436c'; + +export const TEST_FEE_DEPOSIT_BASIS_POINTS_1 = 100n; + +export const TEST_FEE_RATE_1 = 6n; diff --git a/tests/unit/psbt-functions.test.ts b/tests/unit/psbt-functions.test.ts new file mode 100644 index 0000000..9c0246d --- /dev/null +++ b/tests/unit/psbt-functions.test.ts @@ -0,0 +1,87 @@ +import { hexToBytes } from '@noble/hashes/utils'; +import { p2wpkh } from '@scure/btc-signer'; +import { P2Ret, P2TROut } from '@scure/btc-signer/payment'; +import { regtest } from 'bitcoinjs-lib/src/networks'; + +import * as bitcoinFunctions from '../../src/functions/bitcoin/bitcoin-functions'; +import { + createTaprootMultisigPayment, + deriveUnhardenedKeyPairFromRootPrivateKey, +} from '../../src/functions/bitcoin/bitcoin-functions'; +import { createFundingTransaction } from '../../src/functions/bitcoin/psbt-functions'; +import { TEST_REGTEST_BITCOIN_BLOCKCHAIN_API } from '../mocks/api.test.constants'; +import { TEST_REGTEST_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1 } from '../mocks/attestor.test.constants'; +import { + TEST_BITCOIN_REGTEST_NATIVE_SEGWIT_PUBLIC_KEY_1, + TEST_BITCOIN_REGTEST_NATIVE_SEGWIT_UTXOS_1, + TEST_BITCOIN_REGTEST_NATIVE_SEGWIT_XPRIV_1, + TEST_BITCOIN_REGTEST_TAPROOT_PUBLIC_KEY_1, +} from '../mocks/bitcoin-account.test.constants'; +import { + TEST_FEE_DEPOSIT_BASIS_POINTS_1, + TEST_FEE_RATE_1, + TEST_FEE_RECIPIENT_PUBLIC_KEY_1, + TEST_UNHARDENED_DERIVED_UNSPENDABLE_KEY_COMMITED_TO_UUID_1, +} from '../mocks/bitcoin.test.constants'; + +describe('PSBT Functions', () => { + let depositPayment: P2Ret; + let multisigPayment: P2TROut; + + beforeAll(() => { + multisigPayment = createTaprootMultisigPayment( + Buffer.from(TEST_UNHARDENED_DERIVED_UNSPENDABLE_KEY_COMMITED_TO_UUID_1, 'hex'), + Buffer.from(TEST_REGTEST_ATTESTOR_UNHARDENED_DERIVED_PUBLIC_KEY_1, 'hex'), + Buffer.from(TEST_BITCOIN_REGTEST_TAPROOT_PUBLIC_KEY_1, 'hex'), + regtest + ); + + depositPayment = p2wpkh( + Buffer.from(TEST_BITCOIN_REGTEST_NATIVE_SEGWIT_PUBLIC_KEY_1, 'hex'), + regtest + ); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('createFundingTransaction', () => { + it('should successfully create a valid funding transaction', async () => { + jest + .spyOn(bitcoinFunctions, 'getUTXOs') + .mockImplementationOnce(async () => TEST_BITCOIN_REGTEST_NATIVE_SEGWIT_UTXOS_1); + + const depositTransaction = await createFundingTransaction( + TEST_REGTEST_BITCOIN_BLOCKCHAIN_API, + regtest, + 99008600n, + multisigPayment, + depositPayment, + TEST_FEE_RATE_1, + TEST_FEE_RECIPIENT_PUBLIC_KEY_1, + TEST_FEE_DEPOSIT_BASIS_POINTS_1 + ); + + expect(depositTransaction).toBeDefined(); + expect(depositTransaction.inputsLength).toBe(1); + expect(depositTransaction.outputsLength).toBe(2); + expect(depositTransaction.getOutput(0).amount?.toString()).toBe('99008600'); + expect(depositTransaction.getOutput(0).script).toStrictEqual(multisigPayment.script); + expect(depositTransaction.getOutput(1).amount?.toString()).toBe('990086'); + expect(depositTransaction.getOutput(1).script).toStrictEqual( + p2wpkh(hexToBytes(TEST_FEE_RECIPIENT_PUBLIC_KEY_1), regtest).script + ); + + expect(() => + depositTransaction.sign( + deriveUnhardenedKeyPairFromRootPrivateKey( + TEST_BITCOIN_REGTEST_NATIVE_SEGWIT_XPRIV_1, + regtest, + 'p2wpkh', + 0 + ).privateKey! + ) + ).not.toThrow(); + }); + }); +});