From f34890f4435f58a60adc36fdc4fb7b84decc18aa Mon Sep 17 00:00:00 2001 From: Shadouts Date: Sun, 24 Nov 2024 12:06:04 -0900 Subject: [PATCH] feat: PsbtV2.toV0() --- .../caravan-psbt/src/psbtv2/psbtv2.test.ts | 36 ++++++ packages/caravan-psbt/src/psbtv2/psbtv2.ts | 55 ++++++-- .../caravan-psbt/src/psbtv2/psbtv2maps.ts | 118 +++++++++++++++++- packages/caravan-psbt/src/psbtv2/types.ts | 7 ++ 4 files changed, 205 insertions(+), 11 deletions(-) diff --git a/packages/caravan-psbt/src/psbtv2/psbtv2.test.ts b/packages/caravan-psbt/src/psbtv2/psbtv2.test.ts index 0f98c358..7fade1ae 100644 --- a/packages/caravan-psbt/src/psbtv2/psbtv2.test.ts +++ b/packages/caravan-psbt/src/psbtv2/psbtv2.test.ts @@ -612,6 +612,8 @@ const BIP_174_VECTORS_VALID_PSBT = [ hex: "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab300000000000000", base64: "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA", + unsignedRawTx: + "0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300", inputs: 1, outputs: 2, }, @@ -623,6 +625,8 @@ const BIP_174_VECTORS_VALID_PSBT = [ hex: "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac000000000001076a47304402204759661797c01b036b25928948686218347d89864b719e1f7fcf57d1e511658702205309eabf56aa4d8891ffd111fdf1336f3a29da866d7f8486d75546ceedaf93190121035cdc61fc7ba971c0b501a646a2a83b102cb43881217ca682dc86e2d73fa882920001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb82308000000", base64: "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA", + unsignedRawTx: + "0200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000", inputs: 2, outputs: 2, }, @@ -634,6 +638,8 @@ const BIP_174_VECTORS_VALID_PSBT = [ hex: "70736274ff0100750200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000100fda5010100000000010289a3c71eab4d20e0371bbba4cc698fa295c9463afa2e397f8533ccb62f9567e50100000017160014be18d152a9b012039daf3da7de4f53349eecb985ffffffff86f8aa43a71dff1448893a530a7237ef6b4608bbb2dd2d0171e63aec6a4890b40100000017160014fe3e9ef1a745e974d902c4355943abcb34bd5353ffffffff0200c2eb0b000000001976a91485cff1097fd9e008bb34af709c62197b38978a4888ac72fef84e2c00000017a914339725ba21efd62ac753a9bcd067d6c7a6a39d05870247304402202712be22e0270f394f568311dc7ca9a68970b8025fdd3b240229f07f8a5f3a240220018b38d7dcd314e734c9276bd6fb40f673325bc4baa144c800d2f2f02db2765c012103d2e15674941bad4a996372cb87e1856d3652606d98562fe39c5e9e7e413f210502483045022100d12b852d85dcd961d2f5f4ab660654df6eedcc794c0c33ce5cc309ffb5fce58d022067338a8e0e1725c197fb1a88af59f51e44e4255b20167c8684031c05d1f2592a01210223b72beef0965d10be0778efecd61fcac6f79a4ea169393380734464f84f2ab30000000001030401000000000000", base64: "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAQMEAQAAAAAAAA==", + unsignedRawTx: + "0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf60000000000feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300", inputs: 1, outputs: 2, }, @@ -646,6 +652,8 @@ const BIP_174_VECTORS_VALID_PSBT = [ hex: "70736274ff0100a00200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000000100df0200000001268171371edff285e937adeea4b37b78000c0566cbb3ad64641713ca42171bf6000000006a473044022070b2245123e6bf474d60c5b50c043d4c691a5d2435f09a34a7662a9dc251790a022001329ca9dacf280bdf30740ec0390422422c81cb45839457aeb76fc12edd95b3012102657d118d3357b8e0f4c2cd46db7b39f6d9c38d9a70abcb9b2de5dc8dbfe4ce31feffffff02d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e13000001012000e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787010416001485d13537f2e265405a34dbafa9e3dda01fb8230800220202ead596687ca806043edc3de116cdf29d5e9257c196cd055cf698c8d02bf24e9910b4a6ba670000008000000080020000800022020394f62be9df19952c5587768aeb7698061ad2c4a25c894f47d8c162b4d7213d0510b4a6ba6700000080010000800200008000", base64: "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEA3wIAAAABJoFxNx7f8oXpN63upLN7eAAMBWbLs61kZBcTykIXG/YAAAAAakcwRAIgcLIkUSPmv0dNYMW1DAQ9TGkaXSQ18Jo0p2YqncJReQoCIAEynKnazygL3zB0DsA5BCJCLIHLRYOUV663b8Eu3ZWzASECZX0RjTNXuOD0ws1G23s59tnDjZpwq8ubLeXcjb/kzjH+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=", + unsignedRawTx: + "0200000002ab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40000000000feffffffab0949a08c5af7c49b8212f417e2f15ab3f5c33dcf153821a8139f877a5b7be40100000000feffffff02603bea0b000000001976a914768a40bbd740cbe81d988e71de2a4d5c71396b1d88ac8e240000000000001976a9146f4620b553fa095e721b9ee0efe9fa039cca459788ac00000000", inputs: 2, outputs: 2, }, @@ -657,6 +665,8 @@ const BIP_174_VECTORS_VALID_PSBT = [ hex: "70736274ff0100550200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac0000000000010120955eea0b0000000017a9146345200f68d189e1adc0df1c4d16ea8f14c0dbeb87220203b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4646304302200424b58effaaa694e1559ea5c93bbfd4a89064224055cdf070b6771469442d07021f5c8eb0fea6516d60b8acb33ad64ede60e8785bfb3aa94b99bdf86151db9a9a010104220020771fd18ad459666dd49f3d564e3dbc42f4c84774e360ada16816a8ed488d5681010547522103b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd462103de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd52ae220603b1341ccba7683b6af4f1238cd6e97e7167d569fac47f1e48d47541844355bd4610b4a6ba67000000800000008004000080220603de55d1e1dac805e3f8a58c1fbf9b94c02f3dbaafe127fefca4995f26f82083bd10b4a6ba670000008000000080050000800000", base64: "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEBIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEBBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SriIGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GELSmumcAAACAAAAAgAQAAIAiBgPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvRC0prpnAAAAgAAAAIAFAACAAAA=", + unsignedRawTx: + "0200000001279a2323a5dfb51fc45f220fa58b0fc13e1e3342792a85d7e36cd6333b5cbc390000000000ffffffff01a05aea0b000000001976a914ffe9c0061097cc3b636f2cb0460fa4fc427d2b4588ac00000000", inputs: 1, outputs: 1, partialSigs: [ @@ -678,6 +688,8 @@ const BIP_174_VECTORS_VALID_PSBT = [ hex: "70736274ff01005202000000019dfc6628c26c5899fe1bd3dc338665bfd55d7ada10f6220973df2d386dec12760100000000ffffffff01f03dcd1d000000001600147b3a00bfdc14d27795c2b74901d09da6ef133579000000004f01043587cf02da3fd0088000000097048b1ad0445b1ec8275517727c87b4e4ebc18a203ffa0f94c01566bd38e9000351b743887ee1d40dc32a6043724f2d6459b3b5a4d73daec8fbae0472f3bc43e20cd90c6a4fae000080000000804f01043587cf02da3fd00880000001b90452427139cd78c2cff2444be353cd58605e3e513285e528b407fae3f6173503d30a5e97c8adbc557dac2ad9a7e39c1722ebac69e668b6f2667cc1d671c83cab0cd90c6a4fae000080010000800001012b0065cd1d000000002200202c5486126c4978079a814e13715d65f36459e4d6ccaded266d0508645bafa6320105475221029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c88712103372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b52ae2206029da12cdb5b235692b91536afefe5c91c3ab9473d8e43b533836ab456299c887110d90c6a4fae0000800000008000000000220603372b34234ed7cf9c1fea5d05d441557927be9542b162eb02e1ab2ce80224c00b10d90c6a4fae0000800100008000000000002202039eff1f547a1d5f92dfa2ba7af6ac971a4bd03ba4a734b03156a256b8ad3a1ef910ede45cc500000080000000800100008000", base64: "cHNidP8BAFICAAAAAZ38ZijCbFiZ/hvT3DOGZb/VXXraEPYiCXPfLTht7BJ2AQAAAAD/////AfA9zR0AAAAAFgAUezoAv9wU0neVwrdJAdCdpu8TNXkAAAAATwEENYfPAto/0AiAAAAAlwSLGtBEWx7IJ1UXcnyHtOTrwYogP/oPlMAVZr046QADUbdDiH7h1A3DKmBDck8tZFmztaTXPa7I+64EcvO8Q+IM2QxqT64AAIAAAACATwEENYfPAto/0AiAAAABuQRSQnE5zXjCz/JES+NTzVhgXj5RMoXlKLQH+uP2FzUD0wpel8itvFV9rCrZp+OcFyLrrGnmaLbyZnzB1nHIPKsM2QxqT64AAIABAACAAAEBKwBlzR0AAAAAIgAgLFSGEmxJeAeagU4TcV1l82RZ5NbMre0mbQUIZFuvpjIBBUdSIQKdoSzbWyNWkrkVNq/v5ckcOrlHPY5DtTODarRWKZyIcSEDNys0I07Xz5wf6l0F1EFVeSe+lUKxYusC4ass6AIkwAtSriIGAp2hLNtbI1aSuRU2r+/lyRw6uUc9jkO1M4NqtFYpnIhxENkMak+uAACAAAAAgAAAAAAiBgM3KzQjTtfPnB/qXQXUQVV5J76VQrFi6wLhqyzoAiTACxDZDGpPrgAAgAEAAIAAAAAAACICA57/H1R6HV+S36K6evaslxpL0DukpzSwMVaiVritOh75EO3kXMUAAACAAAAAgAEAAIAA", + unsignedRawTx: + "02000000019dfc6628c26c5899fe1bd3dc338665bfd55d7ada10f6220973df2d386dec12760100000000ffffffff01f03dcd1d000000001600147b3a00bfdc14d27795c2b74901d09da6ef13357900000000", inputs: 1, outputs: 1, }, @@ -688,6 +700,8 @@ const BIP_174_VECTORS_VALID_PSBT = [ hex: "70736274ff01003f0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000000af00102030405060708090f0102030405060708090a0b0c0d0e0f0000", base64: "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACvABAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA=", + unsignedRawTx: + "0200000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0000000000ffffffff010000000000000000036a010000000000", inputs: 1, outputs: 1, }, @@ -698,6 +712,8 @@ const BIP_174_VECTORS_VALID_PSBT = [ hex: "70736274ff01009d0100000002710ea76ab45c5cb6438e607e59cc037626981805ae9e0dfd9089012abb0be5350100000000ffffffff190994d6a8b3c8c82ccbcfb2fba4106aa06639b872a8d447465c0d42588d6d670000000000ffffffff0200e1f505000000001976a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac605af405000000001600141188ef8e4ce0449eaac8fb141cbf5a1176e6a088000000004f010488b21e039e530cac800000003dbc8a5c9769f031b17e77fea1518603221a18fd18f2b9a54c6c8c1ac75cbc3502f230584b155d1c7f1cd45120a653c48d650b431b67c5b2c13f27d7142037c1691027569c503100008000000080000000800001011f00e1f5050000000016001433b982f91b28f160c920b4ab95e58ce50dda3a4a220203309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c47304402202d704ced830c56a909344bd742b6852dccd103e963bae92d38e75254d2bb424502202d86c437195df46c0ceda084f2a291c3da2d64070f76bf9b90b195e7ef28f77201220603309680f33c7de38ea6a47cd4ecd66f1f5a49747c6ffb8808ed09039243e3ad5c1827569c5031000080000000800000008000000000010000000001011f00e1f50500000000160014388fb944307eb77ef45197d0b0b245e079f011de220202c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b11047304402204cb1fb5f869c942e0e26100576125439179ae88dca8a9dc3ba08f7953988faa60220521f49ca791c27d70e273c9b14616985909361e25be274ea200d7e08827e514d01220602c777161f73d0b7c72b9ee7bde650293d13f095bc7656ad1f525da5fd2e10b1101827569c5031000080000000800000008000000000000000000000220202d20ca502ee289686d21815bd43a80637b0698e1fbcdbe4caed445f6c1a0a90ef1827569c50310000800000008000000080000000000400000000", base64: "cHNidP8BAJ0BAAAAAnEOp2q0XFy2Q45gflnMA3YmmBgFrp4N/ZCJASq7C+U1AQAAAAD/////GQmU1qizyMgsy8+y+6QQaqBmObhyqNRHRlwNQliNbWcAAAAAAP////8CAOH1BQAAAAAZdqkUtrwsDuVlWoQ9ea/t0MzD991kNAmIrGBa9AUAAAAAFgAUEYjvjkzgRJ6qyPsUHL9aEXbmoIgAAAAATwEEiLIeA55TDKyAAAAAPbyKXJdp8DGxfnf+oVGGAyIaGP0Y8rmlTGyMGsdcvDUC8jBYSxVdHH8c1FEgplPEjWULQxtnxbLBPyfXFCA3wWkQJ1acUDEAAIAAAACAAAAAgAABAR8A4fUFAAAAABYAFDO5gvkbKPFgySC0q5XljOUN2jpKIgIDMJaA8zx9446mpHzU7NZvH1pJdHxv+4gI7QkDkkPjrVxHMEQCIC1wTO2DDFapCTRL10K2hS3M0QPpY7rpLTjnUlTSu0JFAiAthsQ3GV30bAztoITyopHD2i1kBw92v5uQsZXn7yj3cgEiBgMwloDzPH3jjqakfNTs1m8fWkl0fG/7iAjtCQOSQ+OtXBgnVpxQMQAAgAAAAIAAAACAAAAAAAEAAAAAAQEfAOH1BQAAAAAWABQ4j7lEMH63fvRRl9CwskXgefAR3iICAsd3Fh9z0LfHK57nveZQKT0T8JW8dlatH1Jdpf0uELEQRzBEAiBMsftfhpyULg4mEAV2ElQ5F5rojcqKncO6CPeVOYj6pgIgUh9JynkcJ9cOJzybFGFphZCTYeJb4nTqIA1+CIJ+UU0BIgYCx3cWH3PQt8crnue95lApPRPwlbx2Vq0fUl2l/S4QsRAYJ1acUDEAAIAAAACAAAAAgAAAAAAAAAAAAAAiAgLSDKUC7iiWhtIYFb1DqAY3sGmOH7zb5MrtRF9sGgqQ7xgnVpxQMQAAgAAAAIAAAACAAAAAAAQAAAAA", + unsignedRawTx: + "0100000002710ea76ab45c5cb6438e607e59cc037626981805ae9e0dfd9089012abb0be5350100000000ffffffff190994d6a8b3c8c82ccbcfb2fba4106aa06639b872a8d447465c0d42588d6d670000000000ffffffff0200e1f505000000001976a914b6bc2c0ee5655a843d79afedd0ccc3f7dd64340988ac605af405000000001600141188ef8e4ce0449eaac8fb141cbf5a1176e6a08800000000", inputs: 2, outputs: 2, }, @@ -707,6 +723,7 @@ const BIP_174_VECTORS_VALID_PSBT = [ case: "Case: PSBT with global unsigned tx that has 0 inputs and 0 outputs", hex: "70736274ff01000a0000000000000000000000", base64: "cHNidP8BAAoAAAAAAAAAAAAAAA==", + unsignedRawTx: "00000000000000000000", inputs: 0, outputs: 0, }, @@ -717,6 +734,8 @@ const BIP_174_VECTORS_VALID_PSBT = [ hex: "70736274ff01004c020000000002d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300000000", base64: "cHNidP8BAEwCAAAAAALT3/UFAAAAABl2qRTQxZkDxbrChodg6Q/VIaRmWqdlIIisAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4ezLhMAAAAA", + unsignedRawTx: + "020000000002d3dff505000000001976a914d0c59903c5bac2868760e90fd521a4665aa7652088ac00e1f5050000000017a9143545e6e33b832c47050f24d3eeb93c9c03948bc787b32e1300", inputs: 0, outputs: 2, }, @@ -1665,3 +1684,20 @@ describe("PsbtV2.setInputSequence", () => { expect(emptyPsbt.isRBFSignaled).toBe(false); }); }); + +describe("PsbtV2.toV0", () => { + // A PSBT is still valid if it includes unrecognized key-value pairs, so we + // don't need to check that the incompatible keys were removed, but we do need + // to check that the required PSBT_GLOBAL_UNSIGNED_TX was properly created. + test.each(BIP_174_VECTORS_VALID_PSBT)( + "Properly constructs PSBT_GLOBAL_UNSIGNED_TX. $case", + (vect) => { + const psbtv2 = PsbtV2.FromV0(vect.base64, true); + const psbtv0 = psbtv2.toV0("hex"); + // At a minimum, the raw tx cannot start before string index 16 because + // the hex psbt must at least start with "70736274ff0100" and one byte for + // length. + expect(psbtv0.indexOf(vect.unsignedRawTx)).toBeGreaterThanOrEqual(16); + }, + ); +}); diff --git a/packages/caravan-psbt/src/psbtv2/psbtv2.ts b/packages/caravan-psbt/src/psbtv2/psbtv2.ts index ee7336b0..cb04bb38 100644 --- a/packages/caravan-psbt/src/psbtv2/psbtv2.ts +++ b/packages/caravan-psbt/src/psbtv2/psbtv2.ts @@ -16,7 +16,7 @@ import { getOptionalMappedBytesAsUInt, parseDerivationPathNodesToBytes, } from "./functions"; -import { PsbtV2Maps } from "./psbtv2maps"; +import { PsbtConversionMaps, PsbtV2Maps } from "./psbtv2maps"; import { bufferize } from "../functions"; /** * The PsbtV2 class is intended to represent an easily modifiable and @@ -915,6 +915,7 @@ export class PsbtV2 extends PsbtV2Maps { redeemScript, witnessScript, bip32Derivation, + sighashType, }: { previousTxId: Buffer | string; outputIndex: number; @@ -928,6 +929,7 @@ export class PsbtV2 extends PsbtV2Maps { masterFingerprint: Buffer; path: string; }[]; + sighashType?: SighashType; }) { // TODO: This must accept and add appropriate locktime fields. There is // significant validation concerning this step detailed in the BIP0370 @@ -990,6 +992,10 @@ export class PsbtV2 extends PsbtV2Maps { map.set(key, bw.render()); } } + if (sighashType !== undefined) { + bw.writeU32(sighashType); + map.set(KeyType.PSBT_IN_SIGHASH_TYPE, bw.render()); + } this.PSBT_GLOBAL_INPUT_COUNT = this.inputMaps.push(map); } @@ -1307,8 +1313,9 @@ export class PsbtV2 extends PsbtV2Maps { * Attempts to return a PsbtV2 by converting from a PsbtV0 string or Buffer. * * This method first starts with a fresh PsbtV2 having just been created. It - * then takes the PsbtV2 through its operator saga through the Signer role. In - * this sense validation for each operator role will be performed. + * then takes the PsbtV2 through its operator saga and through the Input + * Finalizer role. In this sense, validation for each operator role will be + * performed as the Psbt saga is replayed. */ static FromV0(psbt: string | Buffer, allowTxnVersion1 = false): PsbtV2 { const psbtv0Buf = bufferize(psbt); @@ -1326,6 +1333,8 @@ export class PsbtV2 extends PsbtV2Maps { .readInt32LE(0); } + psbtv2.PSBT_GLOBAL_FALLBACK_LOCKTIME = psbtv0.locktime; + // Constructor Role for (const globalXpub of psbtv0GlobalMap.globalXpub ?? []) { psbtv2.addGlobalXpub( @@ -1354,6 +1363,7 @@ export class PsbtV2 extends PsbtV2Maps { redeemScript: input.redeemScript, witnessScript: input.witnessScript, bip32Derivation: input.bip32Derivation, + sighashType: input.sighashType, }); } @@ -1374,16 +1384,47 @@ export class PsbtV2 extends PsbtV2Maps { } // Signer Role - - // Finally, add partialSigs to inputs. This has to be performed last since - // it may change PSBT_GLOBAL_TX_MODIFIABLE preventing inputs or outputs from - // being added. + // This may change PSBT_GLOBAL_TX_MODIFIABLE preventing inputs or outputs + // from being added. for (const [index, input] of psbtv0.data.inputs.entries()) { for (const sig of input.partialSig || []) { psbtv2.addPartialSig(index, sig.pubkey, sig.signature); } } + // Input Finalizer + // TODO: Add input finalizer method which removes other input fields. The + // Input Finalizer role is supposed to remove the other script and partial + // sig fields from the input after the input is finalized. This is maybe + // safe as-is for now since it is building from a v0 conversion. + for (const [index, input] of psbtv0.data.inputs.entries()) { + if (input.finalScriptSig) { + psbtv2.inputMaps[index].set( + KeyType.PSBT_IN_FINAL_SCRIPTSIG, + input.finalScriptSig, + ); + } + if (input.finalScriptWitness) { + psbtv2.inputMaps[index].set( + KeyType.PSBT_IN_FINAL_SCRIPTWITNESS, + input.finalScriptWitness, + ); + } + } + return psbtv2; } + + /** + * Outputs a serialized PSBTv0 from a best-attempt conversion of the fields in + * this PSBTv2. Accepts optional desired format as a string (default base64). + */ + public toV0(format?: "base64" | "hex") { + const converterMap = new PsbtConversionMaps(); + // Copy the values from this PsbtV2 into the converter map. + this.copy(converterMap); + // Creates the unsigned tx and adds it to the map and then removes v2 keys. + converterMap.convertToV0(); + return converterMap.serialize(format); + } } diff --git a/packages/caravan-psbt/src/psbtv2/psbtv2maps.ts b/packages/caravan-psbt/src/psbtv2/psbtv2maps.ts index 0337a158..a404fdc0 100644 --- a/packages/caravan-psbt/src/psbtv2/psbtv2maps.ts +++ b/packages/caravan-psbt/src/psbtv2/psbtv2maps.ts @@ -1,9 +1,9 @@ import { BufferReader, BufferWriter } from "bufio"; +import { Transaction } from "bitcoinjs-lib-v6"; import { readAndSetKeyPairs, serializeMap } from "./functions"; -import { Key, KeyType, Value } from "./types"; +import { Key, KeyType, V0KeyTypes, Value } from "./types"; import { PSBT_MAGIC_BYTES } from "../constants"; -import { PsbtV2 } from "./psbtv2"; import { bufferize } from "../functions"; /** @@ -81,7 +81,7 @@ export abstract class PsbtV2Maps { } /** - * Copies the maps in this PsbtV2 object to another PsbtV2 object. + * Copies the maps in this PsbtV2Maps object to another PsbtV2Maps object. * * NOTE: This copy method is made available to achieve parity with the PSBT * api required by `ledger-bitcoin` for creating merklized PSBTs. HOWEVER, it @@ -89,7 +89,7 @@ export abstract class PsbtV2Maps { * validation defined in the constructor, so it could create a psbtv2 in an * invalid psbt state. PsbtV2.serialize is preferable whenever possible. */ - public copy(to: PsbtV2) { + public copy(to: PsbtV2Maps) { this.copyMap(this.globalMap, to.globalMap); this.copyMaps(this.inputMaps, to.inputMaps); this.copyMaps(this.outputMaps, to.outputMaps); @@ -110,3 +110,113 @@ export abstract class PsbtV2Maps { from.forEach((v, k) => to.set(k, Buffer.from(v))); } } + +/** + * A PsbtV2Maps class that allows for the addition of and removal of map fields + * specifically for converting between PSBT versions. + */ +export class PsbtConversionMaps extends PsbtV2Maps { + /** + * Builds the unsigned transaction that is required on global map key 0x00 for + * a PSBTv0. Relies on bitcoinjs-lib to construct the transaction. + */ + private buildUnsignedTx() { + const tx = new Transaction(); + + tx.version = + this.globalMap.get(KeyType.PSBT_GLOBAL_TX_VERSION)?.readUInt8() || 0; + tx.locktime = + this.globalMap + .get(KeyType.PSBT_GLOBAL_FALLBACK_LOCKTIME) + ?.readUInt32LE() || 0; + + const numInputs = + this.globalMap.get(KeyType.PSBT_GLOBAL_INPUT_COUNT)?.readUInt8() || 0; + const numOutputs = + this.globalMap.get(KeyType.PSBT_GLOBAL_OUTPUT_COUNT)?.readUInt8() || 0; + + for (let i = 0; i < numInputs; i++) { + if (!this.inputMaps[i].has(KeyType.PSBT_IN_PREVIOUS_TXID)) { + console.warn(`Input ${i} is missing previous txid. Skipping.`); + continue; + } + tx.addInput( + this.inputMaps[i].get(KeyType.PSBT_IN_PREVIOUS_TXID) as Buffer, + this.inputMaps[i].get(KeyType.PSBT_IN_OUTPUT_INDEX)?.readUint32LE() || + 0, + this.inputMaps[i].get(KeyType.PSBT_IN_SEQUENCE)?.readUint32LE(), + ); + } + + for (let i = 0; i < numOutputs; i++) { + if ( + !this.outputMaps[i].has(KeyType.PSBT_OUT_SCRIPT) || + !this.outputMaps[i].has(KeyType.PSBT_OUT_AMOUNT) + ) { + console.warn( + `Output ${i} is missing previous out script or amount. Skipping.`, + ); + continue; + } + const bigintAmount = ( + this.outputMaps[i].get(KeyType.PSBT_OUT_AMOUNT) as Buffer + ).readBigInt64LE(); + const numberAmount = parseInt(bigintAmount.toString()); + tx.addOutput( + this.outputMaps[i].get(KeyType.PSBT_OUT_SCRIPT) as Buffer, + numberAmount, + ); + } + + console.log(tx.toHex()); + + return tx.toBuffer(); + } + + /** + * Warns and then deletes map values. Intended for use when converting to a + * PsbtV0. + */ + private v0delete(map: Map, key: KeyType) { + if (map.has(key)) { + const keyName = Object.keys(KeyType)[Object.values(KeyType).indexOf(key)]; + console.warn( + `Key ${keyName} key is not supported on PSBTv0 and will be omitted.`, + ); + } + + map.delete(key); + } + + /** + * Constructs an unsigned txn to be added to the PSBT_GLOBAL_UNSIGNED_TX key + * which is required on PsbtV0. Then removes all the fields which a PsbtV0 is + * incompatible with. + */ + public convertToV0 = () => { + this.globalMap.set( + V0KeyTypes.PSBT_GLOBAL_UNSIGNED_TX, + this.buildUnsignedTx(), + ); + + this.v0delete(this.globalMap, KeyType.PSBT_GLOBAL_TX_VERSION); + this.v0delete(this.globalMap, KeyType.PSBT_GLOBAL_FALLBACK_LOCKTIME); + this.v0delete(this.globalMap, KeyType.PSBT_GLOBAL_INPUT_COUNT); + this.v0delete(this.globalMap, KeyType.PSBT_GLOBAL_OUTPUT_COUNT); + this.v0delete(this.globalMap, KeyType.PSBT_GLOBAL_TX_MODIFIABLE); + this.v0delete(this.globalMap, KeyType.PSBT_GLOBAL_VERSION); + + for (const inputMap of this.inputMaps) { + this.v0delete(inputMap, KeyType.PSBT_IN_PREVIOUS_TXID); + this.v0delete(inputMap, KeyType.PSBT_IN_OUTPUT_INDEX); + this.v0delete(inputMap, KeyType.PSBT_IN_SEQUENCE); + this.v0delete(inputMap, KeyType.PSBT_IN_REQUIRED_TIME_LOCKTIME); + this.v0delete(inputMap, KeyType.PSBT_IN_REQUIRED_HEIGHT_LOCKTIME); + } + + for (const outputMap of this.outputMaps) { + this.v0delete(outputMap, KeyType.PSBT_OUT_AMOUNT); + this.v0delete(outputMap, KeyType.PSBT_OUT_SCRIPT); + } + }; +} diff --git a/packages/caravan-psbt/src/psbtv2/types.ts b/packages/caravan-psbt/src/psbtv2/types.ts index 46197022..da33e250 100644 --- a/packages/caravan-psbt/src/psbtv2/types.ts +++ b/packages/caravan-psbt/src/psbtv2/types.ts @@ -66,6 +66,13 @@ export enum KeyType { PSBT_OUT_PROPRIETARY = "fc", } +/** + * Keys which are required on a PsbtV0 but which are not compatible with PsbtV2. + */ +export enum V0KeyTypes { + PSBT_GLOBAL_UNSIGNED_TX = "00", +} + /** * Provided to friendly-format the `PSBT_GLOBAL_TX_MODIFIABLE` bitmask from * `PsbtV2.PSBT_GLOBAL_TX_MODIFIABLE` which returns