diff --git a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala index ff93fa82c6..7dc8ac7cc7 100644 --- a/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/psbt/PSBTUnitTest.scala @@ -7,19 +7,16 @@ import org.bitcoins.core.number.UInt32 import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.transaction._ import org.bitcoins.core.psbt.GlobalPSBTRecord.{UnsignedTransaction, Version} -import org.bitcoins.core.psbt.InputPSBTRecord.{ - NonWitnessOrUnknownUTXO, - WitnessUTXO -} +import org.bitcoins.core.psbt.InputPSBTRecord._ import org.bitcoins.core.psbt.PSBTGlobalKeyId.XPubKeyKeyId import org.bitcoins.core.script.constant._ import org.bitcoins.core.wallet.utxo.{ConditionalPath, InputInfo} -import org.bitcoins.crypto.{HashType, _} +import org.bitcoins.crypto._ import org.bitcoins.testkitcore.util.BitcoinSUnitTest import org.bitcoins.testkitcore.util.TransactionTestUtil._ import scodec.bits._ -import scala.util.{Failure, Success} +import scala.util._ class PSBTUnitTest extends BitcoinSUnitTest { @@ -549,7 +546,6 @@ class PSBTUnitTest extends BitcoinSUnitTest { } it must "correctly add a non-witness utxo when there is a witness v0 redeem script" in { - val witScript = P2PKHScriptPubKey(ECPublicKey.freshPublicKey) val witness = P2WSHWitnessV0(witScript) val redeemScript = P2WSHWitnessSPKV0(witScript) @@ -571,4 +567,79 @@ class PSBTUnitTest extends BitcoinSUnitTest { .map(_.transactionSpent) .contains(utxoTx)) } + + // -- BIP 371 tests -- + + it must "PSBT with one P2TR key only input with internal key and its derivation path" in { + val bytes = + hex"70736274ff010052020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff0148e6052a01000000160014768e1eeb4cf420866033f80aceff0f9720744969000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a07572116fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000011720fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232002202036b772a6db74d8753c98a827958de6c78ab3312109f37d3e0304484242ece73d818772b2da7540000800100008000000080000000000000000000" + assert(Try(PSBT.fromBytes(bytes)).isSuccess) + val psbt = PSBT.fromBytes(bytes) + assert(PSBT.fromBytes(psbt.bytes) == psbt) + } + + it must "PSBT with one P2TR key only input with internal key, its derivation path, and signature" in { + val bytes = + hex"70736274ff010052020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff0148e6052a01000000160014768e1eeb4cf420866033f80aceff0f9720744969000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757011340bb53ec917bad9d906af1ba87181c48b86ace5aae2b53605a725ca74625631476fc6f5baedaf4f2ee0f477f36f58f3970d5b8273b7e497b97af2e3f125c97af342116fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000011720fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232002202036b772a6db74d8753c98a827958de6c78ab3312109f37d3e0304484242ece73d818772b2da7540000800100008000000080000000000000000000" + assert(Try(PSBT.fromBytes(bytes)).isSuccess) + assert(PSBT.fromBytes(bytes).bytes == bytes) + + val psbt = PSBT.fromBytes(bytes) + + val finalizedT = psbt.finalizePSBT + assert(finalizedT.isSuccess) + // todo can't verify taproot yet +// val finalized = finalizedT.get +// assert(finalized.extractTransactionAndValidate.isSuccess) + } + + it must "PSBT with one P2TR key only output with internal key and its derivation path" in { + val bytes = + hex"70736274ff01005e020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff0148e6052a0100000022512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a07572116fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000011720fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232000105201124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e67121071124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e6711900772b2da7560000800100008000000080000000000500000000" + assert(Try(PSBT.fromBytes(bytes)).isSuccess) + val psbt = PSBT.fromBytes(bytes) + assert(PSBT.fromBytes(psbt.bytes) == psbt) + } + + it must "PSBT with one P2TR script path only input with dummy internal key, scripts, derivation paths for keys in the scripts, and merkle root" in { + val bytes = + hex"70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a0100000022512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b6926215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac06f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f823202cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2acc04215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac097c6e6fea5ff714ff5724499990810e406e98aa10f5bf7e5f6784bc1d0a9a6ce23204320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b2acc06215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f82320fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca9acc021162cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d23901cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09772b2da7560000800100008002000080000000000000000021164320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b23901115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f8772b2da75600008001000080010000800000000000000000211650929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac005007c461e5d2116fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca939016f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970772b2da7560000800100008003000080000000000000000001172050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0011820f0362e2f75a6f420a5bde3eb221d96ae6720cf25f81890c95b1d775acb515e65000105201124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e67121071124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e6711900772b2da7560000800100008000000080000000000500000000" + assert(Try(PSBT.fromBytes(bytes)).isSuccess) + val psbt = PSBT.fromBytes(bytes) + assert(PSBT.fromBytes(psbt.bytes) == psbt) + } + + it must "PSBT with one P2TR script path only output with dummy internal key, taproot tree, and script key derivation paths" in { + val bytes = + hex"70736274ff01005e020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff0148e6052a010000002251200a8cbdc86de1ce1c0f9caeb22d6df7ced3683fe423e05d1e402a879341d6f6f5000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a07572116fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000011720fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2320001052050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac001066f02c02220736e572900fe1252589a2143c8f3c79f71a0412d2353af755e9701c782694a02ac02c02220631c5f3b5832b8fbdebfb19704ceeb323c21f40f7a24f43d68ef0cc26b125969ac01c0222044faa49a0338de488c8dfffecdfb6f329f380bd566ef20c8df6d813eab1c4273ac210744faa49a0338de488c8dfffecdfb6f329f380bd566ef20c8df6d813eab1c42733901f06b798b92a10ed9a9d0bbfd3af173a53b1617da3a4159ca008216cd856b2e0e772b2da75600008001000080010000800000000003000000210750929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac005007c461e5d2107631c5f3b5832b8fbdebfb19704ceeb323c21f40f7a24f43d68ef0cc26b125969390118ace409889785e0ea70ceebb8e1ca892a7a78eaede0f2e296cf435961a8f4ca772b2da756000080010000800200008000000000030000002107736e572900fe1252589a2143c8f3c79f71a0412d2353af755e9701c782694a02390129a5b4915090162d759afd3fe0f93fa3326056d0b4088cb933cae7826cb8d82c772b2da7560000800100008003000080000000000300000000" + assert(Try(PSBT.fromBytes(bytes)).isSuccess) + val psbt = PSBT.fromBytes(bytes) + assert(PSBT.fromBytes(psbt.bytes) == psbt) + } + + it must "PSBT with one P2TR script path only input with dummy internal key, scripts, script key derivation paths, merkle root, and script path" in { + val bytes = + hex"70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a0100000022512083698e458c6664e1595d75da2597de1e22ee97d798e706c4c0a4b5a9823cd743000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b69241142cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b0940bf818d9757d6ffeb538ba057fb4c1fc4e0f5ef186e765beb564791e02af5fd3d5e2551d4e34e33d86f276b82c99c79aed3f0395a081efcd2cc2c65dd7e693d7941144320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b2115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f840e1f1ab6fabfa26b236f21833719dc1d428ab768d80f91f9988d8abef47bfb863bb1f2a529f768c15f00ce34ec283cdc07e88f8428be28f6ef64043c32911811a4114fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca96f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae97040ec1f0379206461c83342285423326708ab031f0da4a253ee45aafa5b8c92034d8b605490f8cd13e00f989989b97e215faa36f12dee3693d2daccf3781c1757f66215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac06f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f823202cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2acc04215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac097c6e6fea5ff714ff5724499990810e406e98aa10f5bf7e5f6784bc1d0a9a6ce23204320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b2acc06215c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f82320fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca9acc021162cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d23901cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b09772b2da7560000800100008002000080000000000000000021164320b0bf16f011b53ea7be615924aa7f27e5d29ad20ea1155d848676c3bad1b23901115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f8772b2da75600008001000080010000800000000000000000211650929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac005007c461e5d2116fa0f7a3cef3b1d0c0a6ce7d26e17ada0b2e5c92d19efad48b41859cb8a451ca939016f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970772b2da7560000800100008003000080000000000000000001172050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0011820f0362e2f75a6f420a5bde3eb221d96ae6720cf25f81890c95b1d775acb515e65000105201124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e67121071124da7aec92ccd06c954562647f437b138b95721a84be2bf2276bbddab3e6711900772b2da7560000800100008000000080000000000500000000" + assert(Try(PSBT.fromBytes(bytes)).isSuccess) + val psbt = PSBT.fromBytes(bytes) + assert(PSBT.fromBytes(psbt.bytes) == psbt) + } + + it must "pass BIP 371 negative tests" in { + val vec = Vector( + hex"70736274ff010071020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02787c01000000000016001483a7e34bd99ff03a4962ef8a1a101bb295461ece606b042a010000001600147ac369df1b20e033d6116623957b0ac49f3c52e8000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a075701172102fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa232000000", + hex"70736274ff010071020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02787c01000000000016001483a7e34bd99ff03a4962ef8a1a101bb295461ece606b042a010000001600147ac369df1b20e033d6116623957b0ac49f3c52e8000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a075701133f173bb3d36c074afb716fec6307a069a2e450b995f3c82785945ab8df0e24260dcd703b0cbf34de399184a9481ac2b3586db6601f026a77f7e4938481bc3475000000", + hex"70736274ff010071020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02787c01000000000016001483a7e34bd99ff03a4962ef8a1a101bb295461ece606b042a010000001600147ac369df1b20e033d6116623957b0ac49f3c52e8000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757011342173bb3d36c074afb716fec6307a069a2e450b995f3c82785945ab8df0e24260dcd703b0cbf34de399184a9481ac2b3586db6601f026a77f7e4938481bc34751701aa000000", + hex"70736274ff010071020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02787c01000000000016001483a7e34bd99ff03a4962ef8a1a101bb295461ece606b042a010000001600147ac369df1b20e033d6116623957b0ac49f3c52e8000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757221602fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da75600008001000080000000800100000000000000000000", + hex"70736274ff01007d020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02887b0100000000001600142382871c7e8421a00093f754d91281e675874b9f606b042a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757000001052102fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa23200", + hex"70736274ff01007d020000000127744ababf3027fe0d6cf23a96eee2efb188ef52301954585883e69b6624b2420000000000ffffffff02887b0100000000001600142382871c7e8421a00093f754d91281e675874b9f606b042a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a0757000000000001012b00f2052a010000002251205a2c2cf5b52cf31f83ad2e8da63ff03183ecd8f609c7510ae8a48e03910a07570000220702fe349064c98d6e2a853fa3c9b12bd8b304a19c195c60efa7ee2393046d3fa2321900772b2da7560000800100008000000080010000000000000000", + hex"70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b6924214022cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b094089756aa3739ccc689ec0fcf3a360be32cc0b59b16e93a1e8bb4605726b2ca7a3ff706c4176649632b2cc68e1f912b8a578e3719ce7710885c7a966f49bcd43cb0000", + hex"70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b69241142cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b094289756aa3739ccc689ec0fcf3a360be32cc0b59b16e93a1e8bb4605726b2ca7a3ff706c4176649632b2cc68e1f912b8a578e3719ce7710885c7a966f49bcd43cb01010000", + hex"70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b69241142cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2cd970e15f53fc0c82f950fd560ffa919b76172be017368a89913af074f400b093f89756aa3739ccc689ec0fcf3a360be32cc0b59b16e93a1e8bb4605726b2ca7a3ff706c4176649632b2cc68e1f912b8a578e3719ce7710885c7a966f49bcd430000", + hex"70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b6926315c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac06f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e1f80023202cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2acc00000", + hex"70736274ff01005e02000000019bd48765230bf9a72e662001f972556e54f0c6f97feb56bcb5600d817f6995260100000000ffffffff0148e6052a01000000225120030da4fce4f7db28c2cb2951631e003713856597fe963882cb500e68112cca63000000000001012b00f2052a01000000225120c2247efbfd92ac47f6f40b8d42d169175a19fa9fa10e4a25d7f35eb4dd85b6926115c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac06f7d62059e9497a1a4a267569d9876da60101aff38e3529b9b939ce7f91ae970115f2e490af7cc45c4f78511f36057ce5c5a5c56325a29fb44dfc203f356e123202cb13ac68248de806aa6a3659cf3c03eb6821d09c8114a4e868febde865bb6d2acc00000" + ) + + assert(vec.forall(b => Try(PSBT.fromBytes(b)).isFailure)) + } } diff --git a/core/src/main/scala/org/bitcoins/core/psbt/PSBTKeyId.scala b/core/src/main/scala/org/bitcoins/core/psbt/PSBTKeyId.scala index 66a25dec3d..5a291f1c07 100644 --- a/core/src/main/scala/org/bitcoins/core/psbt/PSBTKeyId.scala +++ b/core/src/main/scala/org/bitcoins/core/psbt/PSBTKeyId.scala @@ -86,6 +86,12 @@ object PSBTInputKeyId extends PSBTKeyIdFactory[PSBTInputKeyId] { case SHA256PreImageKeyId.byte => SHA256PreImageKeyId case HASH160PreImageKeyId.byte => HASH160PreImageKeyId case HASH256PreImageKeyId.byte => HASH256PreImageKeyId + case TRKeySpendSignatureKeyId.byte => TRKeySpendSignatureKeyId + case TRScriptSpendSignatureKeyId.byte => TRScriptSpendSignatureKeyId + case TRLeafScriptKeyId.byte => TRLeafScriptKeyId + case TRBIP32DerivationPathKeyId.byte => TRBIP32DerivationPathKeyId + case TRInternalKeyKeyId.byte => TRInternalKeyKeyId + case TRMerkelRootKeyId.byte => TRMerkelRootKeyId case _: Byte => UnknownKeyId } @@ -160,6 +166,36 @@ object PSBTInputKeyId extends PSBTKeyIdFactory[PSBTInputKeyId] { type RecordType = InputPSBTRecord.HASH256PreImage } + final case object TRKeySpendSignatureKeyId extends PSBTInputKeyId { + override val byte: Byte = 0x13.byteValue + type RecordType = InputPSBTRecord.TRKeySpendSignature + } + + final case object TRScriptSpendSignatureKeyId extends PSBTInputKeyId { + override val byte: Byte = 0x14.byteValue + type RecordType = InputPSBTRecord.TRScriptSpendSignature + } + + final case object TRLeafScriptKeyId extends PSBTInputKeyId { + override val byte: Byte = 0x15.byteValue + type RecordType = InputPSBTRecord.TRLeafScript + } + + final case object TRBIP32DerivationPathKeyId extends PSBTInputKeyId { + override val byte: Byte = 0x16.byteValue + type RecordType = InputPSBTRecord.TRBIP32DerivationPath + } + + final case object TRInternalKeyKeyId extends PSBTInputKeyId { + override val byte: Byte = 0x17.byteValue + type RecordType = InputPSBTRecord.TRInternalKey + } + + final case object TRMerkelRootKeyId extends PSBTInputKeyId { + override val byte: Byte = 0x18.byteValue + type RecordType = InputPSBTRecord.TRMerkelRoot + } + final case object UnknownKeyId extends PSBTInputKeyId { override val byte: Byte = Byte.MaxValue type RecordType = InputPSBTRecord.Unknown @@ -176,10 +212,13 @@ object PSBTOutputKeyId extends PSBTKeyIdFactory[PSBTOutputKeyId] { override def fromByte(byte: Byte): PSBTOutputKeyId = byte match { - case RedeemScriptKeyId.byte => RedeemScriptKeyId - case WitnessScriptKeyId.byte => WitnessScriptKeyId - case BIP32DerivationPathKeyId.byte => BIP32DerivationPathKeyId - case _: Byte => UnknownKeyId + case RedeemScriptKeyId.byte => RedeemScriptKeyId + case WitnessScriptKeyId.byte => WitnessScriptKeyId + case BIP32DerivationPathKeyId.byte => BIP32DerivationPathKeyId + case TRInternalKeyKeyId.byte => TRInternalKeyKeyId + case TaprootTreeKeyId.byte => TaprootTreeKeyId + case TRBIP32DerivationPathKeyId.byte => TRBIP32DerivationPathKeyId + case _: Byte => UnknownKeyId } final case object RedeemScriptKeyId extends PSBTOutputKeyId { @@ -197,6 +236,21 @@ object PSBTOutputKeyId extends PSBTKeyIdFactory[PSBTOutputKeyId] { type RecordType = OutputPSBTRecord.BIP32DerivationPath } + final case object TRInternalKeyKeyId extends PSBTOutputKeyId { + override val byte: Byte = 0x05.byteValue + type RecordType = OutputPSBTRecord.TRInternalKey + } + + final case object TaprootTreeKeyId extends PSBTOutputKeyId { + override val byte: Byte = 0x06.byteValue + type RecordType = OutputPSBTRecord.TRInternalKey + } + + final case object TRBIP32DerivationPathKeyId extends PSBTOutputKeyId { + override val byte: Byte = 0x07.byteValue + type RecordType = OutputPSBTRecord.TRBIP32DerivationPath + } + final case object UnknownKeyId extends PSBTOutputKeyId { override val byte: Byte = Byte.MaxValue type RecordType = OutputPSBTRecord.Unknown diff --git a/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala b/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala index f865406cae..808477065f 100644 --- a/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala +++ b/core/src/main/scala/org/bitcoins/core/psbt/PSBTMap.scala @@ -195,6 +195,7 @@ case class InputPSBTMap(elements: Vector[InputPSBTRecord]) } } + // todo maybe rethink return type /** The HASH160 of each public key that could be used to sign the input, * if calculable. [[Sha256Hash160Digest]] is used because we won't know the * raw public key for P2PKH scripts @@ -205,7 +206,15 @@ case class InputPSBTMap(elements: Vector[InputPSBTRecord]) spk: ScriptPubKey): Vector[Sha256Hash160Digest] = { spk match { case spk: TaprootScriptPubKey => - throw new IllegalArgumentException(s"Taproot not yet supported: $spk") + leafScriptOpt match { + case Some(script) => + missingSigsFromScript(script.script) + case None => + // key spend 1 signature + if (partialSignatures.isEmpty) { + Vector(CryptoUtil.sha256Hash160(spk.pubKey.bytes)) + } else Vector.empty + } case EmptyScriptPubKey | _: WitnessCommitment | _: NonStandardScriptPubKey | _: UnassignedWitnessScriptPubKey => Vector.empty @@ -318,6 +327,26 @@ case class InputPSBTMap(elements: Vector[InputPSBTRecord]) getRecords(ProofOfReservesCommitmentKeyId).headOption } + def keySpendSignatureOpt: Option[TRKeySpendSignature] = { + getRecords(TRKeySpendSignatureKeyId).headOption + } + + def scriptSpendSignatureOpt: Option[TRScriptSpendSignature] = { + getRecords(TRScriptSpendSignatureKeyId).headOption + } + + def leafScriptOpt: Option[TRLeafScript] = { + getRecords(TRLeafScriptKeyId).headOption + } + + def taprootInternalKey: Option[TRInternalKey] = { + getRecords(TRInternalKeyKeyId).headOption + } + + def taprootMerkelRoot: Option[TRMerkelRoot] = { + getRecords(TRMerkelRootKeyId).headOption + } + def getRecords(key: PSBTInputKeyId): Vector[key.RecordType] = { super.getRecords(key, PSBTInputKeyId) } @@ -606,8 +635,22 @@ case class InputPSBTMap(elements: Vector[InputPSBTRecord]) case EmptyScriptPubKey => val scriptSig = TrivialTrueScriptSignature Success(wipeAndAdd(scriptSig)) + case _: TaprootScriptPubKey => + keySpendSignatureOpt match { + case Some(keySpendSignature) => + val sig = keySpendSignature.signature + val hashType = + sigHashTypeOpt.map(_.hashType).getOrElse(SIGHASH_DEFAULT) + + val witnessScript = TaprootKeyPath(sig, hashType, None) + Success(wipeAndAdd(EmptyScriptSignature, Some(witnessScript))) + case None => + // todo add script spend support + Failure(new UnsupportedOperationException( + s"Cannot finalize the following input because no key spend signature was provided: $this")) + } case _: NonStandardScriptPubKey | _: UnassignedWitnessScriptPubKey | - _: WitnessCommitment | _: TaprootScriptPubKey => + _: WitnessCommitment => Failure( new UnsupportedOperationException( s"$spkToSatisfy is not yet supported")) @@ -948,6 +991,10 @@ case class OutputPSBTMap(elements: Vector[OutputPSBTRecord]) getRecords(BIP32DerivationPathKeyId) } + def taprootInternalKey: Option[TRInternalKey] = { + getRecords(TRInternalKeyKeyId).headOption + } + def getRecords(key: PSBTOutputKeyId): Vector[key.RecordType] = { super.getRecords(key, PSBTOutputKeyId) } diff --git a/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala b/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala index f5ed48c29e..a01b1c575f 100644 --- a/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala +++ b/core/src/main/scala/org/bitcoins/core/psbt/PSBTRecord.scala @@ -2,7 +2,7 @@ package org.bitcoins.core.psbt import org.bitcoins.core.crypto.ExtPublicKey import org.bitcoins.core.hd.BIP32Path -import org.bitcoins.core.number.{Int32, UInt32} +import org.bitcoins.core.number.{Int32, UInt32, UInt64} import org.bitcoins.core.protocol.CompactSizeUInt import org.bitcoins.core.protocol.script._ import org.bitcoins.core.protocol.transaction.{ @@ -16,6 +16,8 @@ import org.bitcoins.core.util.BytesUtil import org.bitcoins.crypto.{HashType, _} import scodec.bits.ByteVector +import scala.annotation.tailrec + sealed trait PSBTRecord extends NetworkElement { type KeyId <: PSBTKeyId @@ -321,6 +323,79 @@ object InputPSBTRecord extends Factory[InputPSBTRecord] { override val value: ByteVector = preImage } + case class TRKeySpendSignature(signature: SchnorrDigitalSignature) + extends InputPSBTRecord { + override type KeyId = TRKeySpendSignatureKeyId.type + + override val key: ByteVector = ByteVector(TRKeySpendSignatureKeyId.byte) + override val value: ByteVector = signature.bytes + } + + case class TRScriptSpendSignature( + xOnlyPubKey: XOnlyPubKey, + leafHash: Sha256Digest, + signature: SchnorrDigitalSignature) + extends InputPSBTRecord { + override type KeyId = TRScriptSpendSignatureKeyId.type + + override val key: ByteVector = ByteVector( + TRScriptSpendSignatureKeyId.byte) ++ xOnlyPubKey.bytes ++ leafHash.bytes + override val value: ByteVector = signature.bytes + } + + case class TRLeafScript( + controlBlock: ControlBlock, + script: RawScriptPubKey, + leafVersion: Byte) + extends InputPSBTRecord { + override type KeyId = TRLeafScriptKeyId.type + + override val key: ByteVector = { + ByteVector(TRLeafScriptKeyId.byte) ++ controlBlock.bytes + } + + override val value: ByteVector = { + script.asmBytes ++ ByteVector.fromByte(leafVersion) + } + } + + case class TRBIP32DerivationPath( + xOnlyPubKey: XOnlyPubKey, + hashes: Vector[Sha256Digest], + masterFingerprint: ByteVector, + path: BIP32Path) + extends InputPSBTRecord { + + override type KeyId = TRBIP32DerivationPathKeyId.type + + override val key: ByteVector = + ByteVector(TRBIP32DerivationPathKeyId.byte) ++ xOnlyPubKey.bytes + + override val value: ByteVector = { + val hashesBytes = if (hashes.isEmpty) { + CompactSizeUInt.zero.bytes + } else { + CompactSizeUInt(UInt64(hashes.length)).bytes ++ + hashes.map(_.bytes).reduce(_ ++ _) + } + hashesBytes ++ path.foldLeft(masterFingerprint)(_ ++ _.toUInt32.bytesLE) + } + } + + case class TRInternalKey(xOnlyPubKey: XOnlyPubKey) extends InputPSBTRecord { + override type KeyId = TRInternalKeyKeyId.type + + override val key: ByteVector = ByteVector(TRInternalKeyKeyId.byte) + override val value: ByteVector = xOnlyPubKey.bytes + } + + case class TRMerkelRoot(hash: Sha256Digest) extends InputPSBTRecord { + override type KeyId = TRMerkelRootKeyId.type + + override val key: ByteVector = ByteVector(TRMerkelRootKeyId.byte) + override val value: ByteVector = hash.bytes + } + case class Unknown(key: ByteVector, value: ByteVector) extends InputPSBTRecord { override type KeyId = UnknownKeyId.type @@ -421,6 +496,63 @@ object InputPSBTRecord extends Factory[InputPSBTRecord] { require(record.hash.bytes == hash, "Received invalid HASH256PreImage, hash does not match") record + case TRKeySpendSignatureKeyId => + require(key.size == 1, + s"The key must only contain the 1 byte type, got: ${key.size}") + + val sig = SchnorrDigitalSignature.fromBytes(value) + TRKeySpendSignature(sig) + case TRScriptSpendSignatureKeyId => + require( + key.size == 65, + s"The key must only contain the 65 bytes type, got: ${key.size}") + + val (xOnlyPubKey, leafHash) = key.tail.splitAt(32) + val sig = SchnorrDigitalSignature.fromBytes(value) + + TRScriptSpendSignature(XOnlyPubKey(xOnlyPubKey), + Sha256Digest(leafHash), + sig) + + case TRLeafScriptKeyId => + val controlBlock = ControlBlock(key.tail) + + val script = RawScriptPubKey.fromAsmBytes(value.init) + + TRLeafScript(controlBlock, script, value.last) + case TRBIP32DerivationPathKeyId => + val pubKey = XOnlyPubKey(key.tail) + val numHashes = CompactSizeUInt.fromBytes(value) + val hashes = value + .drop(numHashes.byteSize) + .take(numHashes.num.toInt * 32) + .grouped(32) + .map(Sha256Digest.fromBytes) + .toVector + val remaining = value.drop(numHashes.byteSize + hashes.size * 32) + val fingerprint = remaining.take(4) + val path = BIP32Path.fromBytesLE(remaining.drop(4)) + + TRBIP32DerivationPath(xOnlyPubKey = pubKey, + hashes = hashes, + masterFingerprint = fingerprint, + path = path) + case TRInternalKeyKeyId => + require(key.size == 1, + s"The key must only contain the 1 byte type, got: ${key.size}") + + require( + value.size == 32, + s"The value must contain the 32 byte x-only public key, got: ${value.size}") + TRInternalKey(XOnlyPubKey.fromBytes(value)) + case TRMerkelRootKeyId => + require(key.size == 1, + s"The key must only contain the 1 byte type, got: ${key.size}") + + require( + value.size == 32, + s"The value must contain the 32 byte x-only public key, got: ${value.size}") + TRMerkelRoot(Sha256Digest(value)) case UnknownKeyId => InputPSBTRecord.Unknown(key, value) } @@ -464,6 +596,57 @@ object OutputPSBTRecord extends Factory[OutputPSBTRecord] { path.foldLeft(masterFingerprint)(_ ++ _.toUInt32.bytesLE) } + case class TaprootTree( + leafs: Vector[ + (Byte, Byte, ByteVector) + ] // todo change to TapScriptPubKey when we have a TapScriptPubKey type + ) extends OutputPSBTRecord { + require(leafs.nonEmpty) + override type KeyId = TaprootTreeKeyId.type + + override val key: ByteVector = + ByteVector(TaprootTreeKeyId.byte) + + override val value: ByteVector = { + leafs.foldLeft(ByteVector.empty) { (acc, leaf) => + val spk = leaf._3 + acc ++ ByteVector.fromByte(leaf._1) ++ ByteVector.fromByte( + leaf._2) ++ CompactSizeUInt.calc(spk).bytes ++ spk + } + } + } + + case class TRBIP32DerivationPath( + xOnlyPubKey: XOnlyPubKey, + hashes: Vector[Sha256Digest], + masterFingerprint: ByteVector, + path: BIP32Path) + extends OutputPSBTRecord { + + override type KeyId = TRBIP32DerivationPathKeyId.type + + override val key: ByteVector = + ByteVector(TRBIP32DerivationPathKeyId.byte) ++ xOnlyPubKey.bytes + + override val value: ByteVector = { + val hashesBytes = if (hashes.isEmpty) { + CompactSizeUInt.zero.bytes + } else { + CompactSizeUInt(UInt64(hashes.size)).bytes ++ + hashes.map(_.bytes).reduce(_ ++ _) + } + hashesBytes ++ path.foldLeft(masterFingerprint)(_ ++ _.toUInt32.bytesLE) + } + } + + case class TRInternalKey(xOnlyPubKey: XOnlyPubKey) extends OutputPSBTRecord { + override type KeyId = TRInternalKeyKeyId.type + + override val key: ByteVector = ByteVector(TRInternalKeyKeyId.byte) + + override val value: ByteVector = xOnlyPubKey.bytes + } + case class Unknown(key: ByteVector, value: ByteVector) extends OutputPSBTRecord { override type KeyId = UnknownKeyId.type @@ -491,6 +674,49 @@ object OutputPSBTRecord extends Factory[OutputPSBTRecord] { val path = BIP32Path.fromBytesLE(value.drop(4)) OutputPSBTRecord.BIP32DerivationPath(pubKey, fingerprint, path) + case PSBTOutputKeyId.TRInternalKeyKeyId => + require(key.size == 1, + s"The key must only contain the 1 byte type, got: ${key.size}") + + val xOnlyPubKey = XOnlyPubKey.fromBytes(value) + OutputPSBTRecord.TRInternalKey(xOnlyPubKey) + case TaprootTreeKeyId => + @tailrec + def loop( + bytes: ByteVector, + accum: Vector[(Byte, Byte, ByteVector)]): Vector[ + (Byte, Byte, ByteVector)] = { + if (bytes.isEmpty) { + accum + } else { + val depth = bytes.head + val version = bytes.tail.head + val spkLen = CompactSizeUInt.fromBytes(bytes.drop(2)) + val spk = bytes.drop(spkLen.byteSize + 2) + + val remaining = bytes.drop(2 + spkLen.byteSize + spk.length) + loop(remaining, accum :+ (depth, version, spk)) + } + } + val leafs = loop(value, Vector.empty) + OutputPSBTRecord.TaprootTree(leafs) + case PSBTOutputKeyId.TRBIP32DerivationPathKeyId => + val pubKey = XOnlyPubKey(key.tail) + val numHashes = CompactSizeUInt.fromBytes(value) + val hashes = value + .drop(numHashes.byteSize) + .take(numHashes.num.toInt * 32) + .grouped(32) + .map(Sha256Digest.fromBytes) + .toVector + val remaining = value.drop(numHashes.byteSize + hashes.size * 32) + val fingerprint = remaining.take(4) + val path = BIP32Path.fromBytesLE(remaining.drop(4)) + + OutputPSBTRecord.TRBIP32DerivationPath(xOnlyPubKey = pubKey, + hashes = hashes, + masterFingerprint = fingerprint, + path = path) case UnknownKeyId => OutputPSBTRecord.Unknown(key, value) }