From 3ffebdd3ee5620e59a93435434d97c37c570c766 Mon Sep 17 00:00:00 2001 From: ChrisCho-H Date: Fri, 18 Aug 2023 18:39:08 +0900 Subject: [PATCH 1/5] feat: calculate segwit, taproot and script hash for input/output --- utils.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/utils.js b/utils.js index 687fe66..844ed78 100644 --- a/utils.js +++ b/utils.js @@ -2,15 +2,27 @@ var TX_EMPTY_SIZE = 4 + 1 + 1 + 4 var TX_INPUT_BASE = 32 + 4 + 1 + 4 var TX_INPUT_PUBKEYHASH = 107 +var TX_INPUT_SEGWIT = 27 +var TX_INPUT_TAPROOT = 16.5 var TX_OUTPUT_BASE = 8 + 1 var TX_OUTPUT_PUBKEYHASH = 25 +var TX_OUTPUT_SCRIPTHASH = 23 +var TX_OUTPUT_SEGWIT = 22 +var TX_OUTPUT_SEGWIT_SCRIPTHASH = 34 function inputBytes (input) { - return TX_INPUT_BASE + (input.script ? input.script.length : TX_INPUT_PUBKEYHASH) + return TX_INPUT_BASE + (input.script ? input.script.length : + input.witnessUtxo ? TX_INPUT_SEGWIT : + input.isTaproot ? TX_INPUT_TAPROOT : TX_INPUT_PUBKEYHASH) } function outputBytes (output) { - return TX_OUTPUT_BASE + (output.script ? output.script.length : TX_OUTPUT_PUBKEYHASH) + return TX_OUTPUT_BASE + (output.script ? output.script.length : + output.address?.startsWith('bc1') || output.address?.startsWith('tb1') ? + output.address?.length === 42 ? TX_OUTPUT_SEGWIT : TX_OUTPUT_SEGWIT_SCRIPTHASH : + output.address?.startsWith('3') || output.address?.startsWith('2') ? + TX_OUTPUT_SCRIPTHASH : TX_OUTPUT_PUBKEYHASH + ) } function dustThreshold (output, feeRate) { From 524b2750de591e63d289b66e4a1041c25ab7b111 Mon Sep 17 00:00:00 2001 From: ChrisCho-H Date: Fri, 18 Aug 2023 18:39:51 +0900 Subject: [PATCH 2/5] feat: add script and isTaproot optional field --- index.d.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index 4a3a5f3..873a0ff 100644 --- a/index.d.ts +++ b/index.d.ts @@ -7,9 +7,12 @@ export interface UTXO { script: Buffer, value: number } + script?: Buffer, + isTaproot?: boolean } export interface Target { - address: string, + address?: string, + script?: Buffer, value?: number } export interface SelectedUTXO { From d85ad87409873bf98425bf1ee702b29069686a87 Mon Sep 17 00:00:00 2001 From: ChrisCho-H Date: Tue, 22 Aug 2023 14:54:10 +0900 Subject: [PATCH 3/5] fix: round up taproot input byte and check first --- utils.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/utils.js b/utils.js index 844ed78..5e497ab 100644 --- a/utils.js +++ b/utils.js @@ -3,7 +3,7 @@ var TX_EMPTY_SIZE = 4 + 1 + 1 + 4 var TX_INPUT_BASE = 32 + 4 + 1 + 4 var TX_INPUT_PUBKEYHASH = 107 var TX_INPUT_SEGWIT = 27 -var TX_INPUT_TAPROOT = 16.5 +var TX_INPUT_TAPROOT = 17 // round up 16.5 bytes var TX_OUTPUT_BASE = 8 + 1 var TX_OUTPUT_PUBKEYHASH = 25 var TX_OUTPUT_SCRIPTHASH = 23 @@ -11,18 +11,19 @@ var TX_OUTPUT_SEGWIT = 22 var TX_OUTPUT_SEGWIT_SCRIPTHASH = 34 function inputBytes (input) { - return TX_INPUT_BASE + (input.script ? input.script.length : - input.witnessUtxo ? TX_INPUT_SEGWIT : - input.isTaproot ? TX_INPUT_TAPROOT : TX_INPUT_PUBKEYHASH) + return TX_INPUT_BASE + (input.script ? input.script.length + : input.isTaproot ? TX_INPUT_TAPROOT + : input.witnessUtxo ? TX_INPUT_SEGWIT + : TX_INPUT_PUBKEYHASH) } function outputBytes (output) { - return TX_OUTPUT_BASE + (output.script ? output.script.length : - output.address?.startsWith('bc1') || output.address?.startsWith('tb1') ? - output.address?.length === 42 ? TX_OUTPUT_SEGWIT : TX_OUTPUT_SEGWIT_SCRIPTHASH : - output.address?.startsWith('3') || output.address?.startsWith('2') ? - TX_OUTPUT_SCRIPTHASH : TX_OUTPUT_PUBKEYHASH - ) + return TX_OUTPUT_BASE + (output.script ? output.script.length + : output.address?.startsWith('bc1') || output.address?.startsWith('tb1') + ? output.address?.length === 42 ? TX_OUTPUT_SEGWIT : TX_OUTPUT_SEGWIT_SCRIPTHASH + : output.address?.startsWith('3') || output.address?.startsWith('2') + ? TX_OUTPUT_SCRIPTHASH : TX_OUTPUT_PUBKEYHASH + ) } function dustThreshold (output, feeRate) { From 4039f9fe7f3a75bd9e9c5bc2e88cc9d7012cba58 Mon Sep 17 00:00:00 2001 From: ChrisCho-H Date: Tue, 22 Aug 2023 14:55:50 +0900 Subject: [PATCH 4/5] test: add test data for various input/output types --- test/fixtures/index.json | 231 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) diff --git a/test/fixtures/index.json b/test/fixtures/index.json index 9ebcdbf..968d400 100644 --- a/test/fixtures/index.json +++ b/test/fixtures/index.json @@ -804,6 +804,237 @@ 10000 ], "expected": {} + }, + { + "description": "segwit mainnet input and output", + "feeRate": 10, + "inputs": [{ + "value": 103430, + "witnessUtxo": { + "script": "script public key", + "value": 103430 + } + }], + "outputs": [ + { + "value": 100000, + "address": "bc1q34aq5drpuwy3wgl9lhup9892qp6svr8ldzyy7c" + } + ], + "expected": { + "inputs": [ + { + "i": 0, + "value": 103430, + "witnessUtxo": { + "script": "script public key", + "value": 103430 + } + } + ], + "outputs": [ + { + "value": 100000, + "address": "bc1q34aq5drpuwy3wgl9lhup9892qp6svr8ldzyy7c" + }, + { + "value": 2000 + } + ], + "fee": 1430 + } + }, + { + "description": "segwit testnet input and output", + "feeRate": 10, + "inputs": [{ + "value": 103430, + "witnessUtxo": { + "script": "script public key", + "value": 103430 + } + }], + "outputs": [ + { + "value": 100000, + "address": "tb1q34aq5drpuwy3wgl9lhup9892qp6svr8ldzyy7c" + } + ], + "expected": { + "inputs": [ + { + "i": 0, + "value": 103430, + "witnessUtxo": { + "script": "script public key", + "value": 103430 + } + } + ], + "outputs": [ + { + "value": 100000, + "address": "tb1q34aq5drpuwy3wgl9lhup9892qp6svr8ldzyy7c" + }, + { + "value": 2000 + } + ], + "fee": 1430 + } + }, + { + "description": "p2sh mainnet output", + "feeRate": 10, + "inputs": [ + 105000 + ], + "outputs": [ + { + "value": 100000, + "address": "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy" + } + ], + "expected": { + "inputs": [ + { + "i": 0, + "value": 105000 + } + ], + "outputs": [ + { + "value": 100000, + "address": "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy" + }, + { + "value": 2760 + } + ], + "fee": 2240 + } + }, + { + "description": "p2sh testnet output", + "feeRate": 10, + "inputs": [ + 105000 + ], + "outputs": [ + { + "value": 100000, + "address": "2J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy" + } + ], + "expected": { + "inputs": [ + { + "i": 0, + "value": 105000 + } + ], + "outputs": [ + { + "value": 100000, + "address": "2J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy" + }, + { + "value": 2760 + } + ], + "fee": 2240 + } + }, + { + "description": "p2wsh or taproot mainnet output", + "feeRate": 10, + "inputs": [ + 104010 + ], + "outputs": [ + { + "value": 100000, + "address": "bc1qeklep85ntjz4605drds6aww9u0qr46qzrv5xswd35uhjuj8ahfcqgf6hak" + } + ], + "expected": { + "inputs": [ + { + "i": 0, + "value": 104010 + } + ], + "outputs": [ + { + "value": 100000, + "address": "bc1qeklep85ntjz4605drds6aww9u0qr46qzrv5xswd35uhjuj8ahfcqgf6hak" + }, + { + "value": 1660 + } + ], + "fee": 2350 + } + }, + { + "description": "p2wsh or taproot testnet output", + "feeRate": 10, + "inputs": [ + 104010 + ], + "outputs": [ + { + "value": 100000, + "address": "tb1qeklep85ntjz4605drds6aww9u0qr46qzrv5xswd35uhjuj8ahfcqgf6hak" + } + ], + "expected": { + "inputs": [ + { + "i": 0, + "value": 104010 + } + ], + "outputs": [ + { + "value": 100000, + "address": "tb1qeklep85ntjz4605drds6aww9u0qr46qzrv5xswd35uhjuj8ahfcqgf6hak" + }, + { + "value": 1660 + } + ], + "fee": 2350 + } + }, + { + "description": "taproot input", + "feeRate": 10, + "inputs": [{ + "value": 103360, + "isTaproot": true + }], + "outputs": [ + 100000 + ], + "expected": { + "inputs": [ + { + "i": 0, + "value": 103360, + "isTaproot": true + } + ], + "outputs": [ + { + "value": 100000 + }, + { + "value": 2000 + } + ], + "fee": 1360 + } } ] From 369093c6f20b9592056f71a35743e57f698ec683 Mon Sep 17 00:00:00 2001 From: ChrisCho-H Date: Thu, 31 Aug 2023 15:32:24 +0900 Subject: [PATCH 5/5] feat: support nested p2sh and p2wsh --- index.d.ts | 3 +- test/fixtures/accumulative.json | 12 ++--- test/fixtures/index.json | 86 +++++++++++++++++++++++++++++++-- utils.js | 9 ++-- 4 files changed, 95 insertions(+), 15 deletions(-) diff --git a/index.d.ts b/index.d.ts index 873a0ff..58eabe9 100644 --- a/index.d.ts +++ b/index.d.ts @@ -7,7 +7,8 @@ export interface UTXO { script: Buffer, value: number } - script?: Buffer, + redeemScript?: Buffer, + witnessScript?: Buffer, isTaproot?: boolean } export interface Target { diff --git a/test/fixtures/accumulative.json b/test/fixtures/accumulative.json index 23505d5..73e761f 100644 --- a/test/fixtures/accumulative.json +++ b/test/fixtures/accumulative.json @@ -163,7 +163,7 @@ "feeRate": 5, "inputs": [ { - "script": { + "redeemScript": { "length": 1000 }, "value": 3000 @@ -220,7 +220,7 @@ "feeRate": 5, "inputs": [ { - "script": { + "redeemScript": { "length": 500 }, "value": 3000 @@ -239,7 +239,7 @@ "inputs": [ { "i": 0, - "script": { + "redeemScript": { "length": 500 }, "value": 3000 @@ -272,7 +272,7 @@ "value": 3000 }, { - "script": { + "redeemScript": { "length": 400 }, "value": 3000 @@ -672,7 +672,7 @@ "inputs": [ 20000, { - "script": { + "redeemScript": { "length": 300 }, "value": 10000 @@ -691,7 +691,7 @@ }, { "i": 1, - "script": { + "redeemScript": { "length": 300 }, "value": 10000 diff --git a/test/fixtures/index.json b/test/fixtures/index.json index 968d400..3cff1dc 100644 --- a/test/fixtures/index.json +++ b/test/fixtures/index.json @@ -136,7 +136,7 @@ "feeRate": 5, "inputs": [ { - "script": { + "redeemScript": { "length": 1000 }, "value": 3000 @@ -175,7 +175,7 @@ "feeRate": 5, "inputs": [ { - "script": { + "redeemScript": { "length": 500 }, "value": 3000 @@ -220,7 +220,7 @@ "value": 3000 }, { - "script": { + "redeemScript": { "length": 400 }, "value": 3000 @@ -620,7 +620,7 @@ "inputs": [ 20000, { - "script": { + "redeemScript": { "length": 300 }, "value": 10000 @@ -1035,6 +1035,84 @@ ], "fee": 1360 } + }, + { + "description": "1 output, passes, skipped detrimental input", + "feeRate": 5, + "inputs": [ + { + "redeemScript": { + "length": 1000 + }, + "value": 3000 + }, + { + "value": 3000 + }, + { + "value": 3000 + } + ], + "outputs": [ + 4000 + ], + "expected": { + "fee": 2000, + "inputs": [ + { + "i": 1, + "value": 3000 + }, + { + "i": 2, + "value": 3000 + } + ], + "outputs": [ + { + "value": 4000 + } + ] + } + }, + { + "description": "1 output, passes, skipped detrimental input", + "feeRate": 5, + "inputs": [ + { + "witnessScript": { + "length": 4000 + }, + "value": 3000 + }, + { + "value": 3000 + }, + { + "value": 3000 + } + ], + "outputs": [ + 4000 + ], + "expected": { + "fee": 2000, + "inputs": [ + { + "i": 1, + "value": 3000 + }, + { + "i": 2, + "value": 3000 + } + ], + "outputs": [ + { + "value": 4000 + } + ] + } } ] diff --git a/utils.js b/utils.js index 5e497ab..af55a19 100644 --- a/utils.js +++ b/utils.js @@ -11,10 +11,11 @@ var TX_OUTPUT_SEGWIT = 22 var TX_OUTPUT_SEGWIT_SCRIPTHASH = 34 function inputBytes (input) { - return TX_INPUT_BASE + (input.script ? input.script.length - : input.isTaproot ? TX_INPUT_TAPROOT - : input.witnessUtxo ? TX_INPUT_SEGWIT - : TX_INPUT_PUBKEYHASH) + return TX_INPUT_BASE + (input.redeemScript ? input.redeemScript.length : 0) + + (input.witnessScript ? parseInt(input.witnessScript.length / 4) + : input.isTaproot ? TX_INPUT_TAPROOT + : input.witnessUtxo ? TX_INPUT_SEGWIT + : !input.redeemScript ? TX_INPUT_PUBKEYHASH : 0) } function outputBytes (output) {