diff --git a/package-lock.json b/package-lock.json
index 75ffeaacc..17e16965a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "bitcoinjs-lib",
- "version": "6.0.2",
+ "version": "6.1.0-rc.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -24,15 +24,15 @@
}
},
"@babel/compat-data": {
- "version": "7.18.5",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.5.tgz",
- "integrity": "sha512-BxhE40PVCBxVEJsSBhB6UWyAuqJRxGsAw8BdHMJ3AKGydcwuWW4kOO3HmqBQAdcq/OP+/DlTVxLvsCzRTnZuGg==",
+ "version": "7.17.10",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.10.tgz",
+ "integrity": "sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==",
"dev": true
},
"@babel/core": {
- "version": "7.18.5",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.5.tgz",
- "integrity": "sha512-MGY8vg3DxMnctw0LdvSEojOsumc70g0t18gNyUdAZqB1Rpd1Bqo/svHGvt+UJ6JcGX+DIekGFDxxIWofBxLCnQ==",
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.2.tgz",
+ "integrity": "sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ==",
"dev": true,
"requires": {
"@ampproject/remapping": "^2.1.0",
@@ -41,10 +41,10 @@
"@babel/helper-compilation-targets": "^7.18.2",
"@babel/helper-module-transforms": "^7.18.0",
"@babel/helpers": "^7.18.2",
- "@babel/parser": "^7.18.5",
+ "@babel/parser": "^7.18.0",
"@babel/template": "^7.16.7",
- "@babel/traverse": "^7.18.5",
- "@babel/types": "^7.18.4",
+ "@babel/traverse": "^7.18.2",
+ "@babel/types": "^7.18.2",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -235,9 +235,9 @@
}
},
"@babel/parser": {
- "version": "7.18.5",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.5.tgz",
- "integrity": "sha512-YZWVaglMiplo7v8f1oMQ5ZPQr0vn7HPeZXxXWsxXJRjGVrzUFn9OxFQl1sb5wzfootjA/yChhW84BV+383FSOw==",
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.3.tgz",
+ "integrity": "sha512-rL50YcEuHbbauAFAysNsJA4/f89fGTOBRNs9P81sniKnKAr4xULe5AecolcsKbi88xu0ByWYDj/S1AJ3FSFuSQ==",
"dev": true
},
"@babel/template": {
@@ -274,9 +274,9 @@
}
},
"@babel/traverse": {
- "version": "7.18.5",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.5.tgz",
- "integrity": "sha512-aKXj1KT66sBj0vVzk6rEeAO6Z9aiiQ68wfDgge3nHhA/my6xMM/7HGQUNumKZaoa2qUPQ5whJG9aAifsxUKfLA==",
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.2.tgz",
+ "integrity": "sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.16.7",
@@ -285,8 +285,8 @@
"@babel/helper-function-name": "^7.17.9",
"@babel/helper-hoist-variables": "^7.16.7",
"@babel/helper-split-export-declaration": "^7.16.7",
- "@babel/parser": "^7.18.5",
- "@babel/types": "^7.18.4",
+ "@babel/parser": "^7.18.0",
+ "@babel/types": "^7.18.2",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@@ -314,9 +314,9 @@
}
},
"@babel/types": {
- "version": "7.18.4",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz",
- "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==",
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.2.tgz",
+ "integrity": "sha512-0On6B8A4/+mFUto5WERt3EEuG1NznDirvwca1O8UwXQHVY8g3R7OzYgxXdOfMwLO08UrpUD/2+3Bclyq+/C94Q==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.16.7",
@@ -336,12 +336,6 @@
"resolve-from": "^5.0.0"
},
"dependencies": {
- "camelcase": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
- "dev": true
- },
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@@ -361,15 +355,6 @@
"p-locate": "^4.1.0"
}
},
- "p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "requires": {
- "p-try": "^2.0.0"
- }
- },
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
@@ -523,9 +508,9 @@
}
},
"ajv": {
- "version": "8.11.0",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz",
- "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==",
+ "version": "8.11.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz",
+ "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
@@ -647,9 +632,9 @@
"dev": true
},
"bip174": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz",
- "integrity": "sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ=="
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.0.tgz",
+ "integrity": "sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA=="
},
"bip32": {
"version": "3.0.1",
@@ -711,12 +696,6 @@
"integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==",
"dev": true
},
- "bn.js": {
- "version": "4.11.8",
- "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
- "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
- "dev": true
- },
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -743,15 +722,15 @@
"dev": true
},
"browserslist": {
- "version": "4.20.4",
- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.4.tgz",
- "integrity": "sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw==",
+ "version": "4.20.3",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz",
+ "integrity": "sha512-NBhymBQl1zM0Y5dQT/O+xiLP9/rzOIQdKM/eMJBAq7yBgaB6krIYLGejrwVYnSHZdqjscB1SPuAjHwxjvN6Wdg==",
"dev": true,
"requires": {
- "caniuse-lite": "^1.0.30001349",
- "electron-to-chromium": "^1.4.147",
+ "caniuse-lite": "^1.0.30001332",
+ "electron-to-chromium": "^1.4.118",
"escalade": "^3.1.1",
- "node-releases": "^2.0.5",
+ "node-releases": "^2.0.3",
"picocolors": "^1.0.0"
}
},
@@ -798,15 +777,15 @@
}
},
"camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
"caniuse-lite": {
- "version": "1.0.30001352",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001352.tgz",
- "integrity": "sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==",
+ "version": "1.0.30001343",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001343.tgz",
+ "integrity": "sha512-8KeCrAtPMabo/XW14B+R9sZYoClx1n0b+WYgwDKZPtWR3TcdvWzdSy7mPyFEmR5WU1St9v1PW6sdO5dkFOEzfA==",
"dev": true
},
"chalk": {
@@ -953,9 +932,9 @@
}
},
"dayjs": {
- "version": "1.11.3",
- "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.3.tgz",
- "integrity": "sha512-xxwlswWOlGhzgQ4TKzASQkUhqERI3egRNqgV4ScR8wlANA/A9tZ7miXa44vTTKEq5l7vWoL5G57bG3zA+Kow0A==",
+ "version": "1.11.6",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.6.tgz",
+ "integrity": "sha512-zZbY5giJAinCG+7AGaw0wIhNZ6J8AhWuSXKvuc1KAyMiRsvGQWqh4L+MomvhdAYjN+lqvVCMq1I41e3YHvXkyQ==",
"dev": true
},
"debug": {
@@ -976,9 +955,9 @@
}
},
"decamelize": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
- "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
"default-require-extensions": {
@@ -1017,9 +996,9 @@
}
},
"electron-to-chromium": {
- "version": "1.4.153",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.153.tgz",
- "integrity": "sha512-57AV9DNW1R52HjOqnGOCCTLHMHItLTGu/WjB1KYIa4BQ7p0u8J0j8N78akPcOBStKE801xcMjTpmbAylflfIYQ==",
+ "version": "1.4.139",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.139.tgz",
+ "integrity": "sha512-lYxzcUCjWxxVug+A7UxBCUiVr13TCjfZFYJS9Lq1VpU/ErwV4a6zUQo9dfojuGpw/L/x9REGuBl6ICQPGgbs3g==",
"dev": true
},
"emoji-regex": {
@@ -1232,7 +1211,7 @@
"imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
"dev": true
},
"indent-string": {
@@ -1268,7 +1247,7 @@
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
- "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
"dev": true
},
"is-fullwidth-code-point": {
@@ -1313,7 +1292,7 @@
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true
},
"is-unicode-supported": {
@@ -1331,7 +1310,7 @@
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"istanbul-lib-coverage": {
@@ -1370,17 +1349,18 @@
}
},
"istanbul-lib-processinfo": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz",
- "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==",
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.2.tgz",
+ "integrity": "sha512-kOwpa7z9hme+IBPZMzQ5vdQj8srYgAtaRqeI48NGmAQ+/5yKiHLV0QbYqQpxsdEF0+w14SoB8YbnHKcXE2KnYw==",
"dev": true,
"requires": {
"archy": "^1.0.0",
- "cross-spawn": "^7.0.3",
- "istanbul-lib-coverage": "^3.2.0",
+ "cross-spawn": "^7.0.0",
+ "istanbul-lib-coverage": "^3.0.0-alpha.1",
+ "make-dir": "^3.0.0",
"p-map": "^3.0.0",
"rimraf": "^3.0.0",
- "uuid": "^8.3.2"
+ "uuid": "^3.3.3"
},
"dependencies": {
"rimraf": {
@@ -1489,7 +1469,7 @@
"lodash.flattendeep": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
- "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==",
+ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
"dev": true
},
"lodash.get": {
@@ -1615,9 +1595,9 @@
}
},
"minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
@@ -1819,12 +1799,6 @@
"color-convert": "^2.0.1"
}
},
- "camelcase": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
- "dev": true
- },
"cliui": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@@ -1851,12 +1825,6 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
- "decamelize": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
- "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
- "dev": true
- },
"find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
@@ -1899,15 +1867,6 @@
"brace-expansion": "^1.1.7"
}
},
- "p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "requires": {
- "p-try": "^2.0.0"
- }
- },
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
@@ -1937,12 +1896,6 @@
"strip-ansi": "^6.0.0"
}
},
- "y18n": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
- "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
- "dev": true
- },
"yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
@@ -1984,12 +1937,12 @@
}
},
"p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
- "yocto-queue": "^0.1.0"
+ "p-try": "^2.0.0"
}
},
"p-locate": {
@@ -1999,6 +1952,17 @@
"dev": true,
"requires": {
"p-limit": "^3.0.2"
+ },
+ "dependencies": {
+ "p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "requires": {
+ "yocto-queue": "^0.1.0"
+ }
+ }
}
},
"p-map": {
@@ -2053,9 +2017,9 @@
"dev": true
},
"pbkdf2": {
- "version": "3.0.17",
- "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
- "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz",
+ "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==",
"dev": true,
"requires": {
"create-hash": "^1.1.2",
@@ -2105,15 +2069,6 @@
"p-locate": "^4.1.0"
}
},
- "p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "requires": {
- "p-try": "^2.0.0"
- }
- },
"p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
@@ -2198,7 +2153,7 @@
"release-zalgo": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz",
- "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==",
+ "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=",
"dev": true,
"requires": {
"es6-error": "^4.0.1"
@@ -2207,7 +2162,7 @@
"require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
"dev": true
},
"require-from-string": {
@@ -2294,7 +2249,7 @@
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
- "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
},
"sha.js": {
@@ -2361,12 +2316,6 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
- },
- "is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true
}
}
},
@@ -2481,9 +2430,9 @@
}
},
"table": {
- "version": "6.8.0",
- "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz",
- "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==",
+ "version": "6.8.1",
+ "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz",
+ "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==",
"dev": true,
"requires": {
"ajv": "^8.0.1",
@@ -2491,46 +2440,6 @@
"slice-ansi": "^4.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "dev": true
- },
- "emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "dev": true
- },
- "is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "dev": true
- },
- "string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dev": true,
- "requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- }
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dev": true,
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- }
}
},
"test-exclude": {
@@ -2572,9 +2481,9 @@
}
},
"tiny-secp256k1": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.1.2.tgz",
- "integrity": "sha512-8qPw7zDK6Hco2tVGYGQeOmOPp/hZnREwy2iIkcq0ygAuqc9WHo29vKN94lNymh1QbB3nthtAMF6KTIrdbsIotA==",
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.0.tgz",
+ "integrity": "sha512-2hPuUGCroLrxh6xxwoe+1RgPpOOK1w2uTnhgiHBpvoutBR+krNuT4hOXQyOaaYnZgoXBB6hBYkuAJHxyeBOPzQ==",
"dev": true,
"requires": {
"uint8array-tools": "0.0.6"
@@ -2583,7 +2492,7 @@
"to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
- "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
"dev": true
},
"to-regex-range": {
@@ -2702,9 +2611,9 @@
}
},
"uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"dev": true
},
"varuint-bitcoin": {
@@ -2727,7 +2636,7 @@
"which-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
- "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
"dev": true
},
"wif": {
@@ -2800,9 +2709,9 @@
}
},
"y18n": {
- "version": "5.0.8",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
"dev": true
},
"yargs": {
@@ -2818,6 +2727,14 @@
"string-width": "^4.2.0",
"y18n": "^5.0.5",
"yargs-parser": "^20.2.2"
+ },
+ "dependencies": {
+ "y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true
+ }
}
},
"yargs-parser": {
@@ -2836,6 +2753,20 @@
"decamelize": "^4.0.0",
"flat": "^5.0.2",
"is-plain-obj": "^2.1.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true
+ },
+ "decamelize": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "dev": true
+ }
}
},
"yn": {
diff --git a/package.json b/package.json
index dd644a20a..b2414f42f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "bitcoinjs-lib",
- "version": "6.0.2",
+ "version": "6.1.0-rc.0",
"description": "Client-side Bitcoin JavaScript library",
"main": "./src/index.js",
"types": "./src/index.d.ts",
@@ -50,7 +50,7 @@
],
"dependencies": {
"bech32": "^2.0.0",
- "bip174": "^2.0.1",
+ "bip174": "^2.1.0",
"bs58check": "^2.1.2",
"create-hash": "^1.1.0",
"ripemd160": "^2.0.2",
@@ -73,7 +73,6 @@
"bip39": "^3.0.2",
"bip65": "^1.0.1",
"bip68": "^1.0.3",
- "bn.js": "^4.11.8",
"bs58": "^4.0.0",
"dhttp": "^3.0.0",
"ecpair": "^2.0.1",
@@ -86,7 +85,7 @@
"randombytes": "^2.1.0",
"regtest-client": "0.2.0",
"rimraf": "^2.6.3",
- "tiny-secp256k1": "^2.1.2",
+ "tiny-secp256k1": "^2.2.0",
"ts-node": "^8.3.0",
"tslint": "^6.1.3",
"typescript": "^4.4.4"
diff --git a/src/address.js b/src/address.js
index 164bf7ef1..de0154a3a 100644
--- a/src/address.js
+++ b/src/address.js
@@ -4,14 +4,13 @@ exports.toOutputScript = exports.fromOutputScript = exports.toBech32 = exports.t
const networks = require('./networks');
const payments = require('./payments');
const bscript = require('./script');
-const types = require('./types');
+const types_1 = require('./types');
const bech32_1 = require('bech32');
const bs58check = require('bs58check');
-const { typeforce } = types;
const FUTURE_SEGWIT_MAX_SIZE = 40;
const FUTURE_SEGWIT_MIN_SIZE = 2;
const FUTURE_SEGWIT_MAX_VERSION = 16;
-const FUTURE_SEGWIT_MIN_VERSION = 1;
+const FUTURE_SEGWIT_MIN_VERSION = 2;
const FUTURE_SEGWIT_VERSION_DIFF = 0x50;
const FUTURE_SEGWIT_VERSION_WARNING =
'WARNING: Sending to a future segwit version address can lead to loss of funds. ' +
@@ -69,7 +68,10 @@ function fromBech32(address) {
}
exports.fromBech32 = fromBech32;
function toBase58Check(hash, version) {
- typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments);
+ (0, types_1.typeforce)(
+ (0, types_1.tuple)(types_1.Hash160bit, types_1.UInt8),
+ arguments,
+ );
const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(version, 0);
hash.copy(payload, 1);
@@ -99,6 +101,9 @@ function fromOutputScript(output, network) {
try {
return payments.p2wsh({ output, network }).address;
} catch (e) {}
+ try {
+ return payments.p2tr({ output, network }).address;
+ } catch (e) {}
try {
return _toFutureSegwitAddress(output, network);
} catch (e) {}
@@ -129,6 +134,9 @@ function toOutputScript(address, network) {
return payments.p2wpkh({ hash: decodeBech32.data }).output;
if (decodeBech32.data.length === 32)
return payments.p2wsh({ hash: decodeBech32.data }).output;
+ } else if (decodeBech32.version === 1) {
+ if (decodeBech32.data.length === 32)
+ return payments.p2tr({ pubkey: decodeBech32.data }).output;
} else if (
decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION &&
decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&
diff --git a/src/ecc_lib.d.ts b/src/ecc_lib.d.ts
new file mode 100644
index 000000000..201ebb5cf
--- /dev/null
+++ b/src/ecc_lib.d.ts
@@ -0,0 +1,3 @@
+import { TinySecp256k1Interface } from './types';
+export declare function initEccLib(eccLib: TinySecp256k1Interface | undefined): void;
+export declare function getEccLib(): TinySecp256k1Interface;
diff --git a/src/ecc_lib.js b/src/ecc_lib.js
new file mode 100644
index 000000000..eaa8a5327
--- /dev/null
+++ b/src/ecc_lib.js
@@ -0,0 +1,91 @@
+'use strict';
+Object.defineProperty(exports, '__esModule', { value: true });
+exports.getEccLib = exports.initEccLib = void 0;
+const _ECCLIB_CACHE = {};
+function initEccLib(eccLib) {
+ if (!eccLib) {
+ // allow clearing the library
+ _ECCLIB_CACHE.eccLib = eccLib;
+ } else if (eccLib !== _ECCLIB_CACHE.eccLib) {
+ // new instance, verify it
+ verifyEcc(eccLib);
+ _ECCLIB_CACHE.eccLib = eccLib;
+ }
+}
+exports.initEccLib = initEccLib;
+function getEccLib() {
+ if (!_ECCLIB_CACHE.eccLib)
+ throw new Error(
+ 'No ECC Library provided. You must call initEccLib() with a valid TinySecp256k1Interface instance',
+ );
+ return _ECCLIB_CACHE.eccLib;
+}
+exports.getEccLib = getEccLib;
+const h = hex => Buffer.from(hex, 'hex');
+function verifyEcc(ecc) {
+ assert(typeof ecc.isXOnlyPoint === 'function');
+ assert(
+ ecc.isXOnlyPoint(
+ h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),
+ ),
+ );
+ assert(
+ ecc.isXOnlyPoint(
+ h('fffffffffffffffffffffffffffffffffffffffffffffffffffffffeeffffc2e'),
+ ),
+ );
+ assert(
+ ecc.isXOnlyPoint(
+ h('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9'),
+ ),
+ );
+ assert(
+ ecc.isXOnlyPoint(
+ h('0000000000000000000000000000000000000000000000000000000000000001'),
+ ),
+ );
+ assert(
+ !ecc.isXOnlyPoint(
+ h('0000000000000000000000000000000000000000000000000000000000000000'),
+ ),
+ );
+ assert(
+ !ecc.isXOnlyPoint(
+ h('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'),
+ ),
+ );
+ assert(typeof ecc.xOnlyPointAddTweak === 'function');
+ tweakAddVectors.forEach(t => {
+ const r = ecc.xOnlyPointAddTweak(h(t.pubkey), h(t.tweak));
+ if (t.result === null) {
+ assert(r === null);
+ } else {
+ assert(r !== null);
+ assert(r.parity === t.parity);
+ assert(Buffer.from(r.xOnlyPubkey).equals(h(t.result)));
+ }
+ });
+}
+function assert(bool) {
+ if (!bool) throw new Error('ecc library invalid');
+}
+const tweakAddVectors = [
+ {
+ pubkey: '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798',
+ tweak: 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
+ parity: -1,
+ result: null,
+ },
+ {
+ pubkey: '1617d38ed8d8657da4d4761e8057bc396ea9e4b9d29776d4be096016dbd2509b',
+ tweak: 'a8397a935f0dfceba6ba9618f6451ef4d80637abf4e6af2669fbc9de6a8fd2ac',
+ parity: 1,
+ result: 'e478f99dab91052ab39a33ea35fd5e6e4933f4d28023cd597c9a1f6760346adf',
+ },
+ {
+ pubkey: '2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991',
+ tweak: '823c3cd2142744b075a87eade7e1b8678ba308d566226a0056ca2b7a76f86b47',
+ parity: 0,
+ result: '9534f8dc8c6deda2dc007655981c78b49c5d96c778fbf363462a11ec9dfd948c',
+ },
+];
diff --git a/src/index.d.ts b/src/index.d.ts
index b93c2aa40..420979ffe 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -12,3 +12,4 @@ export { Transaction } from './transaction';
export { Network } from './networks';
export { Payment, PaymentCreator, PaymentOpts, Stack, StackElement, } from './payments';
export { Input as TxInput, Output as TxOutput } from './transaction';
+export { initEccLib } from './ecc_lib';
diff --git a/src/index.js b/src/index.js
index 983b0cc76..25d0b5a22 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,6 +1,6 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
-exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0;
+exports.initEccLib = exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.address = void 0;
const address = require('./address');
exports.address = address;
const crypto = require('./crypto');
@@ -39,3 +39,10 @@ Object.defineProperty(exports, 'Transaction', {
return transaction_1.Transaction;
},
});
+var ecc_lib_1 = require('./ecc_lib');
+Object.defineProperty(exports, 'initEccLib', {
+ enumerable: true,
+ get: function() {
+ return ecc_lib_1.initEccLib;
+ },
+});
diff --git a/src/ops.js b/src/ops.js
index 9d629cd00..7853ad0f0 100644
--- a/src/ops.js
+++ b/src/ops.js
@@ -117,6 +117,7 @@ const OPS = {
OP_NOP8: 183,
OP_NOP9: 184,
OP_NOP10: 185,
+ OP_CHECKSIGADD: 186,
OP_PUBKEYHASH: 253,
OP_PUBKEY: 254,
OP_INVALIDOPCODE: 255,
diff --git a/src/payments/bip341.d.ts b/src/payments/bip341.d.ts
new file mode 100644
index 000000000..8dd82409a
--- /dev/null
+++ b/src/payments/bip341.d.ts
@@ -0,0 +1,42 @@
+///
+import { Tapleaf, Taptree } from '../types';
+export declare const LEAF_VERSION_TAPSCRIPT = 192;
+export declare const MAX_TAPTREE_DEPTH = 128;
+interface HashLeaf {
+ hash: Buffer;
+}
+interface HashBranch {
+ hash: Buffer;
+ left: HashTree;
+ right: HashTree;
+}
+interface TweakedPublicKey {
+ parity: number;
+ x: Buffer;
+}
+/**
+ * Binary tree representing leaf, branch, and root node hashes of a Taptree.
+ * Each node contains a hash, and potentially left and right branch hashes.
+ * This tree is used for 2 purposes: Providing the root hash for tweaking,
+ * and calculating merkle inclusion proofs when constructing a control block.
+ */
+export declare type HashTree = HashLeaf | HashBranch;
+export declare function rootHashFromPath(controlBlock: Buffer, leafHash: Buffer): Buffer;
+/**
+ * Build a hash tree of merkle nodes from the scripts binary tree.
+ * @param scriptTree - the tree of scripts to pairwise hash.
+ */
+export declare function toHashTree(scriptTree: Taptree): HashTree;
+/**
+ * Given a HashTree, finds the path from a particular hash to the root.
+ * @param node - the root of the tree
+ * @param hash - the hash to search for
+ * @returns - array of sibling hashes, from leaf (inclusive) to root
+ * (exclusive) needed to prove inclusion of the specified hash. undefined if no
+ * path is found
+ */
+export declare function findScriptPath(node: HashTree, hash: Buffer): Buffer[] | undefined;
+export declare function tapleafHash(leaf: Tapleaf): Buffer;
+export declare function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer;
+export declare function tweakKey(pubKey: Buffer, h: Buffer | undefined): TweakedPublicKey | null;
+export {};
diff --git a/src/payments/bip341.js b/src/payments/bip341.js
new file mode 100644
index 000000000..02c976b5f
--- /dev/null
+++ b/src/payments/bip341.js
@@ -0,0 +1,108 @@
+'use strict';
+Object.defineProperty(exports, '__esModule', { value: true });
+exports.tweakKey = exports.tapTweakHash = exports.tapleafHash = exports.findScriptPath = exports.toHashTree = exports.rootHashFromPath = exports.MAX_TAPTREE_DEPTH = exports.LEAF_VERSION_TAPSCRIPT = void 0;
+const buffer_1 = require('buffer');
+const ecc_lib_1 = require('../ecc_lib');
+const bcrypto = require('../crypto');
+const bufferutils_1 = require('../bufferutils');
+const types_1 = require('../types');
+exports.LEAF_VERSION_TAPSCRIPT = 0xc0;
+exports.MAX_TAPTREE_DEPTH = 128;
+const isHashBranch = ht => 'left' in ht && 'right' in ht;
+function rootHashFromPath(controlBlock, leafHash) {
+ if (controlBlock.length < 33)
+ throw new TypeError(
+ `The control-block length is too small. Got ${
+ controlBlock.length
+ }, expected min 33.`,
+ );
+ const m = (controlBlock.length - 33) / 32;
+ let kj = leafHash;
+ for (let j = 0; j < m; j++) {
+ const ej = controlBlock.slice(33 + 32 * j, 65 + 32 * j);
+ if (kj.compare(ej) < 0) {
+ kj = tapBranchHash(kj, ej);
+ } else {
+ kj = tapBranchHash(ej, kj);
+ }
+ }
+ return kj;
+}
+exports.rootHashFromPath = rootHashFromPath;
+/**
+ * Build a hash tree of merkle nodes from the scripts binary tree.
+ * @param scriptTree - the tree of scripts to pairwise hash.
+ */
+function toHashTree(scriptTree) {
+ if ((0, types_1.isTapleaf)(scriptTree))
+ return { hash: tapleafHash(scriptTree) };
+ const hashes = [toHashTree(scriptTree[0]), toHashTree(scriptTree[1])];
+ hashes.sort((a, b) => a.hash.compare(b.hash));
+ const [left, right] = hashes;
+ return {
+ hash: tapBranchHash(left.hash, right.hash),
+ left,
+ right,
+ };
+}
+exports.toHashTree = toHashTree;
+/**
+ * Given a HashTree, finds the path from a particular hash to the root.
+ * @param node - the root of the tree
+ * @param hash - the hash to search for
+ * @returns - array of sibling hashes, from leaf (inclusive) to root
+ * (exclusive) needed to prove inclusion of the specified hash. undefined if no
+ * path is found
+ */
+function findScriptPath(node, hash) {
+ if (isHashBranch(node)) {
+ const leftPath = findScriptPath(node.left, hash);
+ if (leftPath !== undefined) return [...leftPath, node.right.hash];
+ const rightPath = findScriptPath(node.right, hash);
+ if (rightPath !== undefined) return [...rightPath, node.left.hash];
+ } else if (node.hash.equals(hash)) {
+ return [];
+ }
+ return undefined;
+}
+exports.findScriptPath = findScriptPath;
+function tapleafHash(leaf) {
+ const version = leaf.version || exports.LEAF_VERSION_TAPSCRIPT;
+ return bcrypto.taggedHash(
+ 'TapLeaf',
+ buffer_1.Buffer.concat([
+ buffer_1.Buffer.from([version]),
+ serializeScript(leaf.output),
+ ]),
+ );
+}
+exports.tapleafHash = tapleafHash;
+function tapTweakHash(pubKey, h) {
+ return bcrypto.taggedHash(
+ 'TapTweak',
+ buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]),
+ );
+}
+exports.tapTweakHash = tapTweakHash;
+function tweakKey(pubKey, h) {
+ if (!buffer_1.Buffer.isBuffer(pubKey)) return null;
+ if (pubKey.length !== 32) return null;
+ if (h && h.length !== 32) return null;
+ const tweakHash = tapTweakHash(pubKey, h);
+ const res = (0, ecc_lib_1.getEccLib)().xOnlyPointAddTweak(pubKey, tweakHash);
+ if (!res || res.xOnlyPubkey === null) return null;
+ return {
+ parity: res.parity,
+ x: buffer_1.Buffer.from(res.xOnlyPubkey),
+ };
+}
+exports.tweakKey = tweakKey;
+function tapBranchHash(a, b) {
+ return bcrypto.taggedHash('TapBranch', buffer_1.Buffer.concat([a, b]));
+}
+function serializeScript(s) {
+ const varintLen = bufferutils_1.varuint.encodingLength(s.length);
+ const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better
+ bufferutils_1.varuint.encode(s.length, buffer);
+ return buffer_1.Buffer.concat([buffer, s]);
+}
diff --git a/src/payments/index.d.ts b/src/payments/index.d.ts
index 1edf07167..07c12cc48 100644
--- a/src/payments/index.d.ts
+++ b/src/payments/index.d.ts
@@ -1,5 +1,6 @@
///
import { Network } from '../networks';
+import { Taptree } from '../types';
import { p2data as embed } from './embed';
import { p2ms } from './p2ms';
import { p2pk } from './p2pk';
@@ -7,6 +8,7 @@ import { p2pkh } from './p2pkh';
import { p2sh } from './p2sh';
import { p2wpkh } from './p2wpkh';
import { p2wsh } from './p2wsh';
+import { p2tr } from './p2tr';
export interface Payment {
name?: string;
network?: Network;
@@ -17,11 +19,14 @@ export interface Payment {
pubkeys?: Buffer[];
input?: Buffer;
signatures?: Buffer[];
+ internalPubkey?: Buffer;
pubkey?: Buffer;
signature?: Buffer;
address?: string;
hash?: Buffer;
redeem?: Payment;
+ redeemVersion?: number;
+ scriptTree?: Taptree;
witness?: Buffer[];
}
export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;
@@ -33,4 +38,4 @@ export interface PaymentOpts {
export declare type StackElement = Buffer | number;
export declare type Stack = StackElement[];
export declare type StackFunction = () => Stack;
-export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh };
+export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr };
diff --git a/src/payments/index.js b/src/payments/index.js
index c23c529c6..9ce55f859 100644
--- a/src/payments/index.js
+++ b/src/payments/index.js
@@ -1,6 +1,6 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
-exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0;
+exports.p2tr = exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0;
const embed_1 = require('./embed');
Object.defineProperty(exports, 'embed', {
enumerable: true,
@@ -50,5 +50,12 @@ Object.defineProperty(exports, 'p2wsh', {
return p2wsh_1.p2wsh;
},
});
+const p2tr_1 = require('./p2tr');
+Object.defineProperty(exports, 'p2tr', {
+ enumerable: true,
+ get: function() {
+ return p2tr_1.p2tr;
+ },
+});
// TODO
// witness commitment
diff --git a/src/payments/p2tr.d.ts b/src/payments/p2tr.d.ts
new file mode 100644
index 000000000..350ed0ffc
--- /dev/null
+++ b/src/payments/p2tr.d.ts
@@ -0,0 +1,2 @@
+import { Payment, PaymentOpts } from './index';
+export declare function p2tr(a: Payment, opts?: PaymentOpts): Payment;
diff --git a/src/payments/p2tr.js b/src/payments/p2tr.js
new file mode 100644
index 000000000..182aa62c6
--- /dev/null
+++ b/src/payments/p2tr.js
@@ -0,0 +1,294 @@
+'use strict';
+Object.defineProperty(exports, '__esModule', { value: true });
+exports.p2tr = void 0;
+const buffer_1 = require('buffer');
+const networks_1 = require('../networks');
+const bscript = require('../script');
+const types_1 = require('../types');
+const ecc_lib_1 = require('../ecc_lib');
+const bip341_1 = require('./bip341');
+const lazy = require('./lazy');
+const bech32_1 = require('bech32');
+const OPS = bscript.OPS;
+const TAPROOT_WITNESS_VERSION = 0x01;
+const ANNEX_PREFIX = 0x50;
+function p2tr(a, opts) {
+ if (
+ !a.address &&
+ !a.output &&
+ !a.pubkey &&
+ !a.internalPubkey &&
+ !(a.witness && a.witness.length > 1)
+ )
+ throw new TypeError('Not enough data');
+ opts = Object.assign({ validate: true }, opts || {});
+ (0, types_1.typeforce)(
+ {
+ address: types_1.typeforce.maybe(types_1.typeforce.String),
+ input: types_1.typeforce.maybe(types_1.typeforce.BufferN(0)),
+ network: types_1.typeforce.maybe(types_1.typeforce.Object),
+ output: types_1.typeforce.maybe(types_1.typeforce.BufferN(34)),
+ internalPubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)),
+ hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)),
+ pubkey: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)),
+ signature: types_1.typeforce.maybe(types_1.typeforce.BufferN(64)),
+ witness: types_1.typeforce.maybe(
+ types_1.typeforce.arrayOf(types_1.typeforce.Buffer),
+ ),
+ scriptTree: types_1.typeforce.maybe(types_1.isTaptree),
+ redeem: types_1.typeforce.maybe({
+ output: types_1.typeforce.maybe(types_1.typeforce.Buffer),
+ redeemVersion: types_1.typeforce.maybe(types_1.typeforce.Number),
+ witness: types_1.typeforce.maybe(
+ types_1.typeforce.arrayOf(types_1.typeforce.Buffer),
+ ),
+ }),
+ redeemVersion: types_1.typeforce.maybe(types_1.typeforce.Number),
+ },
+ a,
+ );
+ const _address = lazy.value(() => {
+ const result = bech32_1.bech32m.decode(a.address);
+ const version = result.words.shift();
+ const data = bech32_1.bech32m.fromWords(result.words);
+ return {
+ version,
+ prefix: result.prefix,
+ data: buffer_1.Buffer.from(data),
+ };
+ });
+ // remove annex if present, ignored by taproot
+ const _witness = lazy.value(() => {
+ if (!a.witness || !a.witness.length) return;
+ if (
+ a.witness.length >= 2 &&
+ a.witness[a.witness.length - 1][0] === ANNEX_PREFIX
+ ) {
+ return a.witness.slice(0, -1);
+ }
+ return a.witness.slice();
+ });
+ const _hashTree = lazy.value(() => {
+ if (a.scriptTree) return (0, bip341_1.toHashTree)(a.scriptTree);
+ if (a.hash) return { hash: a.hash };
+ return;
+ });
+ const network = a.network || networks_1.bitcoin;
+ const o = { name: 'p2tr', network };
+ lazy.prop(o, 'address', () => {
+ if (!o.pubkey) return;
+ const words = bech32_1.bech32m.toWords(o.pubkey);
+ words.unshift(TAPROOT_WITNESS_VERSION);
+ return bech32_1.bech32m.encode(network.bech32, words);
+ });
+ lazy.prop(o, 'hash', () => {
+ const hashTree = _hashTree();
+ if (hashTree) return hashTree.hash;
+ const w = _witness();
+ if (w && w.length > 1) {
+ const controlBlock = w[w.length - 1];
+ const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK;
+ const script = w[w.length - 2];
+ const leafHash = (0, bip341_1.tapleafHash)({
+ output: script,
+ version: leafVersion,
+ });
+ return (0, bip341_1.rootHashFromPath)(controlBlock, leafHash);
+ }
+ return null;
+ });
+ lazy.prop(o, 'output', () => {
+ if (!o.pubkey) return;
+ return bscript.compile([OPS.OP_1, o.pubkey]);
+ });
+ lazy.prop(o, 'redeemVersion', () => {
+ if (a.redeemVersion) return a.redeemVersion;
+ if (
+ a.redeem &&
+ a.redeem.redeemVersion !== undefined &&
+ a.redeem.redeemVersion !== null
+ ) {
+ return a.redeem.redeemVersion;
+ }
+ return bip341_1.LEAF_VERSION_TAPSCRIPT;
+ });
+ lazy.prop(o, 'redeem', () => {
+ const witness = _witness(); // witness without annex
+ if (!witness || witness.length < 2) return;
+ return {
+ output: witness[witness.length - 2],
+ witness: witness.slice(0, -2),
+ redeemVersion:
+ witness[witness.length - 1][0] & types_1.TAPLEAF_VERSION_MASK,
+ };
+ });
+ lazy.prop(o, 'pubkey', () => {
+ if (a.pubkey) return a.pubkey;
+ if (a.output) return a.output.slice(2);
+ if (a.address) return _address().data;
+ if (o.internalPubkey) {
+ const tweakedKey = (0, bip341_1.tweakKey)(o.internalPubkey, o.hash);
+ if (tweakedKey) return tweakedKey.x;
+ }
+ });
+ lazy.prop(o, 'internalPubkey', () => {
+ if (a.internalPubkey) return a.internalPubkey;
+ const witness = _witness();
+ if (witness && witness.length > 1)
+ return witness[witness.length - 1].slice(1, 33);
+ });
+ lazy.prop(o, 'signature', () => {
+ if (a.signature) return a.signature;
+ const witness = _witness(); // witness without annex
+ if (!witness || witness.length !== 1) return;
+ return witness[0];
+ });
+ lazy.prop(o, 'witness', () => {
+ if (a.witness) return a.witness;
+ const hashTree = _hashTree();
+ if (hashTree && a.redeem && a.redeem.output && a.internalPubkey) {
+ const leafHash = (0, bip341_1.tapleafHash)({
+ output: a.redeem.output,
+ version: o.redeemVersion,
+ });
+ const path = (0, bip341_1.findScriptPath)(hashTree, leafHash);
+ if (!path) return;
+ const outputKey = (0, bip341_1.tweakKey)(a.internalPubkey, hashTree.hash);
+ if (!outputKey) return;
+ const controlBock = buffer_1.Buffer.concat(
+ [
+ buffer_1.Buffer.from([o.redeemVersion | outputKey.parity]),
+ a.internalPubkey,
+ ].concat(path),
+ );
+ return [a.redeem.output, controlBock];
+ }
+ if (a.signature) return [a.signature];
+ });
+ // extended validation
+ if (opts.validate) {
+ let pubkey = buffer_1.Buffer.from([]);
+ if (a.address) {
+ if (network && network.bech32 !== _address().prefix)
+ throw new TypeError('Invalid prefix or Network mismatch');
+ if (_address().version !== TAPROOT_WITNESS_VERSION)
+ throw new TypeError('Invalid address version');
+ if (_address().data.length !== 32)
+ throw new TypeError('Invalid address data');
+ pubkey = _address().data;
+ }
+ if (a.pubkey) {
+ if (pubkey.length > 0 && !pubkey.equals(a.pubkey))
+ throw new TypeError('Pubkey mismatch');
+ else pubkey = a.pubkey;
+ }
+ if (a.output) {
+ if (
+ a.output.length !== 34 ||
+ a.output[0] !== OPS.OP_1 ||
+ a.output[1] !== 0x20
+ )
+ throw new TypeError('Output is invalid');
+ if (pubkey.length > 0 && !pubkey.equals(a.output.slice(2)))
+ throw new TypeError('Pubkey mismatch');
+ else pubkey = a.output.slice(2);
+ }
+ if (a.internalPubkey) {
+ const tweakedKey = (0, bip341_1.tweakKey)(a.internalPubkey, o.hash);
+ if (pubkey.length > 0 && !pubkey.equals(tweakedKey.x))
+ throw new TypeError('Pubkey mismatch');
+ else pubkey = tweakedKey.x;
+ }
+ if (pubkey && pubkey.length) {
+ if (!(0, ecc_lib_1.getEccLib)().isXOnlyPoint(pubkey))
+ throw new TypeError('Invalid pubkey for p2tr');
+ }
+ const hashTree = _hashTree();
+ if (a.hash && hashTree) {
+ if (!a.hash.equals(hashTree.hash)) throw new TypeError('Hash mismatch');
+ }
+ if (a.redeem && a.redeem.output && hashTree) {
+ const leafHash = (0, bip341_1.tapleafHash)({
+ output: a.redeem.output,
+ version: o.redeemVersion,
+ });
+ if (!(0, bip341_1.findScriptPath)(hashTree, leafHash))
+ throw new TypeError('Redeem script not in tree');
+ }
+ const witness = _witness();
+ // compare the provided redeem data with the one computed from witness
+ if (a.redeem && o.redeem) {
+ if (a.redeem.redeemVersion) {
+ if (a.redeem.redeemVersion !== o.redeem.redeemVersion)
+ throw new TypeError('Redeem.redeemVersion and witness mismatch');
+ }
+ if (a.redeem.output) {
+ if (bscript.decompile(a.redeem.output).length === 0)
+ throw new TypeError('Redeem.output is invalid');
+ // output redeem is constructed from the witness
+ if (o.redeem.output && !a.redeem.output.equals(o.redeem.output))
+ throw new TypeError('Redeem.output and witness mismatch');
+ }
+ if (a.redeem.witness) {
+ if (
+ o.redeem.witness &&
+ !stacksEqual(a.redeem.witness, o.redeem.witness)
+ )
+ throw new TypeError('Redeem.witness and witness mismatch');
+ }
+ }
+ if (witness && witness.length) {
+ if (witness.length === 1) {
+ // key spending
+ if (a.signature && !a.signature.equals(witness[0]))
+ throw new TypeError('Signature mismatch');
+ } else {
+ // script path spending
+ const controlBlock = witness[witness.length - 1];
+ if (controlBlock.length < 33)
+ throw new TypeError(
+ `The control-block length is too small. Got ${
+ controlBlock.length
+ }, expected min 33.`,
+ );
+ if ((controlBlock.length - 33) % 32 !== 0)
+ throw new TypeError(
+ `The control-block length of ${controlBlock.length} is incorrect!`,
+ );
+ const m = (controlBlock.length - 33) / 32;
+ if (m > 128)
+ throw new TypeError(
+ `The script path is too long. Got ${m}, expected max 128.`,
+ );
+ const internalPubkey = controlBlock.slice(1, 33);
+ if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey))
+ throw new TypeError('Internal pubkey mismatch');
+ if (!(0, ecc_lib_1.getEccLib)().isXOnlyPoint(internalPubkey))
+ throw new TypeError('Invalid internalPubkey for p2tr witness');
+ const leafVersion = controlBlock[0] & types_1.TAPLEAF_VERSION_MASK;
+ const script = witness[witness.length - 2];
+ const leafHash = (0, bip341_1.tapleafHash)({
+ output: script,
+ version: leafVersion,
+ });
+ const hash = (0, bip341_1.rootHashFromPath)(controlBlock, leafHash);
+ const outputKey = (0, bip341_1.tweakKey)(internalPubkey, hash);
+ if (!outputKey)
+ // todo: needs test data
+ throw new TypeError('Invalid outputKey for p2tr witness');
+ if (pubkey.length && !pubkey.equals(outputKey.x))
+ throw new TypeError('Pubkey mismatch for p2tr witness');
+ if (outputKey.parity !== (controlBlock[0] & 1))
+ throw new Error('Incorrect parity');
+ }
+ }
+ }
+ return Object.assign(o, a);
+}
+exports.p2tr = p2tr;
+function stacksEqual(a, b) {
+ if (a.length !== b.length) return false;
+ return a.every((x, i) => {
+ return x.equals(b[i]);
+ });
+}
diff --git a/src/psbt.d.ts b/src/psbt.d.ts
index 8603a6955..4c622b31f 100644
--- a/src/psbt.d.ts
+++ b/src/psbt.d.ts
@@ -80,7 +80,10 @@ export declare class Psbt {
getFeeRate(): number;
getFee(): number;
finalizeAllInputs(): this;
- finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc): this;
+ finalizeInput(inputIndex: number, finalScriptsFunc?: FinalScriptsFunc | FinalTaprootScriptsFunc): this;
+ finalizeTaprootInput(inputIndex: number, tapLeafHashToFinalize?: Buffer, finalScriptsFunc?: FinalTaprootScriptsFunc): this;
+ private _finalizeInput;
+ private _finalizeTaprootInput;
getInputType(inputIndex: number): AllScriptType;
inputHasPubkey(inputIndex: number, pubkey: Buffer): boolean;
inputHasHDKey(inputIndex: number, root: HDSigner): boolean;
@@ -88,6 +91,8 @@ export declare class Psbt {
outputHasHDKey(outputIndex: number, root: HDSigner): boolean;
validateSignaturesOfAllInputs(validator: ValidateSigFunction): boolean;
validateSignaturesOfInput(inputIndex: number, validator: ValidateSigFunction, pubkey?: Buffer): boolean;
+ private _validateSignaturesOfInput;
+ private validateSignaturesOfTaprootInput;
signAllInputsHD(hdKeyPair: HDSigner, sighashTypes?: number[]): this;
signAllInputsHDAsync(hdKeyPair: HDSigner | HDSignerAsync, sighashTypes?: number[]): Promise;
signInputHD(inputIndex: number, hdKeyPair: HDSigner, sighashTypes?: number[]): this;
@@ -95,7 +100,14 @@ export declare class Psbt {
signAllInputs(keyPair: Signer, sighashTypes?: number[]): this;
signAllInputsAsync(keyPair: Signer | SignerAsync, sighashTypes?: number[]): Promise;
signInput(inputIndex: number, keyPair: Signer, sighashTypes?: number[]): this;
+ signTaprootInput(inputIndex: number, keyPair: Signer, tapLeafHashToSign?: Buffer, sighashTypes?: number[]): this;
+ private _signInput;
+ private _signTaprootInput;
signInputAsync(inputIndex: number, keyPair: Signer | SignerAsync, sighashTypes?: number[]): Promise;
+ signTaprootInputAsync(inputIndex: number, keyPair: Signer | SignerAsync, tapLeafHash?: Buffer, sighashTypes?: number[]): Promise;
+ private _signInputAsync;
+ private _signTaprootInputAsync;
+ private checkTaprootHashesForSig;
toBuffer(): Buffer;
toHex(): string;
toBase64(): string;
@@ -155,12 +167,14 @@ export interface Signer {
publicKey: Buffer;
network?: any;
sign(hash: Buffer, lowR?: boolean): Buffer;
+ signSchnorr?(hash: Buffer): Buffer;
getPublicKey?(): Buffer;
}
export interface SignerAsync {
publicKey: Buffer;
network?: any;
sign(hash: Buffer, lowR?: boolean): Promise;
+ signSchnorr?(hash: Buffer): Promise;
getPublicKey?(): Buffer;
}
/**
@@ -178,5 +192,10 @@ isP2WSH: boolean) => {
finalScriptSig: Buffer | undefined;
finalScriptWitness: Buffer | undefined;
};
+declare type FinalTaprootScriptsFunc = (inputIndex: number, // Which input is it?
+input: PsbtInput, // The PSBT input contents
+tapLeafHashToFinalize?: Buffer) => {
+ finalScriptWitness: Buffer | undefined;
+};
declare type AllScriptType = 'witnesspubkeyhash' | 'pubkeyhash' | 'multisig' | 'pubkey' | 'nonstandard' | 'p2sh-witnesspubkeyhash' | 'p2sh-pubkeyhash' | 'p2sh-multisig' | 'p2sh-pubkey' | 'p2sh-nonstandard' | 'p2wsh-pubkeyhash' | 'p2wsh-multisig' | 'p2wsh-pubkey' | 'p2wsh-nonstandard' | 'p2sh-p2wsh-pubkeyhash' | 'p2sh-p2wsh-multisig' | 'p2sh-p2wsh-pubkey' | 'p2sh-p2wsh-nonstandard';
export {};
diff --git a/src/psbt.js b/src/psbt.js
index 616219580..2f2e94a96 100644
--- a/src/psbt.js
+++ b/src/psbt.js
@@ -6,11 +6,13 @@ const varuint = require('bip174/src/lib/converter/varint');
const utils_1 = require('bip174/src/lib/utils');
const address_1 = require('./address');
const bufferutils_1 = require('./bufferutils');
-const crypto_1 = require('./crypto');
const networks_1 = require('./networks');
const payments = require('./payments');
+const bip341_1 = require('./payments/bip341');
const bscript = require('./script');
const transaction_1 = require('./transaction');
+const bip371_1 = require('./psbt/bip371');
+const psbtutils_1 = require('./psbt/psbtutils');
/**
* These are the default arguments for a Psbt instance.
*/
@@ -198,6 +200,7 @@ class Psbt {
`Requires single object with at least [hash] and [index]`,
);
}
+ (0, bip371_1.checkTaprootInputFields)(inputData, inputData, 'addInput');
checkInputsForPartialSig(this.data.inputs, 'addInput');
if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript);
const c = this.__CACHE;
@@ -237,6 +240,7 @@ class Psbt {
const script = (0, address_1.toOutputScript)(address, network);
outputData = Object.assign(outputData, { script });
}
+ (0, bip371_1.checkTaprootOutputFields)(outputData, outputData, 'addOutput');
const c = this.__CACHE;
this.data.addOutput(outputData);
c.__FEE = undefined;
@@ -271,8 +275,33 @@ class Psbt {
range(this.data.inputs.length).forEach(idx => this.finalizeInput(idx));
return this;
}
- finalizeInput(inputIndex, finalScriptsFunc = getFinalScripts) {
+ finalizeInput(inputIndex, finalScriptsFunc) {
const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
+ if ((0, bip371_1.isTaprootInput)(input))
+ return this._finalizeTaprootInput(
+ inputIndex,
+ input,
+ undefined,
+ finalScriptsFunc,
+ );
+ return this._finalizeInput(inputIndex, input, finalScriptsFunc);
+ }
+ finalizeTaprootInput(
+ inputIndex,
+ tapLeafHashToFinalize,
+ finalScriptsFunc = bip371_1.tapScriptFinalizer,
+ ) {
+ const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
+ if ((0, bip371_1.isTaprootInput)(input))
+ return this._finalizeTaprootInput(
+ inputIndex,
+ input,
+ tapLeafHashToFinalize,
+ finalScriptsFunc,
+ );
+ throw new Error(`Cannot finalize input #${inputIndex}. Not Taproot.`);
+ }
+ _finalizeInput(inputIndex, input, finalScriptsFunc = getFinalScripts) {
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
inputIndex,
input,
@@ -296,6 +325,37 @@ class Psbt {
this.data.clearFinalizedInput(inputIndex);
return this;
}
+ _finalizeTaprootInput(
+ inputIndex,
+ input,
+ tapLeafHashToFinalize,
+ finalScriptsFunc = bip371_1.tapScriptFinalizer,
+ ) {
+ if (!input.witnessUtxo)
+ throw new Error(
+ `Cannot finalize input #${inputIndex}. Missing withness utxo.`,
+ );
+ // Check key spend first. Increased privacy and reduced block space.
+ if (input.tapKeySig) {
+ const payment = payments.p2tr({
+ output: input.witnessUtxo.script,
+ signature: input.tapKeySig,
+ });
+ const finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)(
+ payment.witness,
+ );
+ this.data.updateInput(inputIndex, { finalScriptWitness });
+ } else {
+ const { finalScriptWitness } = finalScriptsFunc(
+ inputIndex,
+ input,
+ tapLeafHashToFinalize,
+ );
+ this.data.updateInput(inputIndex, { finalScriptWitness });
+ }
+ this.data.clearFinalizedInput(inputIndex);
+ return this;
+ }
getInputType(inputIndex) {
const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
const script = getScriptFromUtxo(inputIndex, input, this.__CACHE);
@@ -341,6 +401,16 @@ class Psbt {
return results.reduce((final, res) => res === true && final, true);
}
validateSignaturesOfInput(inputIndex, validator, pubkey) {
+ const input = this.data.inputs[inputIndex];
+ if ((0, bip371_1.isTaprootInput)(input))
+ return this.validateSignaturesOfTaprootInput(
+ inputIndex,
+ validator,
+ pubkey,
+ );
+ return this._validateSignaturesOfInput(inputIndex, validator, pubkey);
+ }
+ _validateSignaturesOfInput(inputIndex, validator, pubkey) {
const input = this.data.inputs[inputIndex];
const partialSig = (input || {}).partialSig;
if (!input || !partialSig || partialSig.length < 1)
@@ -374,6 +444,54 @@ class Psbt {
}
return results.every(res => res === true);
}
+ validateSignaturesOfTaprootInput(inputIndex, validator, pubkey) {
+ const input = this.data.inputs[inputIndex];
+ const tapKeySig = (input || {}).tapKeySig;
+ const tapScriptSig = (input || {}).tapScriptSig;
+ if (!input && !tapKeySig && !(tapScriptSig && !tapScriptSig.length))
+ throw new Error('No signatures to validate');
+ if (typeof validator !== 'function')
+ throw new Error('Need validator function to validate signatures');
+ pubkey = pubkey && (0, bip371_1.toXOnly)(pubkey);
+ const allHashses = pubkey
+ ? getTaprootHashesForSig(
+ inputIndex,
+ input,
+ this.data.inputs,
+ pubkey,
+ this.__CACHE,
+ )
+ : getAllTaprootHashesForSig(
+ inputIndex,
+ input,
+ this.data.inputs,
+ this.__CACHE,
+ );
+ if (!allHashses.length) throw new Error('No signatures for this pubkey');
+ const tapKeyHash = allHashses.find(h => !!h.leafHash);
+ if (tapKeySig && tapKeyHash) {
+ const isValidTapkeySig = validator(
+ tapKeyHash.pubkey,
+ tapKeyHash.hash,
+ tapKeySig,
+ );
+ if (!isValidTapkeySig) return false;
+ }
+ if (tapScriptSig) {
+ for (const tapSig of tapScriptSig) {
+ const tapSigHash = allHashses.find(h => tapSig.pubkey.equals(h.pubkey));
+ if (tapSigHash) {
+ const isValidTapScriptSig = validator(
+ tapSig.pubkey,
+ tapSigHash.hash,
+ tapSig.signature,
+ );
+ if (!isValidTapScriptSig) return false;
+ }
+ }
+ }
+ return true;
+ }
signAllInputsHD(
hdKeyPair,
sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
@@ -457,10 +575,7 @@ class Psbt {
.catch(reject);
});
}
- signAllInputs(
- keyPair,
- sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
- ) {
+ signAllInputs(keyPair, sighashTypes) {
if (!keyPair || !keyPair.publicKey)
throw new Error('Need Signer to sign input');
// TODO: Add a pubkey/pubkeyhash cache to each input
@@ -480,10 +595,7 @@ class Psbt {
}
return this;
}
- signAllInputsAsync(
- keyPair,
- sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
- ) {
+ signAllInputsAsync(keyPair, sighashTypes) {
return new Promise((resolve, reject) => {
if (!keyPair || !keyPair.publicKey)
return reject(new Error('Need Signer to sign input'));
@@ -512,13 +624,40 @@ class Psbt {
});
});
}
- signInput(
+ signInput(inputIndex, keyPair, sighashTypes) {
+ if (!keyPair || !keyPair.publicKey)
+ throw new Error('Need Signer to sign input');
+ const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
+ if ((0, bip371_1.isTaprootInput)(input)) {
+ return this._signTaprootInput(
+ inputIndex,
+ input,
+ keyPair,
+ undefined,
+ sighashTypes,
+ );
+ }
+ return this._signInput(inputIndex, keyPair, sighashTypes);
+ }
+ signTaprootInput(inputIndex, keyPair, tapLeafHashToSign, sighashTypes) {
+ if (!keyPair || !keyPair.publicKey)
+ throw new Error('Need Signer to sign input');
+ const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
+ if ((0, bip371_1.isTaprootInput)(input))
+ return this._signTaprootInput(
+ inputIndex,
+ input,
+ keyPair,
+ tapLeafHashToSign,
+ sighashTypes,
+ );
+ throw new Error(`Input #${inputIndex} is not of type Taproot.`);
+ }
+ _signInput(
inputIndex,
keyPair,
sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
) {
- if (!keyPair || !keyPair.publicKey)
- throw new Error('Need Signer to sign input');
const { hash, sighashType } = getHashAndSighashType(
this.data.inputs,
inputIndex,
@@ -535,32 +674,182 @@ class Psbt {
this.data.updateInput(inputIndex, { partialSig });
return this;
}
- signInputAsync(
+ _signTaprootInput(
inputIndex,
+ input,
keyPair,
- sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
+ tapLeafHashToSign,
+ allowedSighashTypes = [transaction_1.Transaction.SIGHASH_DEFAULT],
) {
+ const hashesForSig = this.checkTaprootHashesForSig(
+ inputIndex,
+ input,
+ keyPair,
+ tapLeafHashToSign,
+ allowedSighashTypes,
+ );
+ const tapKeySig = hashesForSig
+ .filter(h => !h.leafHash)
+ .map(h =>
+ (0, bip371_1.serializeTaprootSignature)(
+ keyPair.signSchnorr(h.hash),
+ input.sighashType,
+ ),
+ )[0];
+ const tapScriptSig = hashesForSig
+ .filter(h => !!h.leafHash)
+ .map(h => ({
+ pubkey: (0, bip371_1.toXOnly)(keyPair.publicKey),
+ signature: (0, bip371_1.serializeTaprootSignature)(
+ keyPair.signSchnorr(h.hash),
+ input.sighashType,
+ ),
+ leafHash: h.leafHash,
+ }));
+ if (tapKeySig) {
+ this.data.updateInput(inputIndex, { tapKeySig });
+ }
+ if (tapScriptSig.length) {
+ this.data.updateInput(inputIndex, { tapScriptSig });
+ }
+ return this;
+ }
+ signInputAsync(inputIndex, keyPair, sighashTypes) {
return Promise.resolve().then(() => {
if (!keyPair || !keyPair.publicKey)
throw new Error('Need Signer to sign input');
- const { hash, sighashType } = getHashAndSighashType(
- this.data.inputs,
- inputIndex,
- keyPair.publicKey,
- this.__CACHE,
- sighashTypes,
- );
- return Promise.resolve(keyPair.sign(hash)).then(signature => {
- const partialSig = [
- {
- pubkey: keyPair.publicKey,
- signature: bscript.signature.encode(signature, sighashType),
+ const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
+ if ((0, bip371_1.isTaprootInput)(input))
+ return this._signTaprootInputAsync(
+ inputIndex,
+ input,
+ keyPair,
+ undefined,
+ sighashTypes,
+ );
+ return this._signInputAsync(inputIndex, keyPair, sighashTypes);
+ });
+ }
+ signTaprootInputAsync(inputIndex, keyPair, tapLeafHash, sighashTypes) {
+ return Promise.resolve().then(() => {
+ if (!keyPair || !keyPair.publicKey)
+ throw new Error('Need Signer to sign input');
+ const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
+ if ((0, bip371_1.isTaprootInput)(input))
+ return this._signTaprootInputAsync(
+ inputIndex,
+ input,
+ keyPair,
+ tapLeafHash,
+ sighashTypes,
+ );
+ throw new Error(`Input #${inputIndex} is not of type Taproot.`);
+ });
+ }
+ _signInputAsync(
+ inputIndex,
+ keyPair,
+ sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
+ ) {
+ const { hash, sighashType } = getHashAndSighashType(
+ this.data.inputs,
+ inputIndex,
+ keyPair.publicKey,
+ this.__CACHE,
+ sighashTypes,
+ );
+ return Promise.resolve(keyPair.sign(hash)).then(signature => {
+ const partialSig = [
+ {
+ pubkey: keyPair.publicKey,
+ signature: bscript.signature.encode(signature, sighashType),
+ },
+ ];
+ this.data.updateInput(inputIndex, { partialSig });
+ });
+ }
+ async _signTaprootInputAsync(
+ inputIndex,
+ input,
+ keyPair,
+ tapLeafHash,
+ sighashTypes = [transaction_1.Transaction.SIGHASH_DEFAULT],
+ ) {
+ const hashesForSig = this.checkTaprootHashesForSig(
+ inputIndex,
+ input,
+ keyPair,
+ tapLeafHash,
+ sighashTypes,
+ );
+ const signaturePromises = [];
+ const tapKeyHash = hashesForSig.filter(h => !h.leafHash)[0];
+ if (tapKeyHash) {
+ const tapKeySigPromise = Promise.resolve(
+ keyPair.signSchnorr(tapKeyHash.hash),
+ ).then(sig => {
+ return {
+ tapKeySig: (0, bip371_1.serializeTaprootSignature)(
+ sig,
+ input.sighashType,
+ ),
+ };
+ });
+ signaturePromises.push(tapKeySigPromise);
+ }
+ const tapScriptHashes = hashesForSig.filter(h => !!h.leafHash);
+ if (tapScriptHashes.length) {
+ const tapScriptSigPromises = tapScriptHashes.map(tsh => {
+ return Promise.resolve(keyPair.signSchnorr(tsh.hash)).then(
+ signature => {
+ const tapScriptSig = [
+ {
+ pubkey: (0, bip371_1.toXOnly)(keyPair.publicKey),
+ signature: (0, bip371_1.serializeTaprootSignature)(
+ signature,
+ input.sighashType,
+ ),
+ leafHash: tsh.leafHash,
+ },
+ ];
+ return { tapScriptSig };
},
- ];
- this.data.updateInput(inputIndex, { partialSig });
+ );
});
+ signaturePromises.push(...tapScriptSigPromises);
+ }
+ return Promise.all(signaturePromises).then(results => {
+ results.forEach(v => this.data.updateInput(inputIndex, v));
});
}
+ checkTaprootHashesForSig(
+ inputIndex,
+ input,
+ keyPair,
+ tapLeafHashToSign,
+ allowedSighashTypes,
+ ) {
+ if (typeof keyPair.signSchnorr !== 'function')
+ throw new Error(
+ `Need Schnorr Signer to sign taproot input #${inputIndex}.`,
+ );
+ const hashesForSig = getTaprootHashesForSig(
+ inputIndex,
+ input,
+ this.data.inputs,
+ keyPair.publicKey,
+ this.__CACHE,
+ tapLeafHashToSign,
+ allowedSighashTypes,
+ );
+ if (!hashesForSig || !hashesForSig.length)
+ throw new Error(
+ `Can not sign for input #${inputIndex} with the key ${keyPair.publicKey.toString(
+ 'hex',
+ )}`,
+ );
+ return hashesForSig;
+ }
toBuffer() {
checkCache(this.__CACHE);
return this.data.toBuffer();
@@ -579,6 +868,11 @@ class Psbt {
}
updateInput(inputIndex, updateData) {
if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript);
+ (0, bip371_1.checkTaprootInputFields)(
+ this.data.inputs[inputIndex],
+ updateData,
+ 'updateInput',
+ );
this.data.updateInput(inputIndex, updateData);
if (updateData.nonWitnessUtxo) {
addNonWitnessTxCache(
@@ -590,6 +884,12 @@ class Psbt {
return this;
}
updateOutput(outputIndex, updateData) {
+ const outputData = this.data.outputs[outputIndex];
+ (0, bip371_1.checkTaprootOutputFields)(
+ outputData,
+ updateData,
+ 'updateOutput',
+ );
this.data.updateOutput(outputIndex, updateData);
return this;
}
@@ -703,22 +1003,6 @@ function hasSigs(neededSigs, partialSig, pubkeys) {
function isFinalized(input) {
return !!input.finalScriptSig || !!input.finalScriptWitness;
}
-function isPaymentFactory(payment) {
- return script => {
- try {
- payment({ output: script });
- return true;
- } catch (err) {
- return false;
- }
- };
-}
-const isP2MS = isPaymentFactory(payments.p2ms);
-const isP2PK = isPaymentFactory(payments.p2pk);
-const isP2PKH = isPaymentFactory(payments.p2pkh);
-const isP2WPKH = isPaymentFactory(payments.p2wpkh);
-const isP2WSHScript = isPaymentFactory(payments.p2wsh);
-const isP2SHScript = isPaymentFactory(payments.p2sh);
function bip32DerivationIsMine(root) {
return d => {
if (!d.masterFingerprint.equals(root.fingerprint)) return false;
@@ -752,37 +1036,11 @@ function checkFees(psbt, cache, opts) {
}
function checkInputsForPartialSig(inputs, action) {
inputs.forEach(input => {
- let throws = false;
- let pSigs = [];
- if ((input.partialSig || []).length === 0) {
- if (!input.finalScriptSig && !input.finalScriptWitness) return;
- pSigs = getPsigsFromInputFinalScripts(input);
- } else {
- pSigs = input.partialSig;
- }
- pSigs.forEach(pSig => {
- const { hashType } = bscript.signature.decode(pSig.signature);
- const whitelist = [];
- const isAnyoneCanPay =
- hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY;
- if (isAnyoneCanPay) whitelist.push('addInput');
- const hashMod = hashType & 0x1f;
- switch (hashMod) {
- case transaction_1.Transaction.SIGHASH_ALL:
- break;
- case transaction_1.Transaction.SIGHASH_SINGLE:
- case transaction_1.Transaction.SIGHASH_NONE:
- whitelist.push('addOutput');
- whitelist.push('setInputSequence');
- break;
- }
- if (whitelist.indexOf(action) === -1) {
- throws = true;
- }
- });
- if (throws) {
+ const throws = (0, bip371_1.isTaprootInput)(input)
+ ? (0, bip371_1.checkTaprootInputForSigs)(input, action)
+ : (0, psbtutils_1.checkInputForSig)(input, action);
+ if (throws)
throw new Error('Can not modify transaction, signatures exist.');
- }
});
}
function checkPartialSigSighashes(input) {
@@ -796,7 +1054,7 @@ function checkPartialSigSighashes(input) {
});
}
function checkScriptForPubkey(pubkey, script, action) {
- if (!pubkeyInScript(pubkey, script)) {
+ if (!(0, psbtutils_1.pubkeyInScript)(pubkey, script)) {
throw new Error(
`Can not ${action} for this input with the key ${pubkey.toString('hex')}`,
);
@@ -890,9 +1148,13 @@ function prepareFinalScripts(
const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment });
if (isSegwit) {
if (p2wsh) {
- finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness);
+ finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)(
+ p2wsh.witness,
+ );
} else {
- finalScriptWitness = witnessStackToScriptWitness(payment.witness);
+ finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)(
+ payment.witness,
+ );
}
if (p2sh) {
finalScriptSig = p2sh.input;
@@ -934,13 +1196,7 @@ function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) {
const unsignedTx = cache.__TX;
const sighashType =
input.sighashType || transaction_1.Transaction.SIGHASH_ALL;
- if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) {
- const str = sighashTypeToString(sighashType);
- throw new Error(
- `Sighash type is not allowed. Retry the sign method passing the ` +
- `sighashTypes array of whitelisted types. Sighash type: ${str}`,
- );
- }
+ checkSighashTypeAllowed(sighashType, sighashTypes);
let hash;
let prevout;
if (input.nonWitnessUtxo) {
@@ -978,7 +1234,7 @@ function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) {
prevout.value,
sighashType,
);
- } else if (isP2WPKH(meaningfulScript)) {
+ } else if ((0, psbtutils_1.isP2WPKH)(meaningfulScript)) {
// P2WPKH uses the P2PKH template for prevoutScript when signing
const signingScript = payments.p2pkh({ hash: meaningfulScript.slice(2) })
.output;
@@ -1020,6 +1276,89 @@ function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) {
hash,
};
}
+function getAllTaprootHashesForSig(inputIndex, input, inputs, cache) {
+ const allPublicKeys = [];
+ if (input.tapInternalKey) {
+ const outputKey = (0, bip371_1.tweakInternalPubKey)(inputIndex, input);
+ allPublicKeys.push(outputKey);
+ }
+ if (input.tapScriptSig) {
+ const tapScriptPubkeys = input.tapScriptSig.map(tss => tss.pubkey);
+ allPublicKeys.push(...tapScriptPubkeys);
+ }
+ const allHashes = allPublicKeys.map(pubicKey =>
+ getTaprootHashesForSig(inputIndex, input, inputs, pubicKey, cache),
+ );
+ return allHashes.flat();
+}
+function getTaprootHashesForSig(
+ inputIndex,
+ input,
+ inputs,
+ pubkey,
+ cache,
+ tapLeafHashToSign,
+ allowedSighashTypes,
+) {
+ const unsignedTx = cache.__TX;
+ const sighashType =
+ input.sighashType || transaction_1.Transaction.SIGHASH_DEFAULT;
+ checkSighashTypeAllowed(sighashType, allowedSighashTypes);
+ const prevOuts = inputs.map((i, index) =>
+ getScriptAndAmountFromUtxo(index, i, cache),
+ );
+ const signingScripts = prevOuts.map(o => o.script);
+ const values = prevOuts.map(o => o.value);
+ const hashes = [];
+ if (input.tapInternalKey && !tapLeafHashToSign) {
+ const outputKey = (0, bip371_1.tweakInternalPubKey)(inputIndex, input);
+ if ((0, bip371_1.toXOnly)(pubkey).equals(outputKey)) {
+ const tapKeyHash = unsignedTx.hashForWitnessV1(
+ inputIndex,
+ signingScripts,
+ values,
+ sighashType,
+ );
+ hashes.push({ pubkey, hash: tapKeyHash });
+ }
+ }
+ const tapLeafHashes = (input.tapLeafScript || [])
+ .filter(tapLeaf => (0, psbtutils_1.pubkeyInScript)(pubkey, tapLeaf.script))
+ .map(tapLeaf => {
+ const hash = (0, bip341_1.tapleafHash)({
+ output: tapLeaf.script,
+ version: tapLeaf.leafVersion,
+ });
+ return Object.assign({ hash }, tapLeaf);
+ })
+ .filter(
+ tapLeaf => !tapLeafHashToSign || tapLeafHashToSign.equals(tapLeaf.hash),
+ )
+ .map(tapLeaf => {
+ const tapScriptHash = unsignedTx.hashForWitnessV1(
+ inputIndex,
+ signingScripts,
+ values,
+ transaction_1.Transaction.SIGHASH_DEFAULT,
+ tapLeaf.hash,
+ );
+ return {
+ pubkey,
+ hash: tapScriptHash,
+ leafHash: tapLeaf.hash,
+ };
+ });
+ return hashes.concat(tapLeafHashes);
+}
+function checkSighashTypeAllowed(sighashType, sighashTypes) {
+ if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) {
+ const str = sighashTypeToString(sighashType);
+ throw new Error(
+ `Sighash type is not allowed. Retry the sign method passing the ` +
+ `sighashTypes array of whitelisted types. Sighash type: ${str}`,
+ );
+ }
+}
function getPayment(script, scriptType, partialSig) {
let payment;
switch (scriptType) {
@@ -1053,20 +1392,6 @@ function getPayment(script, scriptType, partialSig) {
}
return payment;
}
-function getPsigsFromInputFinalScripts(input) {
- const scriptItems = !input.finalScriptSig
- ? []
- : bscript.decompile(input.finalScriptSig) || [];
- const witnessItems = !input.finalScriptWitness
- ? []
- : bscript.decompile(input.finalScriptWitness) || [];
- return scriptItems
- .concat(witnessItems)
- .filter(item => {
- return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item);
- })
- .map(sig => ({ signature: sig }));
-}
function getScriptFromInput(inputIndex, input, cache) {
const unsignedTx = cache.__TX;
const res = {
@@ -1094,7 +1419,7 @@ function getScriptFromInput(inputIndex, input, cache) {
res.script = input.witnessUtxo.script;
}
}
- if (input.witnessScript || isP2WPKH(res.script)) {
+ if (input.witnessScript || (0, psbtutils_1.isP2WPKH)(res.script)) {
res.isSegwit = true;
}
return res;
@@ -1184,28 +1509,6 @@ function sighashTypeToString(sighashType) {
}
return text;
}
-function witnessStackToScriptWitness(witness) {
- let buffer = Buffer.allocUnsafe(0);
- function writeSlice(slice) {
- buffer = Buffer.concat([buffer, Buffer.from(slice)]);
- }
- function writeVarInt(i) {
- const currentLen = buffer.length;
- const varintLen = varuint.encodingLength(i);
- buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
- varuint.encode(i, buffer, currentLen);
- }
- function writeVarSlice(slice) {
- writeVarInt(slice.length);
- writeSlice(slice);
- }
- function writeVector(vector) {
- writeVarInt(vector.length);
- vector.forEach(writeVarSlice);
- }
- writeVector(witness);
- return buffer;
-}
function addNonWitnessTxCache(cache, input, inputIndex) {
cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo;
const tx = transaction_1.Transaction.fromBuffer(input.nonWitnessUtxo);
@@ -1268,15 +1571,23 @@ function nonWitnessUtxoTxFromCache(cache, input, inputIndex) {
return c[inputIndex];
}
function getScriptFromUtxo(inputIndex, input, cache) {
+ const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache);
+ return script;
+}
+function getScriptAndAmountFromUtxo(inputIndex, input, cache) {
if (input.witnessUtxo !== undefined) {
- return input.witnessUtxo.script;
+ return {
+ script: input.witnessUtxo.script,
+ value: input.witnessUtxo.value,
+ };
} else if (input.nonWitnessUtxo !== undefined) {
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
cache,
input,
inputIndex,
);
- return nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
+ const o = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index];
+ return { script: o.script, value: o.value };
} else {
throw new Error("Can't find pubkey in input without Utxo data");
}
@@ -1290,7 +1601,7 @@ function pubkeyInInput(pubkey, input, inputIndex, cache) {
input.redeemScript,
input.witnessScript,
);
- return pubkeyInScript(pubkey, meaningfulScript);
+ return (0, psbtutils_1.pubkeyInScript)(pubkey, meaningfulScript);
}
function pubkeyInOutput(pubkey, output, outputIndex, cache) {
const script = cache.__TX.outs[outputIndex].script;
@@ -1301,7 +1612,7 @@ function pubkeyInOutput(pubkey, output, outputIndex, cache) {
output.redeemScript,
output.witnessScript,
);
- return pubkeyInScript(pubkey, meaningfulScript);
+ return (0, psbtutils_1.pubkeyInScript)(pubkey, meaningfulScript);
}
function redeemFromFinalScriptSig(finalScript) {
if (!finalScript) return;
@@ -1349,9 +1660,10 @@ function getMeaningfulScript(
redeemScript,
witnessScript,
) {
- const isP2SH = isP2SHScript(script);
- const isP2SHP2WSH = isP2SH && redeemScript && isP2WSHScript(redeemScript);
- const isP2WSH = isP2WSHScript(script);
+ const isP2SH = (0, psbtutils_1.isP2SHScript)(script);
+ const isP2SHP2WSH =
+ isP2SH && redeemScript && (0, psbtutils_1.isP2WSHScript)(redeemScript);
+ const isP2WSH = (0, psbtutils_1.isP2WSHScript)(script);
if (isP2SH && redeemScript === undefined)
throw new Error('scriptPubkey is P2SH but redeemScript missing');
if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined)
@@ -1386,24 +1698,18 @@ function getMeaningfulScript(
};
}
function checkInvalidP2WSH(script) {
- if (isP2WPKH(script) || isP2SHScript(script)) {
+ if (
+ (0, psbtutils_1.isP2WPKH)(script) ||
+ (0, psbtutils_1.isP2SHScript)(script)
+ ) {
throw new Error('P2WPKH or P2SH can not be contained within P2WSH');
}
}
-function pubkeyInScript(pubkey, script) {
- const pubkeyHash = (0, crypto_1.hash160)(pubkey);
- const decompiled = bscript.decompile(script);
- if (decompiled === null) throw new Error('Unknown script error');
- return decompiled.some(element => {
- if (typeof element === 'number') return false;
- return element.equals(pubkey) || element.equals(pubkeyHash);
- });
-}
function classifyScript(script) {
- if (isP2WPKH(script)) return 'witnesspubkeyhash';
- if (isP2PKH(script)) return 'pubkeyhash';
- if (isP2MS(script)) return 'multisig';
- if (isP2PK(script)) return 'pubkey';
+ if ((0, psbtutils_1.isP2WPKH)(script)) return 'witnesspubkeyhash';
+ if ((0, psbtutils_1.isP2PKH)(script)) return 'pubkeyhash';
+ if ((0, psbtutils_1.isP2MS)(script)) return 'multisig';
+ if ((0, psbtutils_1.isP2PK)(script)) return 'pubkey';
return 'nonstandard';
}
function range(n) {
diff --git a/src/psbt/bip371.d.ts b/src/psbt/bip371.d.ts
new file mode 100644
index 000000000..fae6e756b
--- /dev/null
+++ b/src/psbt/bip371.d.ts
@@ -0,0 +1,41 @@
+///
+import { Taptree } from '../types';
+import { PsbtInput, PsbtOutput, TapLeaf } from 'bip174/src/lib/interfaces';
+export declare const toXOnly: (pubKey: Buffer) => Buffer;
+/**
+ * Default tapscript finalizer. It searches for the `tapLeafHashToFinalize` if provided.
+ * Otherwise it will search for the tapleaf that has at least one signature and has the shortest path.
+ * @param inputIndex the position of the PSBT input.
+ * @param input the PSBT input.
+ * @param tapLeafHashToFinalize optional, if provided the finalizer will search for a tapleaf that has this hash
+ * and will try to build the finalScriptWitness.
+ * @returns the finalScriptWitness or throws an exception if no tapleaf found.
+ */
+export declare function tapScriptFinalizer(inputIndex: number, input: PsbtInput, tapLeafHashToFinalize?: Buffer): {
+ finalScriptWitness: Buffer | undefined;
+};
+export declare function serializeTaprootSignature(sig: Buffer, sighashType?: number): Buffer;
+export declare function isTaprootInput(input: PsbtInput): boolean;
+export declare function isTaprootOutput(output: PsbtOutput, script?: Buffer): boolean;
+export declare function checkTaprootInputFields(inputData: PsbtInput, newInputData: PsbtInput, action: string): void;
+export declare function checkTaprootOutputFields(outputData: PsbtOutput, newOutputData: PsbtOutput, action: string): void;
+export declare function tweakInternalPubKey(inputIndex: number, input: PsbtInput): Buffer;
+/**
+ * Convert a binary tree to a BIP371 type list. Each element of the list is (according to BIP371):
+ * One or more tuples representing the depth, leaf version, and script for a leaf in the Taproot tree,
+ * allowing the entire tree to be reconstructed. The tuples must be in depth first search order so that
+ * the tree is correctly reconstructed.
+ * @param tree the binary tap tree
+ * @returns a list of BIP 371 tapleaves
+ */
+export declare function tapTreeToList(tree: Taptree): TapLeaf[];
+/**
+ * Convert a BIP371 TapLeaf list to a TapTree (binary).
+ * @param leaves a list of tapleaves where each element of the list is (according to BIP371):
+ * One or more tuples representing the depth, leaf version, and script for a leaf in the Taproot tree,
+ * allowing the entire tree to be reconstructed. The tuples must be in depth first search order so that
+ * the tree is correctly reconstructed.
+ * @returns the corresponding taptree, or throws an exception if the tree cannot be reconstructed
+ */
+export declare function tapTreeFromList(leaves?: TapLeaf[]): Taptree;
+export declare function checkTaprootInputForSigs(input: PsbtInput, action: string): boolean;
diff --git a/src/psbt/bip371.js b/src/psbt/bip371.js
new file mode 100644
index 000000000..8e76ecda7
--- /dev/null
+++ b/src/psbt/bip371.js
@@ -0,0 +1,349 @@
+'use strict';
+Object.defineProperty(exports, '__esModule', { value: true });
+exports.checkTaprootInputForSigs = exports.tapTreeFromList = exports.tapTreeToList = exports.tweakInternalPubKey = exports.checkTaprootOutputFields = exports.checkTaprootInputFields = exports.isTaprootOutput = exports.isTaprootInput = exports.serializeTaprootSignature = exports.tapScriptFinalizer = exports.toXOnly = void 0;
+const types_1 = require('../types');
+const transaction_1 = require('../transaction');
+const psbtutils_1 = require('./psbtutils');
+const bip341_1 = require('../payments/bip341');
+const payments_1 = require('../payments');
+const psbtutils_2 = require('./psbtutils');
+const toXOnly = pubKey => (pubKey.length === 32 ? pubKey : pubKey.slice(1, 33));
+exports.toXOnly = toXOnly;
+/**
+ * Default tapscript finalizer. It searches for the `tapLeafHashToFinalize` if provided.
+ * Otherwise it will search for the tapleaf that has at least one signature and has the shortest path.
+ * @param inputIndex the position of the PSBT input.
+ * @param input the PSBT input.
+ * @param tapLeafHashToFinalize optional, if provided the finalizer will search for a tapleaf that has this hash
+ * and will try to build the finalScriptWitness.
+ * @returns the finalScriptWitness or throws an exception if no tapleaf found.
+ */
+function tapScriptFinalizer(inputIndex, input, tapLeafHashToFinalize) {
+ const tapLeaf = findTapLeafToFinalize(
+ input,
+ inputIndex,
+ tapLeafHashToFinalize,
+ );
+ try {
+ const sigs = sortSignatures(input, tapLeaf);
+ const witness = sigs.concat(tapLeaf.script).concat(tapLeaf.controlBlock);
+ return {
+ finalScriptWitness: (0, psbtutils_1.witnessStackToScriptWitness)(witness),
+ };
+ } catch (err) {
+ throw new Error(`Can not finalize taproot input #${inputIndex}: ${err}`);
+ }
+}
+exports.tapScriptFinalizer = tapScriptFinalizer;
+function serializeTaprootSignature(sig, sighashType) {
+ const sighashTypeByte = sighashType
+ ? Buffer.from([sighashType])
+ : Buffer.from([]);
+ return Buffer.concat([sig, sighashTypeByte]);
+}
+exports.serializeTaprootSignature = serializeTaprootSignature;
+function isTaprootInput(input) {
+ return (
+ input &&
+ !!(
+ input.tapInternalKey ||
+ input.tapMerkleRoot ||
+ (input.tapLeafScript && input.tapLeafScript.length) ||
+ (input.tapBip32Derivation && input.tapBip32Derivation.length) ||
+ (input.witnessUtxo && (0, psbtutils_1.isP2TR)(input.witnessUtxo.script))
+ )
+ );
+}
+exports.isTaprootInput = isTaprootInput;
+function isTaprootOutput(output, script) {
+ return (
+ output &&
+ !!(
+ output.tapInternalKey ||
+ output.tapTree ||
+ (output.tapBip32Derivation && output.tapBip32Derivation.length) ||
+ (script && (0, psbtutils_1.isP2TR)(script))
+ )
+ );
+}
+exports.isTaprootOutput = isTaprootOutput;
+function checkTaprootInputFields(inputData, newInputData, action) {
+ checkMixedTaprootAndNonTaprootInputFields(inputData, newInputData, action);
+ checkIfTapLeafInTree(inputData, newInputData, action);
+}
+exports.checkTaprootInputFields = checkTaprootInputFields;
+function checkTaprootOutputFields(outputData, newOutputData, action) {
+ checkMixedTaprootAndNonTaprootOutputFields(outputData, newOutputData, action);
+ checkTaprootScriptPubkey(outputData, newOutputData);
+}
+exports.checkTaprootOutputFields = checkTaprootOutputFields;
+function checkTaprootScriptPubkey(outputData, newOutputData) {
+ if (!newOutputData.tapTree && !newOutputData.tapInternalKey) return;
+ const tapInternalKey =
+ newOutputData.tapInternalKey || outputData.tapInternalKey;
+ const tapTree = newOutputData.tapTree || outputData.tapTree;
+ if (tapInternalKey) {
+ const { script: scriptPubkey } = outputData;
+ const script = getTaprootScripPubkey(tapInternalKey, tapTree);
+ if (scriptPubkey && !scriptPubkey.equals(script))
+ throw new Error('Error adding output. Script or address missmatch.');
+ }
+}
+function getTaprootScripPubkey(tapInternalKey, tapTree) {
+ const scriptTree = tapTree && tapTreeFromList(tapTree.leaves);
+ const { output } = (0, payments_1.p2tr)({
+ internalPubkey: tapInternalKey,
+ scriptTree,
+ });
+ return output;
+}
+function tweakInternalPubKey(inputIndex, input) {
+ const tapInternalKey = input.tapInternalKey;
+ const outputKey =
+ tapInternalKey &&
+ (0, bip341_1.tweakKey)(tapInternalKey, input.tapMerkleRoot);
+ if (!outputKey)
+ throw new Error(
+ `Cannot tweak tap internal key for input #${inputIndex}. Public key: ${tapInternalKey &&
+ tapInternalKey.toString('hex')}`,
+ );
+ return outputKey.x;
+}
+exports.tweakInternalPubKey = tweakInternalPubKey;
+/**
+ * Convert a binary tree to a BIP371 type list. Each element of the list is (according to BIP371):
+ * One or more tuples representing the depth, leaf version, and script for a leaf in the Taproot tree,
+ * allowing the entire tree to be reconstructed. The tuples must be in depth first search order so that
+ * the tree is correctly reconstructed.
+ * @param tree the binary tap tree
+ * @returns a list of BIP 371 tapleaves
+ */
+function tapTreeToList(tree) {
+ if (!(0, types_1.isTaptree)(tree))
+ throw new Error(
+ 'Cannot convert taptree to tapleaf list. Expecting a tapree structure.',
+ );
+ return _tapTreeToList(tree);
+}
+exports.tapTreeToList = tapTreeToList;
+/**
+ * Convert a BIP371 TapLeaf list to a TapTree (binary).
+ * @param leaves a list of tapleaves where each element of the list is (according to BIP371):
+ * One or more tuples representing the depth, leaf version, and script for a leaf in the Taproot tree,
+ * allowing the entire tree to be reconstructed. The tuples must be in depth first search order so that
+ * the tree is correctly reconstructed.
+ * @returns the corresponding taptree, or throws an exception if the tree cannot be reconstructed
+ */
+function tapTreeFromList(leaves = []) {
+ if (leaves.length === 1 && leaves[0].depth === 0)
+ return {
+ output: leaves[0].script,
+ version: leaves[0].leafVersion,
+ };
+ return instertLeavesInTree(leaves);
+}
+exports.tapTreeFromList = tapTreeFromList;
+function checkTaprootInputForSigs(input, action) {
+ const sigs = extractTaprootSigs(input);
+ return sigs.some(sig =>
+ (0, psbtutils_2.signatureBlocksAction)(sig, decodeSchnorrSignature, action),
+ );
+}
+exports.checkTaprootInputForSigs = checkTaprootInputForSigs;
+function decodeSchnorrSignature(signature) {
+ return {
+ signature: signature.slice(0, 64),
+ hashType:
+ signature.slice(64)[0] || transaction_1.Transaction.SIGHASH_DEFAULT,
+ };
+}
+function extractTaprootSigs(input) {
+ const sigs = [];
+ if (input.tapKeySig) sigs.push(input.tapKeySig);
+ if (input.tapScriptSig)
+ sigs.push(...input.tapScriptSig.map(s => s.signature));
+ if (!sigs.length) {
+ const finalTapKeySig = getTapKeySigFromWithness(input.finalScriptWitness);
+ if (finalTapKeySig) sigs.push(finalTapKeySig);
+ }
+ return sigs;
+}
+function getTapKeySigFromWithness(finalScriptWitness) {
+ if (!finalScriptWitness) return;
+ const witness = finalScriptWitness.slice(2);
+ // todo: add schnorr signature validation
+ if (witness.length === 64 || witness.length === 65) return witness;
+}
+function _tapTreeToList(tree, leaves = [], depth = 0) {
+ if (depth > bip341_1.MAX_TAPTREE_DEPTH)
+ throw new Error('Max taptree depth exceeded.');
+ if (!tree) return [];
+ if ((0, types_1.isTapleaf)(tree)) {
+ leaves.push({
+ depth,
+ leafVersion: tree.version || bip341_1.LEAF_VERSION_TAPSCRIPT,
+ script: tree.output,
+ });
+ return leaves;
+ }
+ if (tree[0]) _tapTreeToList(tree[0], leaves, depth + 1);
+ if (tree[1]) _tapTreeToList(tree[1], leaves, depth + 1);
+ return leaves;
+}
+function instertLeavesInTree(leaves) {
+ let tree;
+ for (const leaf of leaves) {
+ tree = instertLeafInTree(leaf, tree);
+ if (!tree) throw new Error(`No room left to insert tapleaf in tree`);
+ }
+ return tree;
+}
+function instertLeafInTree(leaf, tree, depth = 0) {
+ if (depth > bip341_1.MAX_TAPTREE_DEPTH)
+ throw new Error('Max taptree depth exceeded.');
+ if (leaf.depth === depth) {
+ if (!tree)
+ return {
+ output: leaf.script,
+ version: leaf.leafVersion,
+ };
+ return;
+ }
+ if ((0, types_1.isTapleaf)(tree)) return;
+ const leftSide = instertLeafInTree(leaf, tree && tree[0], depth + 1);
+ if (leftSide) return [leftSide, tree && tree[1]];
+ const rightSide = instertLeafInTree(leaf, tree && tree[1], depth + 1);
+ if (rightSide) return [tree && tree[0], rightSide];
+}
+function checkMixedTaprootAndNonTaprootInputFields(
+ inputData,
+ newInputData,
+ action,
+) {
+ const isBadTaprootUpdate =
+ isTaprootInput(inputData) && hasNonTaprootFields(newInputData);
+ const isBadNonTaprootUpdate =
+ hasNonTaprootFields(inputData) && isTaprootInput(newInputData);
+ const hasMixedFields =
+ inputData === newInputData &&
+ (isTaprootInput(newInputData) && hasNonTaprootFields(newInputData)); // todo: bad? use !===
+ if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields)
+ throw new Error(
+ `Invalid arguments for Psbt.${action}. ` +
+ `Cannot use both taproot and non-taproot fields.`,
+ );
+}
+function checkMixedTaprootAndNonTaprootOutputFields(
+ inputData,
+ newInputData,
+ action,
+) {
+ const isBadTaprootUpdate =
+ isTaprootOutput(inputData) && hasNonTaprootFields(newInputData);
+ const isBadNonTaprootUpdate =
+ hasNonTaprootFields(inputData) && isTaprootOutput(newInputData);
+ const hasMixedFields =
+ inputData === newInputData &&
+ (isTaprootOutput(newInputData) && hasNonTaprootFields(newInputData));
+ if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields)
+ throw new Error(
+ `Invalid arguments for Psbt.${action}. ` +
+ `Cannot use both taproot and non-taproot fields.`,
+ );
+}
+function checkIfTapLeafInTree(inputData, newInputData, action) {
+ if (newInputData.tapMerkleRoot) {
+ const newLeafsInTree = (newInputData.tapLeafScript || []).every(l =>
+ isTapLeafInTree(l, newInputData.tapMerkleRoot),
+ );
+ const oldLeafsInTree = (inputData.tapLeafScript || []).every(l =>
+ isTapLeafInTree(l, newInputData.tapMerkleRoot),
+ );
+ if (!newLeafsInTree || !oldLeafsInTree)
+ throw new Error(
+ `Invalid arguments for Psbt.${action}. Tapleaf not part of taptree.`,
+ );
+ } else if (inputData.tapMerkleRoot) {
+ const newLeafsInTree = (newInputData.tapLeafScript || []).every(l =>
+ isTapLeafInTree(l, inputData.tapMerkleRoot),
+ );
+ if (!newLeafsInTree)
+ throw new Error(
+ `Invalid arguments for Psbt.${action}. Tapleaf not part of taptree.`,
+ );
+ }
+}
+function isTapLeafInTree(tapLeaf, merkleRoot) {
+ if (!merkleRoot) return true;
+ const leafHash = (0, bip341_1.tapleafHash)({
+ output: tapLeaf.script,
+ version: tapLeaf.leafVersion,
+ });
+ const rootHash = (0, bip341_1.rootHashFromPath)(
+ tapLeaf.controlBlock,
+ leafHash,
+ );
+ return rootHash.equals(merkleRoot);
+}
+function sortSignatures(input, tapLeaf) {
+ const leafHash = (0, bip341_1.tapleafHash)({
+ output: tapLeaf.script,
+ version: tapLeaf.leafVersion,
+ });
+ return (input.tapScriptSig || [])
+ .filter(tss => tss.leafHash.equals(leafHash))
+ .map(tss => addPubkeyPositionInScript(tapLeaf.script, tss))
+ .sort((t1, t2) => t2.positionInScript - t1.positionInScript)
+ .map(t => t.signature);
+}
+function addPubkeyPositionInScript(script, tss) {
+ return Object.assign(
+ {
+ positionInScript: (0, psbtutils_1.pubkeyPositionInScript)(
+ tss.pubkey,
+ script,
+ ),
+ },
+ tss,
+ );
+}
+/**
+ * Find tapleaf by hash, or get the signed tapleaf with the shortest path.
+ */
+function findTapLeafToFinalize(input, inputIndex, leafHashToFinalize) {
+ if (!input.tapScriptSig || !input.tapScriptSig.length)
+ throw new Error(
+ `Can not finalize taproot input #${inputIndex}. No tapleaf script signature provided.`,
+ );
+ const tapLeaf = (input.tapLeafScript || [])
+ .sort((a, b) => a.controlBlock.length - b.controlBlock.length)
+ .find(leaf =>
+ canFinalizeLeaf(leaf, input.tapScriptSig, leafHashToFinalize),
+ );
+ if (!tapLeaf)
+ throw new Error(
+ `Can not finalize taproot input #${inputIndex}. Signature for tapleaf script not found.`,
+ );
+ return tapLeaf;
+}
+function canFinalizeLeaf(leaf, tapScriptSig, hash) {
+ const leafHash = (0, bip341_1.tapleafHash)({
+ output: leaf.script,
+ version: leaf.leafVersion,
+ });
+ const whiteListedHash = !hash || hash.equals(leafHash);
+ return (
+ whiteListedHash &&
+ tapScriptSig.find(tss => tss.leafHash.equals(leafHash)) !== undefined
+ );
+}
+function hasNonTaprootFields(io) {
+ return (
+ io &&
+ !!(
+ io.redeemScript ||
+ io.witnessScript ||
+ (io.bip32Derivation && io.bip32Derivation.length)
+ )
+ );
+}
diff --git a/src/psbt/psbtutils.d.ts b/src/psbt/psbtutils.d.ts
new file mode 100644
index 000000000..6491132a8
--- /dev/null
+++ b/src/psbt/psbtutils.d.ts
@@ -0,0 +1,19 @@
+///
+import { PsbtInput } from 'bip174/src/lib/interfaces';
+export declare const isP2MS: (script: Buffer) => boolean;
+export declare const isP2PK: (script: Buffer) => boolean;
+export declare const isP2PKH: (script: Buffer) => boolean;
+export declare const isP2WPKH: (script: Buffer) => boolean;
+export declare const isP2WSHScript: (script: Buffer) => boolean;
+export declare const isP2SHScript: (script: Buffer) => boolean;
+export declare const isP2TR: (script: Buffer) => boolean;
+export declare function witnessStackToScriptWitness(witness: Buffer[]): Buffer;
+export declare function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number;
+export declare function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean;
+export declare function checkInputForSig(input: PsbtInput, action: string): boolean;
+declare type SignatureDecodeFunc = (buffer: Buffer) => {
+ signature: Buffer;
+ hashType: number;
+};
+export declare function signatureBlocksAction(signature: Buffer, signatureDecodeFn: SignatureDecodeFunc, action: string): boolean;
+export {};
diff --git a/src/psbt/psbtutils.js b/src/psbt/psbtutils.js
new file mode 100644
index 000000000..086faa4f0
--- /dev/null
+++ b/src/psbt/psbtutils.js
@@ -0,0 +1,120 @@
+'use strict';
+Object.defineProperty(exports, '__esModule', { value: true });
+exports.signatureBlocksAction = exports.checkInputForSig = exports.pubkeyInScript = exports.pubkeyPositionInScript = exports.witnessStackToScriptWitness = exports.isP2TR = exports.isP2SHScript = exports.isP2WSHScript = exports.isP2WPKH = exports.isP2PKH = exports.isP2PK = exports.isP2MS = void 0;
+const varuint = require('bip174/src/lib/converter/varint');
+const bscript = require('../script');
+const transaction_1 = require('../transaction');
+const crypto_1 = require('../crypto');
+const payments = require('../payments');
+function isPaymentFactory(payment) {
+ return script => {
+ try {
+ payment({ output: script });
+ return true;
+ } catch (err) {
+ return false;
+ }
+ };
+}
+exports.isP2MS = isPaymentFactory(payments.p2ms);
+exports.isP2PK = isPaymentFactory(payments.p2pk);
+exports.isP2PKH = isPaymentFactory(payments.p2pkh);
+exports.isP2WPKH = isPaymentFactory(payments.p2wpkh);
+exports.isP2WSHScript = isPaymentFactory(payments.p2wsh);
+exports.isP2SHScript = isPaymentFactory(payments.p2sh);
+exports.isP2TR = isPaymentFactory(payments.p2tr);
+function witnessStackToScriptWitness(witness) {
+ let buffer = Buffer.allocUnsafe(0);
+ function writeSlice(slice) {
+ buffer = Buffer.concat([buffer, Buffer.from(slice)]);
+ }
+ function writeVarInt(i) {
+ const currentLen = buffer.length;
+ const varintLen = varuint.encodingLength(i);
+ buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
+ varuint.encode(i, buffer, currentLen);
+ }
+ function writeVarSlice(slice) {
+ writeVarInt(slice.length);
+ writeSlice(slice);
+ }
+ function writeVector(vector) {
+ writeVarInt(vector.length);
+ vector.forEach(writeVarSlice);
+ }
+ writeVector(witness);
+ return buffer;
+}
+exports.witnessStackToScriptWitness = witnessStackToScriptWitness;
+function pubkeyPositionInScript(pubkey, script) {
+ const pubkeyHash = (0, crypto_1.hash160)(pubkey);
+ const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling?
+ const decompiled = bscript.decompile(script);
+ if (decompiled === null) throw new Error('Unknown script error');
+ return decompiled.findIndex(element => {
+ if (typeof element === 'number') return false;
+ return (
+ element.equals(pubkey) ||
+ element.equals(pubkeyHash) ||
+ element.equals(pubkeyXOnly)
+ );
+ });
+}
+exports.pubkeyPositionInScript = pubkeyPositionInScript;
+function pubkeyInScript(pubkey, script) {
+ return pubkeyPositionInScript(pubkey, script) !== -1;
+}
+exports.pubkeyInScript = pubkeyInScript;
+function checkInputForSig(input, action) {
+ const pSigs = extractPartialSigs(input);
+ return pSigs.some(pSig =>
+ signatureBlocksAction(pSig, bscript.signature.decode, action),
+ );
+}
+exports.checkInputForSig = checkInputForSig;
+function signatureBlocksAction(signature, signatureDecodeFn, action) {
+ const { hashType } = signatureDecodeFn(signature);
+ const whitelist = [];
+ const isAnyoneCanPay =
+ hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY;
+ if (isAnyoneCanPay) whitelist.push('addInput');
+ const hashMod = hashType & 0x1f;
+ switch (hashMod) {
+ case transaction_1.Transaction.SIGHASH_ALL:
+ break;
+ case transaction_1.Transaction.SIGHASH_SINGLE:
+ case transaction_1.Transaction.SIGHASH_NONE:
+ whitelist.push('addOutput');
+ whitelist.push('setInputSequence');
+ break;
+ }
+ if (whitelist.indexOf(action) === -1) {
+ return true;
+ }
+ return false;
+}
+exports.signatureBlocksAction = signatureBlocksAction;
+function extractPartialSigs(input) {
+ let pSigs = [];
+ if ((input.partialSig || []).length === 0) {
+ if (!input.finalScriptSig && !input.finalScriptWitness) return [];
+ pSigs = getPsigsFromInputFinalScripts(input);
+ } else {
+ pSigs = input.partialSig;
+ }
+ return pSigs.map(p => p.signature);
+}
+function getPsigsFromInputFinalScripts(input) {
+ const scriptItems = !input.finalScriptSig
+ ? []
+ : bscript.decompile(input.finalScriptSig) || [];
+ const witnessItems = !input.finalScriptWitness
+ ? []
+ : bscript.decompile(input.finalScriptWitness) || [];
+ return scriptItems
+ .concat(witnessItems)
+ .filter(item => {
+ return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item);
+ })
+ .map(sig => ({ signature: sig }));
+}
diff --git a/src/types.d.ts b/src/types.d.ts
index 5a8505d34..b3d93589d 100644
--- a/src/types.d.ts
+++ b/src/types.d.ts
@@ -10,6 +10,29 @@ export declare function Signer(obj: any): boolean;
export declare function Satoshi(value: number): boolean;
export declare const ECPoint: any;
export declare const Network: any;
+export interface XOnlyPointAddTweakResult {
+ parity: 1 | 0;
+ xOnlyPubkey: Uint8Array;
+}
+export interface Tapleaf {
+ output: Buffer;
+ version?: number;
+}
+export declare const TAPLEAF_VERSION_MASK = 254;
+export declare function isTapleaf(o: any): o is Tapleaf;
+/**
+ * Binary tree repsenting script path spends for a Taproot input.
+ * Each node is either a single Tapleaf, or a pair of Tapleaf | Taptree.
+ * The tree has no balancing requirements.
+ */
+export declare type Taptree = [Taptree | Tapleaf, Taptree | Tapleaf] | Tapleaf;
+export declare function isTaptree(scriptTree: any): scriptTree is Taptree;
+export interface TinySecp256k1Interface {
+ isXOnlyPoint(p: Uint8Array): boolean;
+ xOnlyPointAddTweak(p: Uint8Array, tweak: Uint8Array): XOnlyPointAddTweakResult | null;
+ privateAdd(d: Uint8Array, tweak: Uint8Array): Uint8Array | null;
+ privateNegate(d: Uint8Array): Uint8Array;
+}
export declare const Buffer256bit: any;
export declare const Hash160bit: any;
export declare const Hash256bit: any;
diff --git a/src/types.js b/src/types.js
index a6d1efa16..3a2dc51ea 100644
--- a/src/types.js
+++ b/src/types.js
@@ -1,6 +1,6 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
-exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.typeforce = void 0;
+exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.isTaptree = exports.isTapleaf = exports.TAPLEAF_VERSION_MASK = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.typeforce = void 0;
const buffer_1 = require('buffer');
exports.typeforce = require('typeforce');
const ZERO32 = buffer_1.Buffer.alloc(32, 0);
@@ -68,6 +68,21 @@ exports.Network = exports.typeforce.compile({
scriptHash: exports.typeforce.UInt8,
wif: exports.typeforce.UInt8,
});
+exports.TAPLEAF_VERSION_MASK = 0xfe;
+function isTapleaf(o) {
+ if (!o || !('output' in o)) return false;
+ if (!buffer_1.Buffer.isBuffer(o.output)) return false;
+ if (o.version !== undefined)
+ return (o.version & exports.TAPLEAF_VERSION_MASK) === o.version;
+ return true;
+}
+exports.isTapleaf = isTapleaf;
+function isTaptree(scriptTree) {
+ if (!(0, exports.Array)(scriptTree)) return isTapleaf(scriptTree);
+ if (scriptTree.length !== 2) return false;
+ return scriptTree.every(t => isTaptree(t));
+}
+exports.isTaptree = isTaptree;
exports.Buffer256bit = exports.typeforce.BufferN(32);
exports.Hash160bit = exports.typeforce.BufferN(20);
exports.Hash256bit = exports.typeforce.BufferN(32);
diff --git a/test/address.spec.ts b/test/address.spec.ts
index b1304c323..23c18b9f6 100644
--- a/test/address.spec.ts
+++ b/test/address.spec.ts
@@ -1,9 +1,12 @@
import * as assert from 'assert';
import { describe, it } from 'mocha';
+import * as ecc from 'tiny-secp256k1';
import * as baddress from '../src/address';
import * as bscript from '../src/script';
import * as fixtures from './fixtures/address.json';
+import { initEccLib } from '../src';
+
const NETWORKS = Object.assign(
{
litecoin: {
@@ -65,6 +68,7 @@ describe('address', () => {
});
describe('fromOutputScript', () => {
+ initEccLib(ecc);
fixtures.standard.forEach(f => {
it('encodes ' + f.script.slice(0, 30) + '... (' + f.network + ')', () => {
const script = bscript.fromASM(f.script);
@@ -79,7 +83,7 @@ describe('address', () => {
const script = bscript.fromASM(f.script);
assert.throws(() => {
- baddress.fromOutputScript(script);
+ baddress.fromOutputScript(script, undefined);
}, new RegExp(f.exception));
});
});
@@ -138,10 +142,11 @@ describe('address', () => {
});
fixtures.invalid.toOutputScript.forEach(f => {
- it('throws when ' + f.exception, () => {
+ it('throws when ' + (f.exception || f.paymentException), () => {
+ const exception = f.paymentException || `${f.address} ${f.exception}`;
assert.throws(() => {
baddress.toOutputScript(f.address, f.network as any);
- }, new RegExp(f.address + ' ' + f.exception));
+ }, new RegExp(exception));
});
});
});
diff --git a/test/fixtures/address.json b/test/fixtures/address.json
index 765ea8aca..0430b7887 100644
--- a/test/fixtures/address.json
+++ b/test/fixtures/address.json
@@ -80,10 +80,10 @@
},
{
"network": "bitcoin",
- "bech32": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
+ "bech32": "bc1p3efq8ujsj0qr5xvms7mv89p8cz0crqdtuxe9ms6grqgxc9sgsntslthf6w",
"version": 1,
- "data": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6",
- "script": "OP_1 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"
+ "data": "8e5203f25093c03a199b87b6c39427c09f8181abe1b25dc34818106c160884d7",
+ "script": "OP_1 8e5203f25093c03a199b87b6c39427c09f8181abe1b25dc34818106c160884d7"
},
{
"network": "bitcoin",
@@ -116,10 +116,16 @@
],
"bech32": [
{
- "address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kt5nd6y",
+ "address": "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy",
+ "version": 0,
+ "prefix": "tb",
+ "data": "000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"
+ },
+ {
+ "address": "bc1p3efq8ujsj0qr5xvms7mv89p8cz0crqdtuxe9ms6grqgxc9sgsntslthf6w",
"version": 1,
"prefix": "bc",
- "data": "751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"
+ "data": "8e5203f25093c03a199b87b6c39427c09f8181abe1b25dc34818106c160884d7"
},
{
"address": "bc1zw508d6qejxtdg4y5r3zarvaryvaxxpcs",
@@ -195,6 +201,19 @@
{
"exception": "has no matching Address",
"script": "OP_0 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675"
+ },
+ {
+ "exception": "has no matching Address",
+ "script": "OP_1 75"
+ },
+ {
+ "exception": "has no matching Address",
+ "script": "OP_1 751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd675"
+ },
+ {
+ "description": "pubkey is not valid x coordinate",
+ "exception": "has no matching Address",
+ "script": "OP_1 fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"
}
],
"toOutputScript": [
@@ -297,6 +316,14 @@
{
"address": "bc1gmk9yu",
"exception": "has no matching Script"
+ },
+ {
+ "address": "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw55h884v",
+ "exception": "has no matching Script"
+ },
+ {
+ "address": "bc1pllllllllllllllllllllllllllllllllllllllllllllallllshsdfvw2y",
+ "paymentException": "TypeError: Invalid pubkey for p2tr"
}
]
}
diff --git a/test/fixtures/p2tr.json b/test/fixtures/p2tr.json
new file mode 100644
index 000000000..aaa82fbb4
--- /dev/null
+++ b/test/fixtures/p2tr.json
@@ -0,0 +1,1198 @@
+{
+ "valid": [
+ {
+ "description": "output and pubkey from address",
+ "arguments": {
+ "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6sj7lcqx"
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5",
+ "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5",
+ "signature": null,
+ "input": null,
+ "witness": null
+ }
+ },
+ {
+ "description": "address and pubkey from output",
+ "arguments": {
+ "output": "OP_1 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5"
+ },
+ "expected": {
+ "name": "p2tr",
+ "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6sj7lcqx",
+ "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5",
+ "signature": null,
+ "input": null,
+ "witness": null
+ }
+ },
+ {
+ "description": "address and output from pubkey",
+ "arguments": {
+ "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5"
+ },
+ "expected": {
+ "name": "p2tr",
+ "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6sj7lcqx",
+ "output": "OP_1 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5",
+ "signature": null,
+ "input": null,
+ "witness": null
+ }
+ },
+ {
+ "description": "address, output and witness from pubkey and signature",
+ "arguments": {
+ "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5",
+ "signature": "a251221c339a7129dd0b769698aca40d8d9da9570ab796a1820b91ab7dbf5acbea21c88ba8f1e9308a21729baf080734beaf97023882d972f75e380d480fd704"
+ },
+ "expected": {
+ "name": "p2tr",
+ "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6sj7lcqx",
+ "output": "OP_1 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5",
+ "input": null,
+ "witness": [
+ "a251221c339a7129dd0b769698aca40d8d9da9570ab796a1820b91ab7dbf5acbea21c88ba8f1e9308a21729baf080734beaf97023882d972f75e380d480fd704"
+ ]
+ }
+ },
+ {
+ "description": "address, output and signature from pubkey and witness",
+ "arguments": {
+ "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5",
+ "witness": [
+ "300602010002010001"
+ ]
+ },
+ "expected": {
+ "name": "p2tr",
+ "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6sj7lcqx",
+ "output": "OP_1 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5",
+ "input": null,
+ "signature": "300602010002010001",
+ "witness": [
+ "300602010002010001"
+ ]
+ }
+ },
+ {
+ "description": "address, pubkey and output from internalPubkey",
+ "arguments": {
+ "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7"
+ },
+ "expected": {
+ "name": "p2tr",
+ "address": "bc1prs7pxymu7jhsptzjlwlqnk8jyg5qmq4sdlc3rwcy7pd3ydz92xjq5ap2sg",
+ "pubkey": "1c3c13137cf4af00ac52fbbe09d8f222280d82b06ff111bb04f05b12344551a4",
+ "output": "OP_1 1c3c13137cf4af00ac52fbbe09d8f222280d82b06ff111bb04f05b12344551a4",
+ "signature": null,
+ "input": null,
+ "witness": null
+ }
+ },
+ {
+ "description": "address, pubkey, internalPubkey, redeeem and output from witness",
+ "arguments": {
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba",
+ "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac",
+ "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c"
+ ]
+ },
+ "expected": {
+ "name": "p2tr",
+ "internalPubkey": "a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a36",
+ "pubkey": "1ebe8b90363bd097aa9f352c8b21914e1886bc09fe9e70c09f33ef2d2abdf4bc",
+ "hash": "c5c62d7fc595ba5fbe61602eb1a29e2e4763408fe1e2b161beb7cb3c71ebcad9",
+ "address": "bc1pr6lghypk80gf025lx5kgkgv3fcvgd0qfl608psylx0hj624a7j7qay80rv",
+ "output": "OP_1 1ebe8b90363bd097aa9f352c8b21914e1886bc09fe9e70c09f33ef2d2abdf4bc",
+ "signature": null,
+ "input": null,
+ "redeem" : {
+ "output": "OP_DROP c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0 OP_CHECKSIG",
+ "redeemVersion": 192,
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba"
+ ]
+ },
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba",
+ "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac",
+ "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c"
+ ]
+ }
+ },
+ {
+ "description": "address, pubkey, internalPubkey and output from witness with annex",
+ "arguments": {
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba",
+ "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac",
+ "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c",
+ "5000000000000000001111111111111111"
+ ]
+ },
+ "expected": {
+ "name": "p2tr",
+ "internalPubkey": "a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a36",
+ "pubkey": "1ebe8b90363bd097aa9f352c8b21914e1886bc09fe9e70c09f33ef2d2abdf4bc",
+ "hash": "c5c62d7fc595ba5fbe61602eb1a29e2e4763408fe1e2b161beb7cb3c71ebcad9",
+ "address": "bc1pr6lghypk80gf025lx5kgkgv3fcvgd0qfl608psylx0hj624a7j7qay80rv",
+ "output": "OP_1 1ebe8b90363bd097aa9f352c8b21914e1886bc09fe9e70c09f33ef2d2abdf4bc",
+ "signature": null,
+ "input": null,
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba",
+ "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac",
+ "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c",
+ "5000000000000000001111111111111111"
+ ]
+ }
+ },
+ {
+ "description": "address, pubkey, output and hash from internalPubkey and a script tree with one leaf",
+ "arguments": {
+ "internalPubkey": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0",
+ "scriptTree": {
+ "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18 OP_CHECKSIG"
+ }
+ },
+ "expected": {
+ "name": "p2tr",
+ "address": "bc1pjegs09vkeder9m4sw3ycjf2rnpa8nljdqmuleunk9eshu8cq3xysvhgp2u",
+ "pubkey": "9651079596cb7232eeb07449892543987a79fe4d06f9fcf2762e617e1f008989",
+ "output": "OP_1 9651079596cb7232eeb07449892543987a79fe4d06f9fcf2762e617e1f008989",
+ "hash": "16e3f3b8b9c1e453c56b547785cdd25259d65823a2064f30783acc58ef012633",
+ "signature": null,
+ "input": null,
+ "witness": null
+ }
+ },
+ {
+ "description": "address, pubkey, output and hash from internalPubkey and a script tree with two leafs",
+ "arguments": {
+ "internalPubkey": "2258b1c3160be0864a541854eec9164a572f094f7562628281a8073bb89173a7",
+ "scriptTree": [
+ {
+ "output": "d826a0a53abb6ffc60df25b9c152870578faef4b2eb5a09bdd672bbe32cdd79b OP_CHECKSIG"
+ },
+ {
+ "output": "d826a0a53abb6ffc60df25b9c152870578faef4b2eb5a09bdd672bbe32cdd79b OP_CHECKSIG"
+ }
+ ]
+ },
+ "expected": {
+ "name": "p2tr",
+ "address": "bc1ptj0v8rwcj6s36p4r26ws6htx0fct43n0mxdvdeh9043whlxlq3kq9965ke",
+ "pubkey": "5c9ec38dd896a11d06a3569d0d5d667a70bac66fd99ac6e6e57d62ebfcdf046c",
+ "output": "OP_1 5c9ec38dd896a11d06a3569d0d5d667a70bac66fd99ac6e6e57d62ebfcdf046c",
+ "hash": "ce00198cd4667abae1f94aa5862d089e2967af5aec20715c692db74e3d66bb73",
+ "signature": null,
+ "input": null,
+ "witness": null
+ }
+ },
+ {
+ "description": "address, pubkey, output and hash from internalPubkey and a script tree with three leafs",
+ "arguments": {
+ "internalPubkey": "7631cacec3343052d87ef4d0065f61dde82d7d2db0c1cc02ef61ef3c982ea763",
+ "scriptTree": [
+ {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG"
+ },
+ [
+ {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG"
+ },
+ {
+ "output": "9b4d495b74887815a1ff623c055c6eac6b6b2e07d2a016d6526ebac71dd99744 OP_CHECKSIG"
+ }
+ ]
+ ]
+ },
+ "expected": {
+ "name": "p2tr",
+ "address": "bc1pkq0t8nkmqswn3qjg9uy6ux2hsyyz4as25v8unfjc9s8q2e4c00sqku9lxh",
+ "pubkey": "b01eb3cedb041d3882482f09ae195781082af60aa30fc9a6582c0e0566b87be0",
+ "output": "OP_1 b01eb3cedb041d3882482f09ae195781082af60aa30fc9a6582c0e0566b87be0",
+ "hash": "7ae0cc2057b1a7bf0e09c787e1d7b6b2355ac112a7b80380a5c1e942155b0c0f",
+ "signature": null,
+ "input": null,
+ "witness": null
+ }
+ },
+ {
+ "description": "address, pubkey, output and hash from internalPubkey and a script tree with four leafs",
+ "arguments": {
+ "internalPubkey": "d0c19def28bb1b39451c1a814737615983967780d223b79969ba692182c6006b",
+ "scriptTree": [
+ [
+ {
+ "output": "9b4d495b74887815a1ff623c055c6eac6b6b2e07d2a016d6526ebac71dd99744 OP_CHECKSIG"
+ },
+ {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG"
+ }
+ ],
+ [
+ {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG"
+ },
+ {
+ "output": "9b4d495b74887815a1ff623c055c6eac6b6b2e07d2a016d6526ebac71dd99744 OP_CHECKSIG"
+ }
+ ]
+ ]
+ },
+ "expected": {
+ "name": "p2tr",
+ "address": "bc1pstdzevc40j059s0473rghhv9e05l9f5xv7l6dtlavvq22rzfna3syjvjut",
+ "pubkey": "82da2cb3157c9f42c1f5f4468bdd85cbe9f2a68667bfa6affd6300a50c499f63",
+ "output": "OP_1 82da2cb3157c9f42c1f5f4468bdd85cbe9f2a68667bfa6affd6300a50c499f63",
+ "hash": "d673e784eac9b70289130a0bd359023a0fbdde51dc069b9efb4157c2cdce3ea5",
+ "signature": null,
+ "input": null,
+ "witness": null
+ }
+ },
+ {
+ "description": "address, pubkey, output and hash from internalPubkey and a script tree with seven leafs",
+ "arguments": {
+ "internalPubkey": "f95886b02a84928c5c15bdca32784993105f73de27fa6ad8c1a60389b999267c",
+ "scriptTree": [
+ [
+ {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG"
+ },
+ [
+ {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG"
+ },
+ {
+ "output": "2258b1c3160be0864a541854eec9164a572f094f7562628281a8073bb89173a7 OP_CHECKSIG"
+ }
+ ]
+ ],
+ [
+ {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG"
+ },
+ [
+ {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG"
+ },
+ [
+ {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG"
+ },
+ {
+ "output": "03a669ea926f381582ec4a000b9472ba8a17347f5fb159eddd4a07036a6718eb OP_CHECKSIG"
+ }
+ ]
+ ]
+ ]
+ ]
+ },
+ "expected": {
+ "name": "p2tr",
+ "address": "bc1pfas4r5s5208puwzj20hvwg2dw2kanc06yxczzdd66729z63pk43q7zwlu6",
+ "pubkey": "4f6151d21453ce1e385253eec7214d72add9e1fa21b02135bad794516a21b562",
+ "output": "OP_1 4f6151d21453ce1e385253eec7214d72add9e1fa21b02135bad794516a21b562",
+ "hash": "16fb2e99bdf86f67ee6980d0418658f15df7e19476053b58f45a89df2e219b1b",
+ "signature": null,
+ "input": null,
+ "witness": null
+ }
+ },
+ {
+ "description": "address, pubkey, and output from internalPubkey redeem, and hash (one leaf, no tree)",
+ "arguments": {
+ "internalPubkey": "aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247",
+ "redeem": {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4 OP_CHECKSIG"
+ },
+ "hash": "b424dea09f840b932a00373cdcdbd25650b8c3acfe54a9f4a641a286721b8d26"
+ },
+ "expected": {
+ "name": "p2tr",
+ "address": "bc1pnxyp0ahcg53jzgrzj57hnlgdtqtzn7qqhmgjgczk8hzhcltq974qazepzf",
+ "pubkey": "998817f6f84523212062953d79fd0d581629f800bed12460563dc57c7d602faa",
+ "output": "OP_1 998817f6f84523212062953d79fd0d581629f800bed12460563dc57c7d602faa",
+ "signature": null,
+ "input": null,
+ "witness": [
+ "2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4ac",
+ "c0aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247"
+ ]
+ }
+ },
+ {
+ "description": "address, pubkey, output and hash from internalPubkey and a script tree with seven leafs (2)",
+ "arguments": {
+ "internalPubkey": "aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247",
+ "redeem": {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4 OP_CHECKSIG"
+ },
+ "scriptTree": [
+ {
+ "output": "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d3 OP_CHECKSIG"
+ },
+ [
+ [
+ {
+ "output": "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d4 OP_CHECKSIG"
+ },
+ [
+ {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac1 OP_CHECKSIG"
+ },
+ {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac2 OP_CHECKSIG"
+ }
+ ]
+ ],
+ [
+ [
+ {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac3 OP_CHECKSIG"
+ },
+ {
+ "output": "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4 OP_CHECKSIG"
+ }
+ ],
+ {
+ "output": "00a9da96087a72258f83b338ef7f0ea8cbbe05da5f18f091eb397d1ecbf7c3d5 OP_CHECKSIG"
+ }
+ ]
+ ]
+ ]
+ },
+ "expected": {
+ "name": "p2tr",
+ "address": "bc1pd2llmtym6c5hyecf5zqsyjz9q0jlxaaksw9j0atx8lc8a0e0vrmsw9ewly",
+ "pubkey": "6abffdac9bd629726709a08102484503e5f377b6838b27f5663ff07ebf2f60f7",
+ "output": "OP_1 6abffdac9bd629726709a08102484503e5f377b6838b27f5663ff07ebf2f60f7",
+ "hash": "88b7e3b495a84aa2bc12780b1773f130ce5eb747b0c28dc4840b7c9280f7326d",
+ "signature": null,
+ "input": null,
+ "witness": [
+ "2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4ac",
+ "c0aba457d16a8d59151c387f24d1eb887efbe24644c1ee64b261282e7baebdb247dac795766bbda1eaeaa45e5bfa0a950fdd5f4c4aada5b1f3082edc9689b9fd0a315fb34a7a93dcaed5e26cf7468be5bd377dda7a4d29128f7dd98db6da9bf04325fff3aa86365bac7534dcb6495867109941ec444dd35294e0706e29e051066d73e0d427bd3249bb921fa78c04fb76511f583ff48c97210d17c2d9dcfbb95023"
+ ]
+ }
+ },
+ {
+ "description": "BIP341 Test case 1",
+ "arguments": {
+ "internalPubkey": "d6889cb081036e0faefa3a35157ad71086b123b2b144b649798b494c300a961d"
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343",
+ "pubkey": "53a1f6e454df1aa2776a2814a721372d6258050de330b3c6d10ee8f4e0dda343",
+ "address": "bc1p2wsldez5mud2yam29q22wgfh9439spgduvct83k3pm50fcxa5dps59h4z5",
+ "signature": null,
+ "input": null,
+ "witness": null
+ }
+ },
+ {
+ "description": "BIP341 Test case 2",
+ "arguments": {
+ "internalPubkey": "187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27",
+ "redeem": {
+ "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG",
+ "redeemVersion": 192
+ },
+ "scriptTree": {
+ "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG",
+ "version": 192
+ }
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3",
+ "pubkey": "147c9c57132f6e7ecddba9800bb0c4449251c92a1e60371ee77557b6620f3ea3",
+ "address": "bc1pz37fc4cn9ah8anwm4xqqhvxygjf9rjf2resrw8h8w4tmvcs0863sa2e586",
+ "hash": "5b75adecf53548f3ec6ad7d78383bf84cc57b55a3127c72b9a2481752dd88b21",
+ "witness": [
+ "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac",
+ "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27"
+ ],
+ "redeem": {
+ "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG",
+ "redeemVersion": 192
+ },
+ "signature": null,
+ "input": null
+ }
+ },
+ {
+ "description": "BIP341 Test case 3",
+ "arguments": {
+ "internalPubkey": "93478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820",
+ "redeem": {
+ "output": "b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007 OP_CHECKSIG",
+ "redeemVersion": 192
+ },
+ "scriptTree": {
+ "output": "b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007 OP_CHECKSIG",
+ "version": 192
+ }
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e",
+ "pubkey": "e4d810fd50586274face62b8a807eb9719cef49c04177cc6b76a9a4251d5450e",
+ "address": "bc1punvppl2stp38f7kwv2u2spltjuvuaayuqsthe34hd2dyy5w4g58qqfuag5",
+ "hash": "c525714a7f49c28aedbbba78c005931a81c234b2f6c99a73e4d06082adc8bf2b",
+ "witness": [
+ "20b617298552a72ade070667e86ca63b8f5789a9fe8731ef91202a91c9f3459007ac",
+ "c093478e9488f956df2396be2ce6c5cced75f900dfa18e7dabd2428aae78451820"
+ ],
+ "signature": null,
+ "input": null
+ }
+ },
+ {
+ "description": "BIP341 Test case 4 - spend leaf 0",
+ "arguments": {
+ "internalPubkey": "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592",
+ "redeem": {
+ "output": "387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48 OP_CHECKSIG",
+ "redeemVersion": 192
+ },
+ "scriptTree": [
+ {
+ "output": "387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48 OP_CHECKSIG",
+ "version": 192
+ },
+ {
+ "output": "424950333431",
+ "version": 152
+ }
+ ]
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 0f63ca2c7639b9bb4be0465cc0aa3ee78a0761ba5f5f7d6ff8eab340f09da561",
+ "pubkey": "0f63ca2c7639b9bb4be0465cc0aa3ee78a0761ba5f5f7d6ff8eab340f09da561",
+ "address": "bc1ppa3u5trk8xumkjlqgewvp237u79qwcd6ta0h6mlca2e5puya54ssw9zq0y",
+ "hash": "f3004d6c183e038105d436db1424f321613366cbb7b05939bf05d763a9ebb962",
+ "witness": [
+ "20387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48ac",
+ "c0ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865927b2c2af8aa3e8b7bfe2f62a155f91427489c5c3b32be47e0b3fac755fc780e0e"
+ ],
+ "signature": null,
+ "input": null
+ }
+ },
+ {
+ "description": "BIP341 Test case 4 - spend leaf 1",
+ "arguments": {
+ "internalPubkey": "ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf3786592",
+ "redeem": {
+ "output": "424950333431",
+ "redeemVersion": 152
+ },
+ "scriptTree": [
+ {
+ "output": "387671353e273264c495656e27e39ba899ea8fee3bb69fb2a680e22093447d48 OP_CHECKSIG",
+ "version": 192
+ },
+ {
+ "output": "424950333431",
+ "version": 152
+ }
+ ]
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 0f63ca2c7639b9bb4be0465cc0aa3ee78a0761ba5f5f7d6ff8eab340f09da561",
+ "pubkey": "0f63ca2c7639b9bb4be0465cc0aa3ee78a0761ba5f5f7d6ff8eab340f09da561",
+ "address": "bc1ppa3u5trk8xumkjlqgewvp237u79qwcd6ta0h6mlca2e5puya54ssw9zq0y",
+ "hash": "f3004d6c183e038105d436db1424f321613366cbb7b05939bf05d763a9ebb962",
+ "witness": [
+ "06424950333431",
+ "98ee4fe085983462a184015d1f782d6a5f8b9c2b60130aff050ce221ecf37865928ad69ec7cf41c2a4001fd1f738bf1e505ce2277acdcaa63fe4765192497f47a7"
+ ],
+ "signature": null,
+ "input": null
+ }
+ },
+ {
+ "description": "BIP341 Test case 5 - spend leaf 0",
+ "arguments": {
+ "internalPubkey": "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8",
+ "redeem": {
+ "output": "44b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fd OP_CHECKSIG",
+ "redeemVersion": 192
+ },
+ "scriptTree": [
+ {
+ "output": "44b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fd OP_CHECKSIG",
+ "version": 192
+ },
+ {
+ "output": "546170726f6f74",
+ "version": 82
+ }
+ ]
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 053690babeabbb7850c32eead0acf8df990ced79f7a31e358fabf2658b4bc587",
+ "pubkey": "053690babeabbb7850c32eead0acf8df990ced79f7a31e358fabf2658b4bc587",
+ "address": "bc1pq5mfpw474wahs5xr9m4dpt8cm7vsemte7733udv040extz6tckrs29g04c",
+ "hash": "d9c2c32808b41c0301d876d49c0af72e1d98e84b99ca9b4bb67fea1a7424b755",
+ "witness": [
+ "2044b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fdac",
+ "c1f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8e44d5f8fa5892c8b6d4d09a08d36edd0b08636e30311302e2448ad8172fb3433"
+ ],
+ "signature": null,
+ "input": null
+ }
+ },
+ {
+ "description": "BIP341 Test case 5 - spend leaf 1",
+ "arguments": {
+ "internalPubkey": "f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd8",
+ "redeem": {
+ "output": "546170726f6f74",
+ "redeemVersion": 82
+ },
+ "scriptTree": [
+ {
+ "output": "44b178d64c32c4a05cc4f4d1407268f764c940d20ce97abfd44db5c3592b72fd OP_CHECKSIG",
+ "version": 192
+ },
+ {
+ "output": "546170726f6f74",
+ "version": 82
+ }
+ ]
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 053690babeabbb7850c32eead0acf8df990ced79f7a31e358fabf2658b4bc587",
+ "pubkey": "053690babeabbb7850c32eead0acf8df990ced79f7a31e358fabf2658b4bc587",
+ "address": "bc1pq5mfpw474wahs5xr9m4dpt8cm7vsemte7733udv040extz6tckrs29g04c",
+ "hash": "d9c2c32808b41c0301d876d49c0af72e1d98e84b99ca9b4bb67fea1a7424b755",
+ "witness": [
+ "07546170726f6f74",
+ "53f9f400803e683727b14f463836e1e78e1c64417638aa066919291a225f0e8dd864512fecdb5afa04f98839b50e6f0cb7b1e539bf6f205f67934083cdcc3c8d89"
+ ],
+ "signature": null,
+ "input": null
+ }
+ },
+ {
+ "description": "BIP341 Test case 6 - spend leaf 0",
+ "arguments": {
+ "internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f",
+ "redeem": {
+ "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG",
+ "redeemVersion": 192
+ },
+ "scriptTree": [
+ {
+ "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG",
+ "version": 192
+ },
+ [
+ {
+ "output": "2352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8 OP_CHECKSIG",
+ "version": 192
+ },
+ {
+ "output": "7337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186a OP_CHECKSIG",
+ "version": 192
+ }
+ ]
+ ]
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605",
+ "pubkey": "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605",
+ "address": "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e",
+ "hash": "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2",
+ "witness": [
+ "2072ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69ac",
+ "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fffe578e9ea769027e4f5a3de40732f75a88a6353a09d767ddeb66accef85e553"
+ ],
+ "signature": null,
+ "input": null
+ }
+ },
+ {
+ "description": "BIP341 Test case 6 - spend leaf 1",
+ "arguments": {
+ "internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f",
+ "redeem": {
+ "output": "2352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8 OP_CHECKSIG",
+ "redeemVersion": 192
+ },
+ "scriptTree": [
+ {
+ "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG",
+ "version": 192
+ },
+ [
+ {
+ "output": "2352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8 OP_CHECKSIG",
+ "version": 192
+ },
+ {
+ "output": "7337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186a OP_CHECKSIG",
+ "version": 192
+ }
+ ]
+ ]
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605",
+ "pubkey": "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605",
+ "address": "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e",
+ "hash": "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2",
+ "witness": [
+ "202352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8ac",
+ "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f9e31407bffa15fefbf5090b149d53959ecdf3f62b1246780238c24501d5ceaf62645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817"
+ ],
+ "signature": null,
+ "input": null
+ }
+ },
+ {
+ "description": "BIP341 Test case 6 - spend leaf 2",
+ "arguments": {
+ "internalPubkey": "e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6f",
+ "redeem": {
+ "output": "7337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186a OP_CHECKSIG",
+ "redeemVersion": 192
+ },
+ "scriptTree": [
+ {
+ "output": "72ea6adcf1d371dea8fba1035a09f3d24ed5a059799bae114084130ee5898e69 OP_CHECKSIG",
+ "version": 192
+ },
+ [
+ {
+ "output": "2352d137f2f3ab38d1eaa976758873377fa5ebb817372c71e2c542313d4abda8 OP_CHECKSIG",
+ "version": 192
+ },
+ {
+ "output": "7337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186a OP_CHECKSIG",
+ "version": 192
+ }
+ ]
+ ]
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605",
+ "pubkey": "91b64d5324723a985170e4dc5a0f84c041804f2cd12660fa5dec09fc21783605",
+ "address": "bc1pjxmy65eywgafs5tsunw95ruycpqcqnev6ynxp7jaasylcgtcxczs6n332e",
+ "hash": "ccbd66c6f7e8fdab47b3a486f59d28262be857f30d4773f2d5ea47f7761ce0e2",
+ "witness": [
+ "207337c0dd4253cb86f2c43a2351aadd82cccb12a172cd120452b9bb8324f2186aac",
+ "c0e0dfe2300b0dd746a3f8674dfd4525623639042569d829c7f0eed9602d263e6fba982a91d4fc552163cb1c0da03676102d5b7a014304c01f0c77b2b8e888de1c2645a02e0aac1fe69d69755733a9b7621b694bb5b5cde2bbfc94066ed62b9817"
+ ],
+ "signature": null,
+ "input": null
+ }
+ },
+ {
+ "description": "BIP341 Test case 7 - spend leaf 0",
+ "arguments": {
+ "internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d",
+ "redeem": {
+ "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG"
+ },
+ "scriptTree": [
+ {
+ "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG",
+ "version": 192
+ },
+ [
+ {
+ "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG",
+ "version": 192
+ },
+ {
+ "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG",
+ "version": 192
+ }
+ ]
+ ]
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831",
+ "pubkey": "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831",
+ "address": "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe",
+ "hash": "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def",
+ "witness": [
+ "2071981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2ac",
+ "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d3cd369a528b326bc9d2133cbd2ac21451acb31681a410434672c8e34fe757e91"
+ ],
+ "signature": null,
+ "input": null
+ }
+ },
+ {
+ "description": "BIP341 Test case 7 - spend leaf 1",
+ "arguments": {
+ "internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d",
+ "redeem": {
+ "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG",
+ "redeemVersion": 192
+ },
+ "scriptTree": [
+ {
+ "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG",
+ "version": 192
+ },
+ [
+ {
+ "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG",
+ "version": 192
+ },
+ {
+ "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG",
+ "version": 192
+ }
+ ]
+ ]
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831",
+ "pubkey": "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831",
+ "address": "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe",
+ "hash": "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def",
+ "witness": [
+ "20d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748ac",
+ "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312dd7485025fceb78b9ed667db36ed8b8dc7b1f0b307ac167fa516fe4352b9f4ef7f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d"
+ ],
+ "signature": null,
+ "input": null
+ }
+ },
+ {
+ "description": "BIP341 Test case 7 - spend leaf 2",
+ "arguments": {
+ "internalPubkey": "55adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d",
+ "redeem": {
+ "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG",
+ "redeemVersion": 192
+ },
+ "scriptTree": [
+ {
+ "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG",
+ "version": 192
+ },
+ [
+ {
+ "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG",
+ "version": 192
+ },
+ {
+ "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG",
+ "version": 192
+ }
+ ]
+ ]
+ },
+ "options": {},
+ "expected": {
+ "name": "p2tr",
+ "output": "OP_1 75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831",
+ "pubkey": "75169f4001aa68f15bbed28b218df1d0a62cbbcf1188c6665110c293c907b831",
+ "address": "bc1pw5tf7sqp4f50zka7629jrr036znzew70zxyvvej3zrpf8jg8hqcssyuewe",
+ "hash": "2f6b2c5397b6d68ca18e09a3f05161668ffe93a988582d55c6f07bd5b3329def",
+ "witness": [
+ "20c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4cac",
+ "c155adf4e8967fbd2e29f20ac896e60c3b0f1d5b0efa9d34941b5958c7b0a0312d737ed1fe30bc42b8022d717b44f0d93516617af64a64753b7a06bf16b26cd711f154e8e8e17c31d3462d7132589ed29353c6fafdb884c5a6e04ea938834f0d9d"
+ ],
+ "signature": null,
+ "input": null
+ }
+ }
+ ],
+ "invalid": [
+ {
+ "exception": "Not enough data",
+ "arguments": {}
+ },
+ {
+ "exception": "Not enough data",
+ "arguments": {
+ "signature": "300602010002010001"
+ }
+ },
+ {
+ "description": "Incorrect Witness Version",
+ "exception": "Output is invalid",
+ "arguments": {
+ "output": "OP_0 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5"
+ }
+ },
+ {
+ "description": "Invalid x coordinate for pubkey in pubkey",
+ "exception": "Invalid pubkey for p2tr",
+ "arguments": {
+ "pubkey": "f136e956540197c21ff3c075d32a6e3c82f1ee1e646cc0f08f51b0b5edafa762"
+ }
+ },
+ {
+ "description": "Invalid x coordinate for pubkey in output",
+ "exception": "Invalid pubkey for p2tr",
+ "arguments": {
+ "output": "OP_1 f136e956540197c21ff3c075d32a6e3c82f1ee1e646cc0f08f51b0b5edafa762"
+ }
+ },
+ {
+ "description": "Invalid x coordinate for pubkey in address",
+ "exception": "Invalid pubkey for p2tr",
+ "arguments": {
+ "address": "bc1p7ymwj4j5qxtuy8lncp6ax2nw8jp0rms7v3kvpuy02xcttmd05a3qmwlnez"
+ }
+ },
+ {
+ "description": "Pubkey mismatch between pubkey and output",
+ "exception": "Pubkey mismatch",
+ "options": {},
+ "arguments": {
+ "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5",
+ "output": "OP_1 12d7dac98d69a086a50b30959a3537950f356ffc6f50a263ab75c8a3ec9d44c1"
+ }
+ },
+ {
+ "description": "Pubkey mismatch between pubkey and address",
+ "exception": "Pubkey mismatch",
+ "options": {},
+ "arguments": {
+ "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5",
+ "address": "bc1pztta4jvddxsgdfgtxz2e5dfhj58n2mludag2ycatwhy28myagnqsnl7mv7"
+ }
+ },
+ {
+ "description": "Pubkey mismatch between output and address",
+ "exception": "Pubkey mismatch",
+ "options": {},
+ "arguments": {
+ "output": "OP_1 ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5",
+ "address": "bc1pztta4jvddxsgdfgtxz2e5dfhj58n2mludag2ycatwhy28myagnqsnl7mv7"
+ }
+ },
+ {
+ "description": "Pubkey mismatch between internalPubkey and pubkey",
+ "exception": "Pubkey mismatch",
+ "options": {},
+ "arguments": {
+ "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7",
+ "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5"
+ }
+ },
+ {
+ "description": "Hash mismatch between scriptTree and hash",
+ "exception": "Hash mismatch",
+ "options": {},
+ "arguments": {
+ "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7",
+ "scriptTree": {
+ "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18 OP_CHECKSIG"
+ },
+ "hash": "b76077013c8e303085e300000000000000000000000000000000000000000000"
+ }
+ },
+ {
+ "exception": "Expected Point",
+ "options": {},
+ "arguments": {
+ "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f8"
+ }
+ },
+ {
+ "exception": "Signature mismatch",
+ "arguments": {
+ "pubkey": "ab610d22c801def8a1e02368d1b92018970eb52a729919705e8a1a2f60c750f5",
+ "signature": "a251221c339a7129dd0b769698aca40d8d9da9570ab796a1820b91ab7dbf5acbea21c88ba8f1e9308a21729baf080734beaf97023882d972f75e380d480fd704",
+ "witness": [
+ "607b8b5b5c8614757736e3d5811790636d2a8e2ea14418f8cff66b2e898b3b7536a49b7c4bc8b3227953194bf5d0548e13e3526fdb36beeefadda1ec834a0db2"
+ ]
+ }
+ },
+ {
+ "exception": "Invalid prefix or Network mismatch",
+ "arguments": {
+ "address": "bcrt1prhepe49mpmhclwcqmkzpaz43revunykc7fc0f9az6pq08sn4qe7sxtrd8y"
+ }
+ },
+ {
+ "exception": "Invalid address version",
+ "arguments": {
+ "address": "bc1z4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82r6s6rxhwd"
+ }
+ },
+ {
+ "exception": "Invalid address data",
+ "arguments": {
+ "address": "bc1p4dss6gkgq8003g0qyd5drwfqrztsadf2w2v3juz73gdz7cx82qh3d2w3"
+ }
+ },
+ {
+ "description": "Control block length too small",
+ "exception": "The control-block length is too small. Got 16, expected min 33.",
+ "arguments": {
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba",
+ "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac",
+ "c1a7957acbaaf7b444c53d9e0c9436e8"
+ ]
+ }
+ },
+ {
+ "description": "Control block must have a length of 33 + 32m (0 <= m <= 128)",
+ "exception": "The control-block length of 40 is incorrect!",
+ "arguments": {
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba",
+ "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac",
+ "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf77"
+ ]
+ }
+ },
+ {
+ "description": "Control block length too large",
+ "exception": "The script path is too long. Got 129, expected max 128.",
+ "arguments": {
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba",
+ "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac",
+ "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c50cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c"
+ ]
+ }
+ },
+ {
+ "description": "Invalid internalPubkey in control block",
+ "exception": "Invalid internalPubkey for p2tr witness",
+ "arguments": {
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba",
+ "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac",
+ "c14444444444444444453d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c"
+ ]
+ }
+ },
+ {
+ "description": "internalPubkey mismatch between control block and internalKey",
+ "exception": "Internal pubkey mismatch",
+ "arguments": {
+ "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7",
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba",
+ "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac",
+ "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c"
+ ]
+ }
+ },
+ {
+ "description": "pubkey mismatch between outputKey and pubkey",
+ "exception": "Pubkey mismatch for p2tr witness",
+ "arguments": {
+ "pubkey": "df0e070ca2fca05ecd191bdba047841d62414b2bdcb6249c258fd64c0dd251ff",
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba",
+ "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac",
+ "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c"
+ ]
+ }
+ },
+ {
+ "description": "parity",
+ "exception": "Incorrect parity",
+ "arguments": {
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba",
+ "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac",
+ "c0a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c"
+ ]
+ }
+ },
+ {
+ "description": "Script Tree is not a binary tree (has tree leafs)",
+ "exception": "property \"scriptTree\" of type \\?isTaptree, got Array",
+ "options": {},
+ "arguments": {
+ "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7",
+ "scriptTree": [
+ {
+ "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG",
+ "version": 192
+ },
+ [
+ {
+ "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG",
+ "version": 192
+ },
+ {
+ "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG",
+ "version": 192
+ },
+ {
+ "output": "c440b462ad48c7a77f94cd4532d8f2119dcebbd7c9764557e62726419b08ad4c OP_CHECKSIG",
+ "version": 192
+ }
+ ]
+ ],
+ "hash": "b76077013c8e303085e300000000000000000000000000000000000000000000"
+ }
+ },
+ {
+ "description": "Script Tree is not a TapTree tree (leaf has no script)",
+ "exception": "property \"scriptTree\" of type \\?isTaptree, got Array",
+ "options": {},
+ "arguments": {
+ "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7",
+ "scriptTree": [
+ {
+ "output": "71981521ad9fc9036687364118fb6ccd2035b96a423c59c5430e98310a11abe2 OP_CHECKSIG",
+ "version": 192
+ },
+ [
+ [
+ [
+ [
+ {
+ "output": "d5094d2dbe9b76e2c245a2b89b6006888952e2faa6a149ae318d69e520617748 OP_CHECKSIG",
+ "version": 192
+ },
+ {
+ "version": 192
+ }
+ ]
+ ]
+ ]
+ ]
+ ],
+ "hash": "b76077013c8e303085e300000000000000000000000000000000000000000000"
+ }
+ },
+ {
+ "description": "Incorrect redeem version",
+ "exception": "Redeem.redeemVersion and witness mismatch",
+ "arguments": {
+ "witness": [
+ "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac",
+ "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27"
+ ],
+ "redeem": {
+ "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8 OP_CHECKSIG",
+ "redeemVersion": 111
+ }
+ }
+ },
+ {
+ "description": "Incorrect redeem output",
+ "exception": "Redeem.output and witness mismatch",
+ "arguments": {
+ "witness": [
+ "20d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e25a48229b8ac",
+ "c1187791b6f712a8ea41c8ecdd0ee77fab3e85263b37e1ec18a3651926b3a6cf27"
+ ],
+ "redeem": {
+ "output": "d85a959b0290bf19bb89ed43c916be835475d013da4b362117393e0000000000 OP_CHECKSIG",
+ "redeemVersion": 192
+ }
+ }
+ },
+ {
+ "description": "Incorrect redeem witness",
+ "exception": "Redeem.witness and witness mismatch",
+ "arguments": {
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba",
+ "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac",
+ "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c"
+ ],
+ "redeem" : {
+ "output": "OP_DROP c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0 OP_CHECKSIG",
+ "redeemVersion": 192,
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e0100000000000000000000",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba"
+ ]
+ }
+ }
+ },
+ {
+ "description": "Incorrect redeem output ASM",
+ "exception": "Redeem.output is invalid",
+ "arguments": {
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e010ac942c99de05aaaa602",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba",
+ "7520c7b5db9562078049719228db2ac80cb9643ec96c8055aa3b29c2c03d4d99edb0ac",
+ "c1a7957acbaaf7b444c53d9e0c9436e8a8a3247fd515095d66ddf6201918b40a3668f9a4ccdffcf778da624dca2dda0b08e763ec52fd4ad403ec7563a3504d0cc168b9a77a410029e01dac89567c9b2e6cd726e840351df3f2f58fefe976200a19244150d04153909f660184d656ee95fa7bf8e1d4ec83da1fca34f64bc279b76d257ec623e08baba2cfa4ea9e99646e88f1eb1668c00c0f15b7443c8ab83481611cc3ae85eb89a7bfc40067eb1d2e6354a32426d0ce710e88bc4cc0718b99c325509c9d02a6a980d675a8969be10ee9bef82cafee2fc913475667ccda37b1bc7f13f64e56c449c532658ba8481631c02ead979754c809584a875951619cec8fb040c33f06468ae0266cd8693d6a64cea5912be32d8de95a6da6300b0c50fdcd6001ea41126e7b7e5280d455054a816560028f5ca53c9a50ee52f10e15c5337315bad1f5277acb109a1418649dc6ead2fe14699742fee7182f2f15e54279c7d932ed2799d01d73c97e68bbc94d6f7f56ee0a80efd7c76e3169e10d1a1ba3b5f1eb02369dc43af687461c7a2a3344d13eb5485dca29a67f16b4cb988923060fd3b65d0f0352bb634bcc44f2fe668836dcd0f604150049835135dc4b4fbf90fb334b3938a1f137eb32f047c65b85e6c1173b890b6d0162b48b186d1f1af8521945924ac8ac8efec321bf34f1d4b3d4a304a10313052c652d53f6ecb8a55586614e8950cde9ab6fe8e22802e93b3b9139112250b80ebc589aba231af535bb20f7eeec2e412f698c17f3fdc0a2e20924a5e38b21a628a9e3b2a61e35958e60c7f5087c"
+ ],
+ "redeem" : {
+ "output": "",
+ "redeemVersion": 192,
+ "witness": [
+ "9675a9982c6398ea9d441cb7a943bcd6ff033cc3a2e01a0178a7d3be4575be863871c6bf3eef5ecd34721c784259385ca9101c3a313e0100000000000000000000",
+ "5799cf4b193b730fb99580b186f7477c2cca4d28957326f6f1a5d14116438530e7ec0ce1cd465ad96968ae8a6a09d4d37a060a115919f56fcfebe7b2277cc2df5cc08fb6cda9105ee2512b2e22635aba"
+ ]
+ }
+ }
+ },
+ {
+ "description": "Redeem script not in tree",
+ "exception": "Redeem script not in tree",
+ "options": {},
+ "arguments": {
+ "internalPubkey": "9fa5ffb68821cf559001caa0577eeea4978b29416def328a707b15e91701a2f7",
+ "scriptTree": {
+ "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c18 OP_CHECKSIG"
+ },
+ "redeem": {
+ "output": "83d8ee77a0f3a32a5cea96fd1624d623b836c1e5d1ac2dcde46814b619320c19 OP_CHECKSIG"
+ }
+ }
+ }
+ ],
+ "dynamic": {
+ "depends": {},
+ "details": []
+ }
+}
diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json
index 0e51d57cf..ec6eda328 100644
--- a/test/fixtures/psbt.json
+++ b/test/fixtures/psbt.json
@@ -116,6 +116,10 @@
{
"description": "PSBT with unknown types in the inputs.",
"psbt": "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA="
+ },
+ {
+ "description": "PSBT with one P2TR input and one P2TR output.",
+ "psbt": "cHNidP8BAF4CAAAAAWbQAKi9hNXynJhqPu8bqkvp0kHihVShkWGh3yLy15+LAAAAAAD/////Aej9AAAAAAAAIlEgRvZJfLLxnVDD6emCqVDcyGIUsB/M5DekIGHHvbEjDTMAAAAAAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAA=="
}
],
"failSignChecks": [
@@ -243,6 +247,36 @@
}
],
"result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA"
+ },
+ {
+ "description": "Update taproot output",
+ "isTaproot": true,
+ "psbt": "cHNidP8BAF4CAAAAAbc48X2AGPRzFoMvXHqldFViBB89ZrPfTH6yxr42pJsWAAAAAAAKAAAAAZBBBgAAAAAAIlEgwjn5jxVDmCN6B+wTPt4zCjlF+5KOEDapZ4S9Y3HZxdwAAAAAAAEBK6BoBgAAAAAAIlEgOJumKbXWO2VGchS5k1l1NzOsAyg8aBVQbDNJ88BPtH5iFcF6ESg3Xx4VxbYhRfEEyJc6dumg3zlxfNO8piELt1JMShpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdYmWrJ1IAOmFFKDvSwulomGXZP6LYOYOGBHzSRnrh2w9cb0vzIGrMAAAA==",
+ "outputData": [
+ {
+ "tapInternalKey": "Buffer.from('b3310106b13af5def8fc341fe7120e844ab73d4437649aefcadd306d974c9958', 'hex')",
+ "tapTree": {
+ "leaves": [
+ {
+ "depth": 1,
+ "leafVersion": 192,
+ "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0ac', 'hex')"
+ },
+ {
+ "depth": 2,
+ "leafVersion": 192,
+ "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0ac', 'hex')"
+ },
+ {
+ "depth": 2,
+ "leafVersion": 192,
+ "script": "Buffer.from('5ab2752003a6145283bd2c2e9689865d93fa2d8398386047cd2467ae1db0f5c6f4bf3206ac', 'hex')"
+ }
+ ]
+ }
+ }
+ ],
+ "result": "cHNidP8BAF4CAAAAAbc48X2AGPRzFoMvXHqldFViBB89ZrPfTH6yxr42pJsWAAAAAAAKAAAAAZBBBgAAAAAAIlEgwjn5jxVDmCN6B+wTPt4zCjlF+5KOEDapZ4S9Y3HZxdwAAAAAAAEBK6BoBgAAAAAAIlEgOJumKbXWO2VGchS5k1l1NzOsAyg8aBVQbDNJ88BPtH5iFcF6ESg3Xx4VxbYhRfEEyJc6dumg3zlxfNO8piELt1JMShpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdYmWrJ1IAOmFFKDvSwulomGXZP6LYOYOGBHzSRnrh2w9cb0vzIGrMAAAQUgszEBBrE69d74/DQf5xIOhEq3PUQ3ZJrvyt0wbZdMmVgBBnIBwCIgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsCsAsAiIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrArALAJVqydSADphRSg70sLpaJhl2T+i2DmDhgR80kZ64dsPXG9L8yBqwA"
}
],
"signer": [
@@ -273,6 +307,71 @@
}
],
"result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU210gwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gEBAwQBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBAQMEAQAAAAEEIgAgjCNTFzdDtZXftKB7crqOQuN5fadOh/59nXSX47ICiQMBBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA="
+ },
+ {
+ "description": "Sign PSBT with 3 inputs [P2PKH, P2TR (key-path), P2WPKH] and two outputs [P2TR, P2WPKH]",
+ "psbt": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgAAABASu4BQEAAAAAACJRIJQh5zSw+dLEZ+p90ZfGGstEZ83LyfTLDFcfi2OlxAyuARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMgAAAA==",
+ "isTaproot": true,
+ "keys": [
+ {
+ "inputToSign": 0,
+ "WIF": "cRyKzLXVgTReWe7wgfEiXktTa9tf4e5DK1STha274d7BBbnucTaR"
+ },
+ {
+ "inputToSign": 1,
+ "WIF": "cR62L1G154fjHFrBCJMxJxbk1rcxhT2xcTh7WstvFdFDsZ9uFiVj"
+ },
+ {
+ "inputToSign": 2,
+ "WIF": "cPPRdCmAMZMjPdHfRmTCmzYVruZHJ8GbM1FqN2W6DnmEPWDg29aL"
+ }
+ ],
+ "result": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgACICAi5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQRzBEAiBpWClBybtHveXkhAgTiE8QSczMJs8MGuH4LOSNRA6s/AIgWlbB3xJOtJIsszj1qZ/whA5jK9wnTzeZzDlVs/ivq2cBAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4BE0Cd7/ny+QreV7urBWKNroQWCvnZczwkU0kLZiKsJQjtftKHWXMknftjt1d4K6aPYH7cBXzhlrUF+2GovjYLccZeARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMiICA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660SDBFAiEAoCIktghL55iuMAmkzwYJzb+h+qmNewZXxAx/06ObxIQCIELCsBz/wd2wPlnJb27OluxMkTPnCyHA2C+SxHiX/FvPAQAAAA=="
+ },
+ {
+ "description": "Sign PSBT with 1 input [P2TR] (script-path, 3-of-3) and one output [P2TR]",
+ "psbt": "cHNidP8BAF4CAAAAAQU2GLj/HTOEN804Hc6JRNTLM7EmDlIBdjG2G1aUw266AAAAAAD/////AYAaBgAAAAAAIlEg35sLGepBXUbR93XfxDJcYBCHqNVjw/jogOgPVdic1UgAAAAAAAEBK6BoBgAAAAAAIlEgVZx4+tHeORcb0jDJnOytrOnNOGL1uS0MdMUg1GQeS15iFcFmuQP4s1ds7KJtMOh4fTw1QCgxkWUA3FUAuUzKHzjDvhpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdZpII+JGq8uLXsUYXjFSofirt3Z0daGaSJXko5nzwgXT/N1rCA5X4Ep3WO0pcLxISTqoFt6ftMPcOUfuTMF3uzFQuf567ogqKs3vBYJ2DTDkTs1ONrRyE1/m2qDX/wXV3eRWjeuNXK6U5zAAAA=",
+ "isTaproot": true,
+ "keys": [
+ {
+ "inputToSign": 0,
+ "WIF": "cRXSy63fXDve59e8cvqozVFfqXJB6YL6cPzoRewmEsux81SgPrfj"
+ },
+ {
+ "inputToSign": 0,
+ "WIF": "cQQXUJocNBS6oZCCtyhCsdN5ammK6WoJWpx44ANKxZSN2A3WDDEN"
+ },
+ {
+ "inputToSign": 0,
+ "WIF": "cTrPNrN2EQo4ppBHcFNxyLBFq2WLjZoNKY5nQbPwAGdhQqqsRKSu"
+ }
+ ],
+ "result": "cHNidP8BAF4CAAAAAQU2GLj/HTOEN804Hc6JRNTLM7EmDlIBdjG2G1aUw266AAAAAAD/////AYAaBgAAAAAAIlEg35sLGepBXUbR93XfxDJcYBCHqNVjw/jogOgPVdic1UgAAAAAAAEBK6BoBgAAAAAAIlEgVZx4+tHeORcb0jDJnOytrOnNOGL1uS0MdMUg1GQeS15BFDlfgSndY7SlwvEhJOqgW3p+0w9w5R+5MwXe7MVC5/nrrqI0FdZdKILLLZgRVK8L9Bn2ijU6IcoqqyImKIWt3MtAA3alBoU7IBCkBk9OHD1wE8fJI4y+lbnTRj48e8AAwRM77q3Rml679qCzGvEAKAs99UNMaXHQIhgGfRP11AMlJkEUj4kary4texRheMVKh+Ku3dnR1oZpIleSjmfPCBdP83WuojQV1l0ogsstmBFUrwv0GfaKNTohyiqrIiYoha3cy0DYJZ6Lv7FZPIBRZFfVgF5v3gcRiQnT8aM82Q5IPkwkzZrGo4ThZblvunG/+hu8ZPuJrUU+uXb+s9rcwSH+BihIQRSoqze8FgnYNMOROzU42tHITX+baoNf/BdXd5FaN641cq6iNBXWXSiCyy2YEVSvC/QZ9oo1OiHKKqsiJiiFrdzLQEfQ5UkAg4lTbhJxjMzB7hu6ad1fywYxHCXjFXHHrm5PJTOFJLg2oTnwuQToz/Z2AW/UET7Op+WSoHZvW4tzzLhiFcFmuQP4s1ds7KJtMOh4fTw1QCgxkWUA3FUAuUzKHzjDvhpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdZpII+JGq8uLXsUYXjFSofirt3Z0daGaSJXko5nzwgXT/N1rCA5X4Ep3WO0pcLxISTqoFt6ftMPcOUfuTMF3uzFQuf567ogqKs3vBYJ2DTDkTs1ONrRyE1/m2qDX/wXV3eRWjeuNXK6U5zAAAA="
+ },
+ {
+ "description": "Sign PSBT with 1 input [P2TR]. Signer pubkey found in two tapleaf scripts (both get signed)",
+ "psbt": "cHNidP8BAF4CAAAAATNOP+fCPDCsZrgGIptDVbwY/yrkM2xaFfLe05BSLulZAAAAAAD/////AZBBBgAAAAAAIlEgo6glLro4LvJCjIXCj+2xYC8NQ5BB/tvaXLukmWNHDaQAAAAAAAEBK6BoBgAAAAAAIlEgo6glLro4LvJCjIXCj+2xYC8NQ5BB/tvaXLukmWNHDaRiFcDXgjtX0c88beKd6Y8zRYbANZXEc8dDhlup8v3SCiX0bBpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWWeEQK6JF8Ev4E8Yg25gvDzbizKAO6m6JD082gFjsWNVpIP1IuUG/pcYMyG7KOHraqMXMKKOJb5eIpnjGt7LrvcsWrCDxKlbBaB54iRPj/r6t5N9DtBFItV8uoHfEwGboXpVsHLogt01J+NwSsj7PM7m6QbLDiXLdoYzn9pHNA4kAGF1pL3+6U5zAQhXA14I7V9HPPG3inemPM0WGwDWVxHPHQ4ZbqfL90gol9Gx4Lk64Mk5QMzz2Xo97sRZFcGH0TduAOb6H0hHOB/0CrCMg/Ui5Qb+lxgzIbso4etqoxcwoo4lvl4imeMa3suu9yxaswAAA",
+ "isTaproot": true,
+ "keys": [
+ {
+ "inputToSign": 0,
+ "WIF": "cSu5bjn9TsAEeqZxEFKy5of3FKfHha6FT56KvfAGmu6rMmPNJTrV"
+ }
+ ],
+ "result": "cHNidP8BAF4CAAAAATNOP+fCPDCsZrgGIptDVbwY/yrkM2xaFfLe05BSLulZAAAAAAD/////AZBBBgAAAAAAIlEgo6glLro4LvJCjIXCj+2xYC8NQ5BB/tvaXLukmWNHDaQAAAAAAAEBK6BoBgAAAAAAIlEgo6glLro4LvJCjIXCj+2xYC8NQ5BB/tvaXLukmWNHDaRBFP1IuUG/pcYMyG7KOHraqMXMKKOJb5eIpnjGt7LrvcsWIjJncH2zqZGpUloPoJZipi/NkKmjWthVrvnedXPOOOdABNUCXyGRAy4nthiGpE8dXioP2P50J+fU5gojlqunTwPKRPvqWcCzOoaJGvUEF+oxpMXZ1+FZWHRppoeoZFParUEU/Ui5Qb+lxgzIbso4etqoxcwoo4lvl4imeMa3suu9yxZZ4RArokXwS/gTxiDbmC8PNuLMoA7qbokPTzaAWOxY1UAF3RzlWz+5cWYG1EqfZTT/CO7O3hGYvMMjlJV5rluR916WCgGYO2hHj9fiEH0rxD3BRbzR78PShCen+beqDncSYhXA14I7V9HPPG3inemPM0WGwDWVxHPHQ4ZbqfL90gol9GwaUpyfs81+d21htiJbbGEOiQb7j6psWaxcPpW1+C0p1lnhECuiRfBL+BPGINuYLw824sygDupuiQ9PNoBY7FjVaSD9SLlBv6XGDMhuyjh62qjFzCijiW+XiKZ4xrey673LFqwg8SpWwWgeeIkT4/6+reTfQ7QRSLVfLqB3xMBm6F6VbBy6ILdNSfjcErI+zzO5ukGyw4ly3aGM5/aRzQOJABhdaS9/ulOcwEIVwNeCO1fRzzxt4p3pjzNFhsA1lcRzx0OGW6ny/dIKJfRseC5OuDJOUDM89l6Pe7EWRXBh9E3bgDm+h9IRzgf9AqwjIP1IuUG/pcYMyG7KOHraqMXMKKOJb5eIpnjGt7LrvcsWrMAAAA=="
+ },
+ {
+ "description": "Sign PSBT with 1 input [P2TR]. Signer pubkey found in two tapleaf scripts (sign only the matching tapleaf hash)",
+ "psbt": "cHNidP8BAF4CAAAAAdXMkUOLeYvgm981k3T7Pmdf5Dr31jOxvpFHiXiU9D2gAAAAAAD/////AZBBBgAAAAAAIlEgD31MIAvwCaanOsAzyBoQ4o/WozgFiqVQTEqGJE18XqwAAAAAAAEBK6BoBgAAAAAAIlEgD31MIAvwCaanOsAzyBoQ4o/WozgFiqVQTEqGJE18XqxiFcAI1JOUM3VATBJC28HI1B5zKQNd5N4/c4g7M1CYhZWnWhpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWk4qTbS/aPOMicn4cednnrKiVhdggWbZxsQhaXXg5/EtpICo/Wsfk0hSOCy21lUyn+vXnc1SG1lNVWHlnXCh3xegqrCCcumXmHlTmveMSBLOPSfb6H3laX97tZtF4gqmxzV+gbLogXBeUvuJ0b/mMOa3539xiz0/Nf9RQCWGZXXoCNcFUYde6U5zAQhXACNSTlDN1QEwSQtvByNQecykDXeTeP3OIOzNQmIWVp1pfFb/yNVlibfG8oN35ON4kJ+kBZQ4LphUz16+7jituPiMgKj9ax+TSFI4LLbWVTKf69edzVIbWU1VYeWdcKHfF6CqswAAA",
+ "isTaproot": true,
+ "keys": [
+ {
+ "inputToSign": 0,
+ "tapLeafHashToSign": "f2d9fd9a2f80e0e7abeac881398fc37198f46e5c802ec00c95152aa6f703e71e",
+ "WIF": "cP76Rzf6bVcmFbnJ3DigWvyNvki2bZeXxoq3B5pcZ8zVRnT4fKdx"
+ }
+ ],
+ "result": "cHNidP8BAF4CAAAAAdXMkUOLeYvgm981k3T7Pmdf5Dr31jOxvpFHiXiU9D2gAAAAAAD/////AZBBBgAAAAAAIlEgD31MIAvwCaanOsAzyBoQ4o/WozgFiqVQTEqGJE18XqwAAAAAAAEBK6BoBgAAAAAAIlEgD31MIAvwCaanOsAzyBoQ4o/WozgFiqVQTEqGJE18XqxBFCo/Wsfk0hSOCy21lUyn+vXnc1SG1lNVWHlnXCh3xegq8tn9mi+A4Oer6siBOY/DcZj0blyALsAMlRUqpvcD5x5Aa7X0m4UCLaHA/Vnjkl+if6rVeiBbIlbHXHLh7RqJyB8Wgs66p6/ZnwSW/HD6o7rMHffIva+jgJgYWf6MvzrfTWIVwAjUk5QzdUBMEkLbwcjUHnMpA13k3j9ziDszUJiFladaGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdaTipNtL9o84yJyfhx52eesqJWF2CBZtnGxCFpdeDn8S2kgKj9ax+TSFI4LLbWVTKf69edzVIbWU1VYeWdcKHfF6CqsIJy6ZeYeVOa94xIEs49J9vofeVpf3u1m0XiCqbHNX6BsuiBcF5S+4nRv+Yw5rfnf3GLPT81/1FAJYZldegI1wVRh17pTnMBCFcAI1JOUM3VATBJC28HI1B5zKQNd5N4/c4g7M1CYhZWnWl8Vv/I1WWJt8byg3fk43iQn6QFlDgumFTPXr7uOK24+IyAqP1rH5NIUjgsttZVMp/r153NUhtZTVVh5Z1wod8XoKqzAAAA="
}
],
"combiner": [
@@ -295,6 +394,42 @@
{
"psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA",
"result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA=="
+ },
+ {
+ "description": "Finalze taproot key-path",
+ "psbt": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgACICAi5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQRzBEAiBpWClBybtHveXkhAgTiE8QSczMJs8MGuH4LOSNRA6s/AIgWlbB3xJOtJIsszj1qZ/whA5jK9wnTzeZzDlVs/ivq2cBAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4BE0Cd7/ny+QreV7urBWKNroQWCvnZczwkU0kLZiKsJQjtftKHWXMknftjt1d4K6aPYH7cBXzhlrUF+2GovjYLccZeARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMiICA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660SDBFAiEAoCIktghL55iuMAmkzwYJzb+h+qmNewZXxAx/06ObxIQCIELCsBz/wd2wPlnJb27OluxMkTPnCyHA2C+SxHiX/FvPAQAAAA==",
+ "result": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgAAEHakcwRAIgaVgpQcm7R73l5IQIE4hPEEnMzCbPDBrh+CzkjUQOrPwCIFpWwd8STrSSLLM49amf8IQOYyvcJ083mcw5VbP4r6tnASECLmi8EfXEugbE+q0WHjzBQSmcz5K3VJs/NHA7uQE0o5AAAQEruAUBAAAAAAAiUSCUIec0sPnSxGfqfdGXxhrLRGfNy8n0ywxXH4tjpcQMrgEIQgFAne/58vkK3le7qwVija6EFgr52XM8JFNJC2YirCUI7X7Sh1lzJJ37Y7dXeCumj2B+3AV84Za1BfthqL42C3HGXgABAR8QJwAAAAAAABYAFE+irKEotvne5TCcT3AFHknGX9cyAQhsAkgwRQIhAKAiJLYIS+eYrjAJpM8GCc2/ofqpjXsGV8QMf9Ojm8SEAiBCwrAc/8HdsD5ZyW9uzpbsTJEz5wshwNgvksR4l/xbzwEhA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660AAAA",
+ "isTaproot": true
+ },
+ {
+ "description": "Finalze taproot script-path",
+ "psbt": "cHNidP8BAF4CAAAAAQU2GLj/HTOEN804Hc6JRNTLM7EmDlIBdjG2G1aUw266AAAAAAD/////AYAaBgAAAAAAIlEg35sLGepBXUbR93XfxDJcYBCHqNVjw/jogOgPVdic1UgAAAAAAAEBK6BoBgAAAAAAIlEgVZx4+tHeORcb0jDJnOytrOnNOGL1uS0MdMUg1GQeS15BFDlfgSndY7SlwvEhJOqgW3p+0w9w5R+5MwXe7MVC5/nrrqI0FdZdKILLLZgRVK8L9Bn2ijU6IcoqqyImKIWt3MtAA3alBoU7IBCkBk9OHD1wE8fJI4y+lbnTRj48e8AAwRM77q3Rml679qCzGvEAKAs99UNMaXHQIhgGfRP11AMlJkEUj4kary4texRheMVKh+Ku3dnR1oZpIleSjmfPCBdP83WuojQV1l0ogsstmBFUrwv0GfaKNTohyiqrIiYoha3cy0DYJZ6Lv7FZPIBRZFfVgF5v3gcRiQnT8aM82Q5IPkwkzZrGo4ThZblvunG/+hu8ZPuJrUU+uXb+s9rcwSH+BihIQRSoqze8FgnYNMOROzU42tHITX+baoNf/BdXd5FaN641cq6iNBXWXSiCyy2YEVSvC/QZ9oo1OiHKKqsiJiiFrdzLQEfQ5UkAg4lTbhJxjMzB7hu6ad1fywYxHCXjFXHHrm5PJTOFJLg2oTnwuQToz/Z2AW/UET7Op+WSoHZvW4tzzLhiFcFmuQP4s1ds7KJtMOh4fTw1QCgxkWUA3FUAuUzKHzjDvhpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdZpII+JGq8uLXsUYXjFSofirt3Z0daGaSJXko5nzwgXT/N1rCA5X4Ep3WO0pcLxISTqoFt6ftMPcOUfuTMF3uzFQuf567ogqKs3vBYJ2DTDkTs1ONrRyE1/m2qDX/wXV3eRWjeuNXK6U5zAAAA=",
+ "result": "cHNidP8BAF4CAAAAAQU2GLj/HTOEN804Hc6JRNTLM7EmDlIBdjG2G1aUw266AAAAAAD/////AYAaBgAAAAAAIlEg35sLGepBXUbR93XfxDJcYBCHqNVjw/jogOgPVdic1UgAAAAAAAEBK6BoBgAAAAAAIlEgVZx4+tHeORcb0jDJnOytrOnNOGL1uS0MdMUg1GQeS14BCP2PAQVAR9DlSQCDiVNuEnGMzMHuG7pp3V/LBjEcJeMVcceubk8lM4UkuDahOfC5BOjP9nYBb9QRPs6n5ZKgdm9bi3PMuEADdqUGhTsgEKQGT04cPXATx8kjjL6VudNGPjx7wADBEzvurdGaXrv2oLMa8QAoCz31Q0xpcdAiGAZ9E/XUAyUmQNglnou/sVk8gFFkV9WAXm/eBxGJCdPxozzZDkg+TCTNmsajhOFluW+6cb/6G7xk+4mtRT65dv6z2tzBIf4GKEhoII+JGq8uLXsUYXjFSofirt3Z0daGaSJXko5nzwgXT/N1rCA5X4Ep3WO0pcLxISTqoFt6ftMPcOUfuTMF3uzFQuf567ogqKs3vBYJ2DTDkTs1ONrRyE1/m2qDX/wXV3eRWjeuNXK6U5xhwWa5A/izV2zsom0w6Hh9PDVAKDGRZQDcVQC5TMofOMO+GlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdYaUpyfs81+d21htiJbbGEOiQb7j6psWaxcPpW1+C0p1gAA",
+ "isTaproot": true
+ },
+ {
+ "description": "Finalze taproot script-path (3-of-3)",
+ "psbt": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgACICAi5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQRzBEAiBpWClBybtHveXkhAgTiE8QSczMJs8MGuH4LOSNRA6s/AIgWlbB3xJOtJIsszj1qZ/whA5jK9wnTzeZzDlVs/ivq2cBAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4BE0ADaUubfpFFrzbU+vL8qCzZE/FO+9unzylfpIgQZ4HTy2qPUtLvbyH59GApdz0SiUZGl8K6Crvt9YIfI/5FxbOLARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMiICA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660SDBFAiEAoCIktghL55iuMAmkzwYJzb+h+qmNewZXxAx/06ObxIQCIELCsBz/wd2wPlnJb27OluxMkTPnCyHA2C+SxHiX/FvPAQAAAA==",
+ "result": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgAAEHakcwRAIgaVgpQcm7R73l5IQIE4hPEEnMzCbPDBrh+CzkjUQOrPwCIFpWwd8STrSSLLM49amf8IQOYyvcJ083mcw5VbP4r6tnASECLmi8EfXEugbE+q0WHjzBQSmcz5K3VJs/NHA7uQE0o5AAAQEruAUBAAAAAAAiUSCUIec0sPnSxGfqfdGXxhrLRGfNy8n0ywxXH4tjpcQMrgEIQgFAA2lLm36RRa821Pry/Kgs2RPxTvvbp88pX6SIEGeB08tqj1LS728h+fRgKXc9EolGRpfCugq77fWCHyP+RcWziwABAR8QJwAAAAAAABYAFE+irKEotvne5TCcT3AFHknGX9cyAQhsAkgwRQIhAKAiJLYIS+eYrjAJpM8GCc2/ofqpjXsGV8QMf9Ojm8SEAiBCwrAc/8HdsD5ZyW9uzpbsTJEz5wshwNgvksR4l/xbzwEhA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660AAAA",
+ "isTaproot": true
+ },
+ {
+ "description": "Finalze taproot with tapkey sig and tapscript sigs (choose tapkey)",
+ "psbt": "cHNidP8BAF4CAAAAAQrtX/VtEfTwY2iXi+s8lzx2JZbV7w9a8q6lONJ4SBm1AAAAAAD/////AZBBBgAAAAAAIlEgp+9JIlFbn3ZwqTz8p4UU73qJfQl4FvNDuyBa51FMm/kAAAAAAAEBK6BoBgAAAAAAIlEgp+9JIlFbn3ZwqTz8p4UU73qJfQl4FvNDuyBa51FMm/kBE0DqpLv0n1NC7N2okYWhHwP+mCqaDGOmNGCobgPw54CAtJdHPKY2i/ioNzq/m2Muh0hUYcLlOcU5U4xF7W+6gvRuQRQGAr5sFRVLM9rtDJmH/nCNJu0u72GXSA2wf9k4GEnXrossGVn91s+ghIb0v5fVWfWjsqU6wRJBundeShgEvDIVQNduanOw6c03gjyLWLim7b/7yet7XRp1uNrtwrMklOMSujYI/h2o+TSqS7eNwCkNddW3R6v03L6S9hWHTUYprclBFDhs6fMvKmERoTq2yrBafaRMmYoKkSuw9D4o3xjiC403iywZWf3Wz6CEhvS/l9VZ9aOypTrBEkG6d15KGAS8MhVAMKZJmsPn+pjmlWzj5oPGOj8XSs0uCMnDL1jjnosEuO7G0y9Y46GlGnfOM54w/0n2qKkdDWHpXX14F9gNesiP5EEUfKybjQL7rXdJwSP1Fb15LaJyYFrW3yJnkN+aP9IdhTOGRF1EGo0fkg5hbe3o7iwS+jTgqmGOQDsHExHbJgmL90A8hHY8WzI+xJsFXMzi5ztkZS8pXf07iNlfDnLKZYyk9ZRBkAsJ9oYsvC19Irrq27l918aqfQc6sZBiskZyMs5wQRR8rJuNAvutd0nBI/UVvXktonJgWtbfImeQ35o/0h2FM4ssGVn91s+ghIb0v5fVWfWjsqU6wRJBundeShgEvDIVQFeVntO1MSRANCT3fpEWhijVJAbF6SVBdPlNAaRKc1sn3Gz+/HTfD+f9sqRlSIsF4D+ouhv6URBtAZUCuKHW6vxiFcFB6oQ4lfHrNv47TKgSz/MUn2hgozxlIgmi2demetlGgxpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWhkRdRBqNH5IOYW3t6O4sEvo04KphjkA7BxMR2yYJi/dpIHysm40C+613ScEj9RW9eS2icmBa1t8iZ5Dfmj/SHYUzrCAGAr5sFRVLM9rtDJmH/nCNJu0u72GXSA2wf9k4GEnXrrogOGzp8y8qYRGhOrbKsFp9pEyZigqRK7D0PijfGOILjTe6U5zAQhXBQeqEOJXx6zb+O0yoEs/zFJ9oYKM8ZSIJotnXpnrZRoMvbQYwKuRcSf7lDrRMU3H56Gh6NeGkogAw4hpvAn/mgCMgfKybjQL7rXdJwSP1Fb15LaJyYFrW3yJnkN+aP9IdhTOswAEXIEHqhDiV8es2/jtMqBLP8xSfaGCjPGUiCaLZ16Z62UaDARggNhmyJFW2/EeankNIJjRP9QFlju4/j/nEEcMAnlXe+XcAAA==",
+ "result": "cHNidP8BAF4CAAAAAQrtX/VtEfTwY2iXi+s8lzx2JZbV7w9a8q6lONJ4SBm1AAAAAAD/////AZBBBgAAAAAAIlEgp+9JIlFbn3ZwqTz8p4UU73qJfQl4FvNDuyBa51FMm/kAAAAAAAEBK6BoBgAAAAAAIlEgp+9JIlFbn3ZwqTz8p4UU73qJfQl4FvNDuyBa51FMm/kBCEIBQOqku/SfU0Ls3aiRhaEfA/6YKpoMY6Y0YKhuA/DngIC0l0c8pjaL+Kg3Or+bYy6HSFRhwuU5xTlTjEXtb7qC9G4AAA==",
+ "isTaproot": true
+ },
+ {
+ "description": "Finalze taproot script-path with two tapleafs signed (chooses the one with the shortest path)",
+ "psbt": "cHNidP8BAF4CAAAAAZsypn8thOjCXwg39Z1pAQCp7ndI8Jrw/8SGSmhgRiJpAAAAAAD/////AZBBBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzQAAAAAAAEBK6BoBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzRBFAUf68vx5rAfWwxMqZNce2OVVqlduXONBcm2JEPAT2Fc2gpbUnze59ZSHmflF7erkp8eUGBfJBmAcUZG5WBbC/pAJihlaGGDKsSd04fSyIk5WDACUedUvDE1C4zD80ubbX5qFzgxizY7pGG5zrrGAoW0KOEJ8TW4WEmh/WsE0erDxEEUIKvXw9clC9dJYjC2Woo/TrIhnRP9powDMmB/GF4rBRsRCNvHrz3TPp4Id2O6W4qhmerk87mqanTdLIapgqEamkDnpOB240Ce+yZlOPO3sxZDmpXhmsxtIl6a/R6TS2ekf2VCdZXg4Dqy3OpjzxQ/jDMbXbzETL2mdHlsgQnBnUcoYhXBr6rSntatZCCpyXS0zh+WAkLdMI4XHA0rVYAbFyHRzrqYKqOqTjCjec9FzSPuoLS+tXB4VvMgzUTs3kZS/bDfqlRY6TCnm2dtB3ED1fA6rlsUAqbWuurDp2W9ONMZj7NiIyAFH+vL8eawH1sMTKmTXHtjlVapXblzjQXJtiRDwE9hXKzAohXBr6rSntatZCCpyXS0zh+WAkLdMI4XHA0rVYAbFyHRzrq0JN6gn4QLkyoANzzc29JWULjDrP5UqfSmQaKGchuNJtrHlXZrvaHq6qReW/oKlQ/dX0xKraWx8wgu3JaJuf0K7iijuCxK7L69N4En3IjdGeHqk+26eie3VOHIgdMIh0q9q7FEmRK2sbmE7NhWpbGiU/lLBm6XT+D9HvPO+T63rSMgIKvXw9clC9dJYjC2Woo/TrIhnRP9powDMmB/GF4rBRuswAAA",
+ "result": "cHNidP8BAF4CAAAAAZsypn8thOjCXwg39Z1pAQCp7ndI8Jrw/8SGSmhgRiJpAAAAAAD/////AZBBBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzQAAAAAAAEBK6BoBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzQBCMcDQCYoZWhhgyrEndOH0siJOVgwAlHnVLwxNQuMw/NLm21+ahc4MYs2O6Rhuc66xgKFtCjhCfE1uFhJof1rBNHqw8QiIAUf68vx5rAfWwxMqZNce2OVVqlduXONBcm2JEPAT2FcrGHBr6rSntatZCCpyXS0zh+WAkLdMI4XHA0rVYAbFyHRzrqYKqOqTjCjec9FzSPuoLS+tXB4VvMgzUTs3kZS/bDfqlRY6TCnm2dtB3ED1fA6rlsUAqbWuurDp2W9ONMZj7NiAAA=",
+ "isTaproot": true
+ },
+ {
+ "description": "Finalze taproot script-path with two tapleafs signed (explicitly choose leaf)",
+ "psbt": "cHNidP8BAF4CAAAAAZsypn8thOjCXwg39Z1pAQCp7ndI8Jrw/8SGSmhgRiJpAAAAAAD/////AZBBBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzQAAAAAAAEBK6BoBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzRBFAUf68vx5rAfWwxMqZNce2OVVqlduXONBcm2JEPAT2Fc2gpbUnze59ZSHmflF7erkp8eUGBfJBmAcUZG5WBbC/pAJihlaGGDKsSd04fSyIk5WDACUedUvDE1C4zD80ubbX5qFzgxizY7pGG5zrrGAoW0KOEJ8TW4WEmh/WsE0erDxEEUIKvXw9clC9dJYjC2Woo/TrIhnRP9powDMmB/GF4rBRsRCNvHrz3TPp4Id2O6W4qhmerk87mqanTdLIapgqEamkDnpOB240Ce+yZlOPO3sxZDmpXhmsxtIl6a/R6TS2ekf2VCdZXg4Dqy3OpjzxQ/jDMbXbzETL2mdHlsgQnBnUcoYhXBr6rSntatZCCpyXS0zh+WAkLdMI4XHA0rVYAbFyHRzrqYKqOqTjCjec9FzSPuoLS+tXB4VvMgzUTs3kZS/bDfqlRY6TCnm2dtB3ED1fA6rlsUAqbWuurDp2W9ONMZj7NiIyAFH+vL8eawH1sMTKmTXHtjlVapXblzjQXJtiRDwE9hXKzAohXBr6rSntatZCCpyXS0zh+WAkLdMI4XHA0rVYAbFyHRzrq0JN6gn4QLkyoANzzc29JWULjDrP5UqfSmQaKGchuNJtrHlXZrvaHq6qReW/oKlQ/dX0xKraWx8wgu3JaJuf0K7iijuCxK7L69N4En3IjdGeHqk+26eie3VOHIgdMIh0q9q7FEmRK2sbmE7NhWpbGiU/lLBm6XT+D9HvPO+T63rSMgIKvXw9clC9dJYjC2Woo/TrIhnRP9powDMmB/GF4rBRuswAAA",
+ "result": "cHNidP8BAF4CAAAAAZsypn8thOjCXwg39Z1pAQCp7ndI8Jrw/8SGSmhgRiJpAAAAAAD/////AZBBBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzQAAAAAAAEBK6BoBgAAAAAAIlEgFa11cf11Y+PGzl0UxS5788pld5zElAl9YY0OtTHhHzQBCMcDQCYoZWhhgyrEndOH0siJOVgwAlHnVLwxNQuMw/NLm21+ahc4MYs2O6Rhuc66xgKFtCjhCfE1uFhJof1rBNHqw8QiIAUf68vx5rAfWwxMqZNce2OVVqlduXONBcm2JEPAT2FcrGHBr6rSntatZCCpyXS0zh+WAkLdMI4XHA0rVYAbFyHRzrqYKqOqTjCjec9FzSPuoLS+tXB4VvMgzUTs3kZS/bDfqlRY6TCnm2dtB3ED1fA6rlsUAqbWuurDp2W9ONMZj7NiAAA=",
+ "isTaproot": true
}
],
"extractor": [
@@ -338,6 +473,51 @@
"index": 2
},
"equals": "cHNidP8BADMCAAAAAQABAgMEBQYHCAkKCwwNDg8AAQIDBAUGBwgJCgsMDQ4PAgAAAAD/////AAAAAAAAAAA="
+ },
+ {
+ "description": "checks for mixed taproot and non-taproot fields",
+ "inputData": {
+ "hash": "Buffer.from('000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f', 'hex')",
+ "index": 0,
+ "redeemScript": "Buffer.from('0014000102030405060708090a0b0c0d0e0f00010000', 'hex')",
+ "tapInternalKey": "Buffer.from('000102030405060708090a0b0c0d0e0f00010203040506070000000000000000', 'hex')"
+ },
+ "exception": "Invalid arguments for Psbt.addInput. Cannot use both taproot and non-taproot fields."
+ },
+ {
+ "description": "checks for tapleaf in taptree",
+ "inputData": {
+ "hash": "Buffer.from('000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f', 'hex')",
+ "index": 0,
+ "tapMerkleRoot": "Buffer.from('5cf5873456b400364b18bdc9a1a233870fa622a6010deef1d4e08474f3a103b4', 'hex')",
+ "tapLeafScript": [
+ {
+ "leafVersion": 192,
+ "script": "Buffer.from('20d5e347235eba74ae0cec686b668d5a9432f45a555d6ab22cebf5974bde9dc4f3ac', 'hex')",
+ "controlBlock": "Buffer.from('c0720768b9946ac22371653d92cc343bf109b8a3f819e231159fd4f2b1328944ecb424dea09f840b932a00373cdcdbd25650b8c3acfe54a9f4a641a286721b8d26dac795766bbda1eaeaa45e5bfa0a950fdd5f4c4aada5b1f3082edc9689b9fd0aee28a3b82c4aecbebd378127dc88dd19e1ea93edba7a27b754e1c881d308874a7ac4489544b7b840d045599ec9415ae71ccb090c2c465c05c9fa05f41c7a06d5', 'hex')"
+ }
+ ]
+ },
+ "exception": "Invalid arguments for Psbt.addInput. Tapleaf not part of taptree."
+ }
+ ]
+ },
+ "updateInput": {
+ "checks": [
+ {
+ "description": "checks for new tapleaf in taptree",
+ "psbt": "cHNidP8BADMCAAAAAQ1YzlMrOEvTUad+B8zippgoNUwrXhwvsHMMGwZb6S7LAAAAAAD/////AAAAAAAAAQEroGgGAAAAAAAiUSD3Vv2gTbCrHTNlF18uY/YKLdATh0ph4pwrCyUHkhtZngEYIHsGySMEjYsxl3fCSP6Ap7tG06AUPLXA8nGnwzSEH70VAAA=",
+ "index": 0,
+ "inputData": {
+ "tapLeafScript": [
+ {
+ "leafVersion": 192,
+ "script": "Buffer.from('20d5e347235eba74ae0cec686b668d5a9432f45a555d6ab22cebf5974bde9dc4f3ac', 'hex')",
+ "controlBlock": "Buffer.from('c0720768b9946ac22371653d92cc343bf109b8a3f819e231159fd4f2b1328944ecb424dea09f840b932a00373cdcdbd25650b8c3acfe54a9f4a641a286721b8d26dac795766bbda1eaeaa45e5bfa0a950fdd5f4c4aada5b1f3082edc9689b9fd0aee28a3b82c4aecbebd378127dc88dd19e1ea93edba7a27b754e1c881d308874a7ac4489544b7b840d045599ec9415ae71ccb090c2c465c05c9fa05f41c7a06d5', 'hex')"
+ }
+ ]
+ },
+ "exception": "Invalid arguments for Psbt.updateInput. Tapleaf not part of taptree."
}
]
},
@@ -351,12 +531,133 @@
},
"exception": "Error adding output."
},
+ {
+ "description": "Checks the mandatory values for adding an output",
+ "outputData": {
+ "value": 1000
+ },
+ "exception": "Invalid arguments for Psbt\\.addOutput. Requires single object with at least \\[script or address\\] and \\[value\\]"
+ },
{
"description": "Adds output normally",
"outputData": {
"address": "1P2NFEBp32V2arRwZNww6tgXEV58FG94mr",
"value": 42
}
+ },
+ {
+ "description": "Adds taproot output with internal tap key and tap tree",
+ "isTaproot": true,
+ "psbt": "cHNidP8BADMCAAAAAcbgSGx76du9GXsr4c6Yk7DFglfHi7M2jdCNUXwc+Q+EAAAAAAD/////AAAAAAAAAQEroGgGAAAAAAAiUSClLxmVQ6aZXLEwkYA/WGZuIE91BHT7xP7DIEAvz/NITaIVwd2NnolThxX+QCpgFksj8u93bzuZy6olaHxJNHaArwK1tCTeoJ+EC5MqADc83NvSVlC4w6z+VKn0pkGihnIbjSbax5V2a72h6uqkXlv6CpUP3V9MSq2lsfMILtyWibn9Cu4oo7gsSuy+vTeBJ9yI3Rnh6pPtunont1ThyIHTCIdKesRIlUS3uEDQRVmeyUFa5xzLCQwsRlwFyfoF9Bx6BtUjIFX368Cp2d6uXSbyrn23vC4KMjRRlEssuTTh7ThM6bXQrMAAAA==",
+ "outputData": {
+ "address": "bc1px4ssshedlz4jc56ses3lftz462a06jwy8my4pwpx6twx30vvv6nsgwcpu3",
+ "value": 410000,
+ "tapInternalKey": "Buffer.from('f6d4ce132444de7f0e3a1d2be9b38ceec798cf9a76eeeac585869445830eb167', 'hex')",
+ "tapTree": {
+ "leaves": [
+ {
+ "depth": 2,
+ "leafVersion": 192,
+ "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0ac', 'hex')"
+ },
+ {
+ "depth": 3,
+ "leafVersion": 192,
+ "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac1ac', 'hex')"
+ },
+ {
+ "depth": 3,
+ "leafVersion": 192,
+ "script": "Buffer.from('202258b1c3160be0864a541854eec9164a572f094f7562628281a8073bb89173a7ac', 'hex')"
+ },
+ {
+ "depth": 2,
+ "leafVersion": 192,
+ "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac2ac', 'hex')"
+ },
+ {
+ "depth": 3,
+ "leafVersion": 192,
+ "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac3ac', 'hex')"
+ },
+ {
+ "depth": 4,
+ "leafVersion": 192,
+ "script": "Buffer.from('2050929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4ac', 'hex')"
+ },
+ {
+ "depth": 4,
+ "leafVersion": 192,
+ "script": "Buffer.from('2055f7ebc0a9d9deae5d26f2ae7db7bc2e0a323451944b2cb934e1ed384ce9b5d0ac', 'hex')"
+ }
+ ]
+ }
+ },
+ "result": "cHNidP8BAF4CAAAAAcbgSGx76du9GXsr4c6Yk7DFglfHi7M2jdCNUXwc+Q+EAAAAAAD/////AZBBBgAAAAAAIlEgNWEIXy34qyxTUMwj9KxV0rr9ScQ+yVC4JtLcaL2MZqcAAAAAAAEBK6BoBgAAAAAAIlEgpS8ZlUOmmVyxMJGAP1hmbiBPdQR0+8T+wyBAL8/zSE2iFcHdjZ6JU4cV/kAqYBZLI/Lvd287mcuqJWh8STR2gK8CtbQk3qCfhAuTKgA3PNzb0lZQuMOs/lSp9KZBooZyG40m2seVdmu9oerqpF5b+gqVD91fTEqtpbHzCC7clom5/QruKKO4LErsvr03gSfciN0Z4eqT7bp6J7dU4ciB0wiHSnrESJVEt7hA0EVZnslBWuccywkMLEZcBcn6BfQcegbVIyBV9+vAqdnerl0m8q59t7wuCjI0UZRLLLk04e04TOm10KzAAAEFIPbUzhMkRN5/DjodK+mzjO7HmM+adu7qxYWGlEWDDrFnAQb9AwECwCIgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsCsA8AiIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrBrAPAIiAiWLHDFgvghkpUGFTuyRZKVy8JT3ViYoKBqAc7uJFzp6wCwCIgUJKbdMGgSVS3i0tgNel6XgeKWg8o7JbVR7/ums6AOsKsA8AiIFCSm3TBoElUt4tLYDXpel4HiloPKOyW1Ue/7prOgDrDrATAIiBQkpt0waBJVLeLS2A16XpeB4paDyjsltVHv+6azoA6xKwEwCIgVffrwKnZ3q5dJvKufbe8LgoyNFGUSyy5NOHtOEzptdCsAA=="
+ },
+ {
+ "description": "Adds taproot output with internal tap key and correct address",
+ "isTaproot": true,
+ "psbt": "cHNidP8BAFwCAAAAAk9Ve2845C8v//JR71uKzf70FjeNfx7SvB4l3A+Q44UKAAAAAAD/////FIp10hu+RPgTSFGPmjwb01Tf/a5UkcPFUpOw/X6UEPYAAAAAAP////8AAAAAAAABASugaAYAAAAAACJRINqadqaDhePTT29qdKQScUmyJxEeDw12HLjkMYMVSlnkARcgDN4bJzix3HdE8+FUM6UgA1lEKpgpIImVUePmAGYh7yYAAQDAAgAAAAFbIoaAJqDd/f4mZppgYV4UR9bgU8lT9pTH4/aDJdFUWgAAAABrSDBFAiEA6JS0xMdSQhG+9gAPSxGs6HazyauyNUBMwrmF386IAxwCIGiyH9QHCzKOBTtg+VsISF4nUi9NfAtJAtC02J03+I07ASEDu1tkEI8W1bd6qrPZ3uLaHvE90BDUvRCwvTTzPYPXzwf/////AaBoBgAAAAAAGXapFL5QEf51k4TA7Mp5d18IM2ddq79oiKwAAAAAAAA=",
+ "outputData": {
+ "tapInternalKey": "Buffer.from('884d969439deced21d1ab71ecd9ef9a6a8795215588ce7eff4ad5efc903e40ec', 'hex')",
+ "address": "bc1p74zfvfd7rndcn8lvzuec4hj4kzp9nnjvapvx5fgtqsegzz656hhsee8kwu",
+ "value": 410000
+ },
+ "result": "cHNidP8BAIcCAAAAAk9Ve2845C8v//JR71uKzf70FjeNfx7SvB4l3A+Q44UKAAAAAAD/////FIp10hu+RPgTSFGPmjwb01Tf/a5UkcPFUpOw/X6UEPYAAAAAAP////8BkEEGAAAAAAAiUSD1RJYlvhzbiZ/sFzOK3lWwglnOTOhYaiULBDKBC1TV7wAAAAAAAQEroGgGAAAAAAAiUSDamnamg4Xj009vanSkEnFJsicRHg8Ndhy45DGDFUpZ5AEXIAzeGyc4sdx3RPPhVDOlIANZRCqYKSCJlVHj5gBmIe8mAAEAwAIAAAABWyKGgCag3f3+JmaaYGFeFEfW4FPJU/aUx+P2gyXRVFoAAAAAa0gwRQIhAOiUtMTHUkIRvvYAD0sRrOh2s8mrsjVATMK5hd/OiAMcAiBosh/UBwsyjgU7YPlbCEheJ1IvTXwLSQLQtNidN/iNOwEhA7tbZBCPFtW3eqqz2d7i2h7xPdAQ1L0QsL008z2D188H/////wGgaAYAAAAAABl2qRS+UBH+dZOEwOzKeXdfCDNnXau/aIisAAAAAAABBSCITZaUOd7O0h0atx7NnvmmqHlSFViM5+/0rV78kD5A7AA="
+ },
+ {
+ "description": "Adds taproot output with internal tap key and bad address",
+ "isTaproot": true,
+ "psbt": "cHNidP8BAFwCAAAAAk9Ve2845C8v//JR71uKzf70FjeNfx7SvB4l3A+Q44UKAAAAAAD/////FIp10hu+RPgTSFGPmjwb01Tf/a5UkcPFUpOw/X6UEPYAAAAAAP////8AAAAAAAABASugaAYAAAAAACJRINqadqaDhePTT29qdKQScUmyJxEeDw12HLjkMYMVSlnkARcgDN4bJzix3HdE8+FUM6UgA1lEKpgpIImVUePmAGYh7yYAAQDAAgAAAAFbIoaAJqDd/f4mZppgYV4UR9bgU8lT9pTH4/aDJdFUWgAAAABrSDBFAiEA6JS0xMdSQhG+9gAPSxGs6HazyauyNUBMwrmF386IAxwCIGiyH9QHCzKOBTtg+VsISF4nUi9NfAtJAtC02J03+I07ASEDu1tkEI8W1bd6qrPZ3uLaHvE90BDUvRCwvTTzPYPXzwf/////AaBoBgAAAAAAGXapFL5QEf51k4TA7Mp5d18IM2ddq79oiKwAAAAAAAA=",
+ "outputData": {
+ "tapInternalKey": "Buffer.from('884d969439deced21d1ab71ecd9ef9a6a8795215588ce7eff4ad5efc903e40ec', 'hex')",
+ "address": "bc1p3efq8ujsj0qr5xvms7mv89p8cz0crqdtuxe9ms6grqgxc9sgsntslthf6w",
+ "value": 410000
+ },
+ "exception": "Error adding output. Script or address missmatch."
+ },
+ {
+ "description": "Adds taproot output with both taproot and non-taproot fields",
+ "isTaproot": true,
+ "psbt": "cHNidP8BAFwCAAAAAk9Ve2845C8v//JR71uKzf70FjeNfx7SvB4l3A+Q44UKAAAAAAD/////FIp10hu+RPgTSFGPmjwb01Tf/a5UkcPFUpOw/X6UEPYAAAAAAP////8AAAAAAAABASugaAYAAAAAACJRINqadqaDhePTT29qdKQScUmyJxEeDw12HLjkMYMVSlnkARcgDN4bJzix3HdE8+FUM6UgA1lEKpgpIImVUePmAGYh7yYAAQDAAgAAAAFbIoaAJqDd/f4mZppgYV4UR9bgU8lT9pTH4/aDJdFUWgAAAABrSDBFAiEA6JS0xMdSQhG+9gAPSxGs6HazyauyNUBMwrmF386IAxwCIGiyH9QHCzKOBTtg+VsISF4nUi9NfAtJAtC02J03+I07ASEDu1tkEI8W1bd6qrPZ3uLaHvE90BDUvRCwvTTzPYPXzwf/////AaBoBgAAAAAAGXapFL5QEf51k4TA7Mp5d18IM2ddq79oiKwAAAAAAAA=",
+ "outputData": {
+ "address": "bc1px4ssshedlz4jc56ses3lftz462a06jwy8my4pwpx6twx30vvv6nsgwcpu3",
+ "value": 410000,
+ "tapInternalKey": "Buffer.from('884d969439deced21d1ab71ecd9ef9a6a8795215588ce7eff4ad5efc903e40ec', 'hex')",
+ "redeemScript": "Buffer.from('5221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae', 'hex')"
+ },
+ "exception": "Invalid arguments for Psbt.addOutput. Cannot use both taproot and non-taproot fields."
+ },
+ {
+ "description": "Adds output after tapkey signature has been added",
+ "isTaproot": true,
+ "psbt": "cHNidP8BAIcCAAAAAvz6EyMK2VQ0DFKWZyIyGZLZCnaIDtG5RI55OrGVs6u7AAAAAAD/////oB9A2l2NN5WPNjBDaRsz9Nmla1WdM3jl96ElSV5WWHMAAAAAAP////8BkEEGAAAAAAAiUSCnucwjGG2x9zGqP+E0PwxTkptd/9AYMW12ch7ffaogBAAAAAAAAQEroGgGAAAAAAAiUSDbRhL5phuWYLyqKbSqVb/Pic2mpPL46idVKQur9i06ugETQOPidQWL9dHDxd819Pn5QNNGyH0fmDv58pexSJVDalZ0W/HPVliPFZy7s//0TfTbvFce87/nKXcrwz5YuItIY6cBFyAOgJIoTQyYcpAqNfsK9CutgWckFE3mBMgpL50lKXb7oQABAL8CAAAAAcdkwPNWdYy1O8QM1xRkrJjas72QGQPEanYEpv6iT3PXAAAAAGpHMEQCIFpl6Nx8hoZA1o5Huo958RS/Hndy+DloLhp0nvRyBzN8AiB9jEbitThSs6TZ1XVBHZebj4nZbLMg3CCtss8WSZpv7QEhA+VxIxwCWeqoeDMYcEw3EC0ZPxIkOqt0YSK9Y3hAKTd1/////wGgaAYAAAAAABl2qRS9gA7n636cw49cUBJWm7IDJPKuuoisAAAAACICAknGKVC7w5Ek4l3IK5XCUwLNCeU4DD+76cdMAYDB72g3RzBEAiAjekFbBddaHY/LZidwKd3sBbifjmbEpka2Ps0B2Crg+wIgUoN4BhGCwMRTDaHbAX+0MXSGYA9nSlqeQJXpWQRCBfQBAAEFIE6DvlLK0f4BrtpMgez01WQYKLUQZ8HtbfuDPD52hmSvAA==",
+ "outputData": {
+ "address": "bc1px4ssshedlz4jc56ses3lftz462a06jwy8my4pwpx6twx30vvv6nsgwcpu3",
+ "value": 410000
+ },
+ "exception": "Can not modify transaction, signatures exist."
+ },
+ {
+ "description": "Adds output after tap key input has been finalized",
+ "isTaproot": true,
+ "psbt": "cHNidP8BAF4CAAAAAfyhtBTm+3OPYBuMHvdPMf9jqniZDY925hbmnt8hxbA6AAAAAAD/////AZBBBgAAAAAAIlEgV6CyzSs5a6Tc8A+TYOiozWnh6FRH9E/VWFanhMAEbLEAAAAAAAEBK6BoBgAAAAAAIlEgV6CyzSs5a6Tc8A+TYOiozWnh6FRH9E/VWFanhMAEbLEBCEIBQOAqWBD/2jhPWzQvesT8sjkN2Cowphp3QvmlWbHiLx753ChcUovvWyBlWiCq77Kk+lZGEhC4vjClSjc26br+dc8AAA==",
+ "outputData": {
+ "address": "bc1px4ssshedlz4jc56ses3lftz462a06jwy8my4pwpx6twx30vvv6nsgwcpu3",
+ "value": 410000
+ },
+ "exception": "Can not modify transaction, signatures exist."
+ },
+ {
+ "description": "Adds output after tapscript signatures have been added",
+ "isTaproot": true,
+ "psbt": "cHNidP8BAF4CAAAAAa/0mhnSBXdEBKbbMC+2hm6AZZtCLBxBeubd1sjtau5dAAAAAAD/////AZBBBgAAAAAAIlEgISRIfamb9rCYzad52ikfoUUuFlvyTcImZMavR0jEaUQAAAAAAAEBK6BoBgAAAAAAIlEgISRIfamb9rCYzad52ikfoUUuFlvyTcImZMavR0jEaURBFBnEcOpwiHjNYPtWJOrQ8Pgc9bxBKyZh/i2D837Z1rC8BibL1C4Z/5e6dKzWfkzpsIbE5WEVn1bYpAAjrqIKMHlAKkl3w3Gfpkl9b0yDVbTlZd4yCEL9V2DJs6zpPrEmn3wiohBy8wwE6EZ0FxQdrCupnHKXhHBjpcHVwfJRQfcy9EEULx1ijisiHgGb/9/hBNhsIOv1ZyWsfmi/Ql+oz7AOuqAGJsvULhn/l7p0rNZ+TOmwhsTlYRWfVtikACOuogoweUBKNkxBf6vT8m7ISt1WikLWW9udCP7OQLXztr1IPalJT5z+esAWmgeLS7QoLgzTu8AnYp/rHxsgZ6CgiV8tlkciQRRXE7VxCk67h7Ee6CbSgNyotChx7CgwNTfxdJkyvCS0DgYmy9QuGf+XunSs1n5M6bCGxOVhFZ9W2KQAI66iCjB5QFN6DGtLlSIFBjZbdh3rbKBtBcEDSiEcuVxnSPpdM1RnQRmw5Ujo+/76wZfmGBMFzV0IA7vnHzzXN73jT6O8/wJiFcDBdB6IhNxYUBYgZT1K7FG5SblQ3S6nQMKRLc2vPcA0BhpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdZpIFcTtXEKTruHsR7oJtKA3Ki0KHHsKDA1N/F0mTK8JLQOrCAvHWKOKyIeAZv/3+EE2Gwg6/VnJax+aL9CX6jPsA66oLogGcRw6nCIeM1g+1Yk6tDw+Bz1vEErJmH+LYPzftnWsLy6U5zAAAA=",
+ "outputData": {
+ "address": "bc1px4ssshedlz4jc56ses3lftz462a06jwy8my4pwpx6twx30vvv6nsgwcpu3",
+ "value": 410000
+ },
+ "exception": "Can not modify transaction, signatures exist."
}
]
},
@@ -459,10 +760,32 @@
"description": "allows signing non-whitelisted sighashtype when explicitly passed in",
"shouldSign": {
"psbt": "cHNidP8BADMBAAAAAYaq+PdOUY2PnV9kZKa82XlqrPByOqwH2TRg2+LQdqs2AAAAAAD/////AAAAAAAAAQEgAOH1BQAAAAAXqRSTNeWHqa9INvSnQ120SZeJc+6JSocBAwSBAAAAAQQWABQvLKRyDqYsPYImhD3eURpDGL10RwAA",
- "sighashTypes": [129],
+ "sighashTypes": [
+ 129
+ ],
"inputToCheck": 0,
"WIF": "KxnAnQh6UJBxLF8Weup77yn8tWhLHhDhnXeyJuzmmcZA5aRdMJni"
}
+ },
+ {
+ "description": "checks taproot key-path signer (tweaked key) matches internal tap key",
+ "isTaproot": true,
+ "shouldSign": {
+ "psbt": "cHNidP8BAF4CAAAAAf17fGksrz9eKGx1nSU3RX4vcwr7bfNdQzPZ9dSEkWBcAAAAAAD/////AZBBBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQAAAAAAAEBK6BoBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQBFyBbMLiqcPfsndZoMRtk9GKEFd0jSA3rwEa60nKQNwdHrAEYIOUOtfVyfPBm+ZzIsEfwzo2JL2WTslXAznzBllU7Oa4zAAA=",
+ "inputToCheck": 0,
+ "WIF": "KypUz2y1jdgzM8HGDUx9DYLmyzd8EWhruvLX2J5iSL7MiAcc7dBG",
+ "result": "cHNidP8BAF4CAAAAAf17fGksrz9eKGx1nSU3RX4vcwr7bfNdQzPZ9dSEkWBcAAAAAAD/////AZBBBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQAAAAAAAEBK6BoBgAAAAAAIlEgPLBe/d3922lmXjTIt52b9NG1HFDC9jzPCTn111AG8TQBE0AAqkKg+dq3eThMoqzjh214urhgUoGTgwHlNyyMQ2RwhfeRIhAp+m9mZwQoXOxK7p2ILjf2G5j28F9KMhMzH7bXARcgWzC4qnD37J3WaDEbZPRihBXdI0gN68BGutJykDcHR6wBGCDlDrX1cnzwZvmcyLBH8M6NiS9lk7JVwM58wZZVOzmuMwAA"
+ }
+ },
+ {
+ "description": "check taproot script-path signer found for the input",
+ "isTaproot": true,
+ "shouldSign": {
+ "psbt": "cHNidP8BAF4CAAAAAUYAJ/rjwsScVTFXg1in8cNdaBDtwLAsQ6l1O8sWijmlAAAAAAD/////AZBBBgAAAAAAIlEgFlrANpbJgyRLzVMhozrVHKr6rGrz7e2bQ+3TyITRhB4AAAAAAAEBK6BoBgAAAAAAIlEgFlrANpbJgyRLzVMhozrVHKr6rGrz7e2bQ+3TyITRhB5BFNs7WfGuA1DZ7ZorvaC77E4rn/I2hbVtogxHpEnxKZ3DGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdZAbjBxWKodWLnyqPP18sLAjhTc/OXtHVtk+Abc/e8SoTIxaOORlmqOegbCKAUyL4+NFdAlgtcUyHUCxWaxJ3ykxGIVwOpmBmLM44rn+zwtvl1cyWYheMCVYuX2agqeiHOvsrXIGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdYaUpyfs81+d21htiJbbGEOiQb7j6psWaxcPpW1+C0p1mkgq1snxZRfWXi8CMjFSaobG3elbWWeL9QqflyaQtTYEG2sINs7WfGuA1DZ7ZorvaC77E4rn/I2hbVtogxHpEnxKZ3DuiB8Z8sVUSbQp0q8mQiD5n5coMrRzQAASnO9NspgIk9pZ7pTnMAAAA==",
+ "inputToCheck": 0,
+ "WIF": "L2wCzcNaJwG1W9djnumJnPQZTCpfeCkR2wgwfupphmThSrwTMCR6",
+ "result": "cHNidP8BAF4CAAAAAUYAJ/rjwsScVTFXg1in8cNdaBDtwLAsQ6l1O8sWijmlAAAAAAD/////AZBBBgAAAAAAIlEgFlrANpbJgyRLzVMhozrVHKr6rGrz7e2bQ+3TyITRhB4AAAAAAAEBK6BoBgAAAAAAIlEgFlrANpbJgyRLzVMhozrVHKr6rGrz7e2bQ+3TyITRhB5BFKtbJ8WUX1l4vAjIxUmqGxt3pW1lni/UKn5cmkLU2BBtT3q/MbhvStFL2jaKZM6N8TeYvCUfSVytPrzfjkXWQbdAoS5lV4a+yP0Y4nTr7ltbsrwMo+U3TNcC/ebE6U6xk/4er9bXfdvZNvgR+TgqrO/iZjNNI6QcGaudPObgsgYKTEEU2ztZ8a4DUNntmiu9oLvsTiuf8jaFtW2iDEekSfEpncMaUpyfs81+d21htiJbbGEOiQb7j6psWaxcPpW1+C0p1kBuMHFYqh1YufKo8/XywsCOFNz85e0dW2T4Btz97xKhMjFo45GWao56BsIoBTIvj40V0CWC1xTIdQLFZrEnfKTEYhXA6mYGYszjiuf7PC2+XVzJZiF4wJVi5fZqCp6Ic6+ytcgaUpyfs81+d21htiJbbGEOiQb7j6psWaxcPpW1+C0p1hpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWaSCrWyfFlF9ZeLwIyMVJqhsbd6VtZZ4v1Cp+XJpC1NgQbawg2ztZ8a4DUNntmiu9oLvsTiuf8jaFtW2iDEekSfEpncO6IHxnyxVRJtCnSryZCIPmflygytHNAABKc702ymAiT2lnulOcwAAA"
+ }
}
]
},
@@ -512,6 +835,14 @@
}
]
},
+ "finalizeInput": {
+ "finalizeTapleafByHash": {
+ "psbt": "cHNidP8BAF4CAAAAAXbYuDMSrbXuy1CXHZaiA8jjI+wcNkkCOL2dJD2dNf3kAAAAAAD/////AZBBBgAAAAAAIlEgcJi/YJDTvc6x6mRTIYgQG/+fEmLs2RjsO/FHW5okOEsAAAAAAAEBK6BoBgAAAAAAIlEgcJi/YJDTvc6x6mRTIYgQG/+fEmLs2RjsO/FHW5okOEtBFG78QHu9pQ+jR/B1+dz9XQtU0jFgoC5AFXcvibFi1jFAEkntiZ6MdFzn0seJJapRqACzEKXd0ZlMa34/iq0HnrVAfYaCk2E35rtXsMfbr/Hrbk/BtLF2VLepE0vIIB3UOWZEUF8akmlnL4Kh0xwMn4Cp99QivnI0B3XOsYfEznfDHUEUesul6blPS+qc/rm41L9nGaAck9KDAZAutbDsNLSZuXXMWh7785JCWLEpzsZRkM/9RJ+EaLYT/0O2sg4cHNHo1EBHFn/xRZno2fX/vLc1l2qHqZGB2vbLkZDj5KsVfqeJd78uIc5b6uAG0K+BBtq2H6HUvl+8RWd2GWmVoMTYppVNYhXAjGyV+gMPwhuPgQ05yTUt2dmE2xJLdkeHBaiap2ej2SuYKqOqTjCjec9FzSPuoLS+tXB4VvMgzUTs3kZS/bDfqvuLvWTUb+e6sUIP9JrqDAHAjkYMd3K3PTfk+B6aMTv4IyB6y6XpuU9L6pz+ubjUv2cZoByT0oMBkC61sOw0tJm5dazAohXAjGyV+gMPwhuPgQ05yTUt2dmE2xJLdkeHBaiap2ej2Su0JN6gn4QLkyoANzzc29JWULjDrP5UqfSmQaKGchuNJtrHlXZrvaHq6qReW/oKlQ/dX0xKraWx8wgu3JaJuf0K7iijuCxK7L69N4En3IjdGeHqk+26eie3VOHIgdMIh0q48S7pX3ESdKuEJ4Jo9s+PwRFNvGdokOHBSefrgpE1JCMgbvxAe72lD6NH8HX53P1dC1TSMWCgLkAVdy+JsWLWMUCswAAA",
+ "index": 0,
+ "leafHash": "1249ed899e8c745ce7d2c78925aa51a800b310a5ddd1994c6b7e3f8aad079eb5",
+ "result": "cHNidP8BAF4CAAAAAXbYuDMSrbXuy1CXHZaiA8jjI+wcNkkCOL2dJD2dNf3kAAAAAAD/////AZBBBgAAAAAAIlEgcJi/YJDTvc6x6mRTIYgQG/+fEmLs2RjsO/FHW5okOEsAAAAAAAEBK6BoBgAAAAAAIlEgcJi/YJDTvc6x6mRTIYgQG/+fEmLs2RjsO/FHW5okOEsBCP0HAQNAfYaCk2E35rtXsMfbr/Hrbk/BtLF2VLepE0vIIB3UOWZEUF8akmlnL4Kh0xwMn4Cp99QivnI0B3XOsYfEznfDHSIgbvxAe72lD6NH8HX53P1dC1TSMWCgLkAVdy+JsWLWMUCsocCMbJX6Aw/CG4+BDTnJNS3Z2YTbEkt2R4cFqJqnZ6PZK7Qk3qCfhAuTKgA3PNzb0lZQuMOs/lSp9KZBooZyG40m2seVdmu9oerqpF5b+gqVD91fTEqtpbHzCC7clom5/QruKKO4LErsvr03gSfciN0Z4eqT7bp6J7dU4ciB0wiHSrjxLulfcRJ0q4Qngmj2z4/BEU28Z2iQ4cFJ5+uCkTUkAAA="
+ }
+ },
"finalizeAllInputs": [
{
"type": "P2PK",
@@ -551,6 +882,18 @@
"incorrectPubkey": "Buffer.from('029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e02a', 'hex')",
"nonExistantIndex": 42
},
+ "validateSignaturesOfTapKeyInput": {
+ "psbt": "cHNidP8BAM8CAAAAAwPzd9k+uLSN1rgF01xY1TIA/8N+YytNZ4VP9gKFP4MyAAAAAAD/////ZtAAqL2E1fKcmGo+7xuqS+nSQeKFVKGRYaHfIvLXn4sAAAAAAP////9+h+SlCwIx1MUDT7Bek0NrWXS7xnSPi5LbYbDc9sxYIgAAAAAA/////wIgKRsAAAAAACJRIEb2SXyy8Z1Qw+npgqlQ3MhiFLAfzOQ3pCBhx72xIw0zuAUBAAAAAAAWABTJijE0v48z5ZmmfEAADXdCBcG0FAAAAAAAAQDiAgAAAAABAUfY2D1t0dyMeEH39C1yOdIxigpqm7XJNqHVT3Lc+FkiAAAAAAD+////AhIsGwAAAAAAGXapFJ5+8XZ3ZP80oFldvEwrcNsBftBmiKyYdK6xAAAAABepFLDBn59UffGbX7u/olyFDG0eG1UJhwJHMEQCIDAd3s05C61flXVFqOtov0NoHRGr8KFcOpH6R/81F46EAiBt+j9hHyvT2hYEyf8fdYsM9IgbnybtPV+kRTHDa6Rj0AEhAmmZfwmoHsmCkEOn9AfRTh+863mURelmE8hSqL4MG1EydJwgACICAi5ovBH1xLoGxPqtFh48wUEpnM+St1SbPzRwO7kBNKOQRzBEAiBpWClBybtHveXkhAgTiE8QSczMJs8MGuH4LOSNRA6s/AIgWlbB3xJOtJIsszj1qZ/whA5jK9wnTzeZzDlVs/ivq2cBAAEBK7gFAQAAAAAAIlEglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4BE0Cd7/ny+QreV7urBWKNroQWCvnZczwkU0kLZiKsJQjtftKHWXMknftjt1d4K6aPYH7cBXzhlrUF+2GovjYLccZeARcglCHnNLD50sRn6n3Rl8Yay0RnzcvJ9MsMVx+LY6XEDK4AAQEfECcAAAAAAAAWABRPoqyhKLb53uUwnE9wBR5Jxl/XMiICA6VOpEBbPJM/xYsqO2euttYFpgec9vcxggyTyoklK660SDBFAiEAoCIktghL55iuMAmkzwYJzb+h+qmNewZXxAx/06ObxIQCIELCsBz/wd2wPlnJb27OluxMkTPnCyHA2C+SxHiX/FvPAQAAAA==",
+ "index": 1,
+ "pubkey": "Buffer.from('024fef5c5163bea69a93e74a59672bbeb081837077cb94cfa6e481a5cf00d8ab18', 'hex')",
+ "incorrectPubkey": "Buffer.from('029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e02a', 'hex')"
+ },
+ "validateSignaturesOfTapScriptInput": {
+ "psbt": "cHNidP8BAF4CAAAAAQU2GLj/HTOEN804Hc6JRNTLM7EmDlIBdjG2G1aUw266AAAAAAD/////AYAaBgAAAAAAIlEg35sLGepBXUbR93XfxDJcYBCHqNVjw/jogOgPVdic1UgAAAAAAAEBK6BoBgAAAAAAIlEgVZx4+tHeORcb0jDJnOytrOnNOGL1uS0MdMUg1GQeS15BFDlfgSndY7SlwvEhJOqgW3p+0w9w5R+5MwXe7MVC5/nrrqI0FdZdKILLLZgRVK8L9Bn2ijU6IcoqqyImKIWt3MtAA3alBoU7IBCkBk9OHD1wE8fJI4y+lbnTRj48e8AAwRM77q3Rml679qCzGvEAKAs99UNMaXHQIhgGfRP11AMlJkEUj4kary4texRheMVKh+Ku3dnR1oZpIleSjmfPCBdP83WuojQV1l0ogsstmBFUrwv0GfaKNTohyiqrIiYoha3cy0DYJZ6Lv7FZPIBRZFfVgF5v3gcRiQnT8aM82Q5IPkwkzZrGo4ThZblvunG/+hu8ZPuJrUU+uXb+s9rcwSH+BihIQRSoqze8FgnYNMOROzU42tHITX+baoNf/BdXd5FaN641cq6iNBXWXSiCyy2YEVSvC/QZ9oo1OiHKKqsiJiiFrdzLQEfQ5UkAg4lTbhJxjMzB7hu6ad1fywYxHCXjFXHHrm5PJTOFJLg2oTnwuQToz/Z2AW/UET7Op+WSoHZvW4tzzLhiFcFmuQP4s1ds7KJtMOh4fTw1QCgxkWUA3FUAuUzKHzjDvhpSnJ+zzX53bWG2IltsYQ6JBvuPqmxZrFw+lbX4LSnWGlKcn7PNfndtYbYiW2xhDokG+4+qbFmsXD6VtfgtKdZpII+JGq8uLXsUYXjFSofirt3Z0daGaSJXko5nzwgXT/N1rCA5X4Ep3WO0pcLxISTqoFt6ftMPcOUfuTMF3uzFQuf567ogqKs3vBYJ2DTDkTs1ONrRyE1/m2qDX/wXV3eRWjeuNXK6U5zAAAA=",
+ "index": 0,
+ "pubkey": "Buffer.from('02395f8129dd63b4a5c2f12124eaa05b7a7ed30f70e51fb93305deecc542e7f9eb', 'hex')",
+ "incorrectPubkey": "Buffer.from('029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e02a', 'hex')"
+ },
"getFeeRate": {
"psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA",
"fee": 21
@@ -565,4 +908,4 @@
"clone": {
"psbt": "cHNidP8BAKYCAAAAAlwKQ3suPWwEJ/zQ9sZsIioOcHKU1KoLMxlMNSXVIkEWAAAAAAD/////YYDJMap+mYgbTrCNAdpWHN+EkKvl+XYao/6co/EQfwMAAAAAAP////8CkF8BAAAAAAAWABRnPBAmVHz2HL+8/1U+QG5L2thjmjhKAAAAAAAAIgAg700yfFRyhWzQnPHIUb/XQqsjlpf4A0uw682pCVWuQ8IAAAAAAAEBKzB1AAAAAAAAIgAgth9oE4cDfC5aV58VgkW5CptHsIxppYzJV8C5kT6aTo8iAgNfNgnFnEPS9s4PY/I5bezpWiAEzQ4DRTASgdSdeMM06UgwRQIhALO0xRpuqP3aVkm+DPykrtqe6fPNSgNblp9K9MAwmtHJAiAHV5ZvZN8Vi49n/o9ISFyvtHsPXXPKqBxC9m2m2HlpYgEBBSMhA182CcWcQ9L2zg9j8jlt7OlaIATNDgNFMBKB1J14wzTprAABASuAOAEAAAAAACIAILYfaBOHA3wuWlefFYJFuQqbR7CMaaWMyVfAuZE+mk6PIgIDXzYJxZxD0vbOD2PyOW3s6VogBM0OA0UwEoHUnXjDNOlIMEUCIQC6XN6tpo9mYlZej4BXSSh5D1K6aILBfQ4WBWXUrISx6wIgVaxFUsz8y59xJ1V4sZ1qarHX9pZ+MJmLKbl2ZSadisoBAQUjIQNfNgnFnEPS9s4PY/I5bezpWiAEzQ4DRTASgdSdeMM06awAAAA="
}
-}
+}
\ No newline at end of file
diff --git a/test/integration/taproot.md b/test/integration/taproot.md
index 401034061..7627450e2 100644
--- a/test/integration/taproot.md
+++ b/test/integration/taproot.md
@@ -9,8 +9,9 @@ A simple keyspend example that is possible with the current API is below.
## TODO
-- [ ] p2tr payment API to make script spends easier
+- [x] p2tr payment API to make script spends easier
- [ ] Support within the Psbt class
+ - partial support added
## Example
diff --git a/test/integration/taproot.spec.ts b/test/integration/taproot.spec.ts
index f7b3733fa..b12c46f31 100644
--- a/test/integration/taproot.spec.ts
+++ b/test/integration/taproot.spec.ts
@@ -1,11 +1,20 @@
import BIP32Factory from 'bip32';
+import ECPairFactory from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import { describe, it } from 'mocha';
-import * as bitcoin from '../..';
+import { PsbtInput, TapLeafScript } from 'bip174/src/lib/interfaces';
import { regtestUtils } from './_regtest';
+import * as bitcoin from '../..';
+import { Taptree } from '../../src/types';
+import { toXOnly, tapTreeToList, tapTreeFromList } from '../../src/psbt/bip371';
+import { witnessStackToScriptWitness } from '../../src/psbt/psbtutils';
+import { TapLeaf } from 'bip174/src/lib/interfaces';
+
const rng = require('randombytes');
const regtest = regtestUtils.network;
+bitcoin.initEccLib(ecc);
const bip32 = BIP32Factory(ecc);
+const ECPair = ECPairFactory(ecc);
describe('bitcoinjs-lib (transaction with taproot)', () => {
it('can create (and broadcast via 3PBP) a taproot keyspend Transaction', async () => {
@@ -42,8 +51,550 @@ describe('bitcoinjs-lib (transaction with taproot)', () => {
value: sendAmount,
});
});
+
+ it('can create (and broadcast via 3PBP) a taproot key-path spend Transaction', async () => {
+ const internalKey = bip32.fromSeed(rng(64), regtest);
+ const p2pkhKey = bip32.fromSeed(rng(64), regtest);
+
+ const { output } = bitcoin.payments.p2tr({
+ internalPubkey: toXOnly(internalKey.publicKey),
+ network: regtest,
+ });
+
+ const { output: p2pkhOutput } = bitcoin.payments.p2pkh({
+ pubkey: p2pkhKey.publicKey,
+ network: regtest,
+ });
+
+ // amount from faucet
+ const amount = 42e4;
+ // amount to send
+ const sendAmount = amount - 1e4;
+ // get faucet
+ const unspent = await regtestUtils.faucetComplex(output!, amount);
+
+ // non segwit utxo
+ const p2pkhUnspent = await regtestUtils.faucetComplex(p2pkhOutput!, amount);
+ const utx = await regtestUtils.fetch(p2pkhUnspent.txId);
+ const nonWitnessUtxo = Buffer.from(utx.txHex, 'hex');
+
+ const psbt = new bitcoin.Psbt({ network: regtest });
+ psbt.addInput({
+ hash: unspent.txId,
+ index: 0,
+ witnessUtxo: { value: amount, script: output! },
+ tapInternalKey: toXOnly(internalKey.publicKey),
+ });
+ psbt.addInput({ index: 0, hash: p2pkhUnspent.txId, nonWitnessUtxo });
+
+ const sendInternalKey = bip32.fromSeed(rng(64), regtest);
+ const sendPubKey = toXOnly(sendInternalKey.publicKey);
+ const { address: sendAddress } = bitcoin.payments.p2tr({
+ internalPubkey: sendPubKey,
+ network: regtest,
+ });
+
+ psbt.addOutput({
+ value: sendAmount,
+ address: sendAddress!,
+ tapInternalKey: sendPubKey,
+ });
+
+ const tweakedSigner = tweakSigner(internalKey!, { network: regtest });
+ await psbt.signInputAsync(0, tweakedSigner);
+ await psbt.signInputAsync(1, p2pkhKey);
+
+ psbt.finalizeAllInputs();
+ const tx = psbt.extractTransaction();
+ const rawTx = tx.toBuffer();
+
+ const hex = rawTx.toString('hex');
+
+ await regtestUtils.broadcast(hex);
+ await regtestUtils.verify({
+ txId: tx.getId(),
+ address: sendAddress!,
+ vout: 0,
+ value: sendAmount,
+ });
+ });
+
+ it('can create (and broadcast via 3PBP) a taproot key-path spend Transaction (with unused scriptTree)', async () => {
+ const internalKey = bip32.fromSeed(rng(64), regtest);
+ const leafKey = bip32.fromSeed(rng(64), regtest);
+
+ const leafScriptAsm = `${toXOnly(leafKey.publicKey).toString(
+ 'hex',
+ )} OP_CHECKSIG`;
+ const leafScript = bitcoin.script.fromASM(leafScriptAsm);
+
+ const scriptTree = {
+ output: leafScript,
+ };
+
+ const { output, address, hash } = bitcoin.payments.p2tr({
+ internalPubkey: toXOnly(internalKey.publicKey),
+ scriptTree,
+ network: regtest,
+ });
+
+ // amount from faucet
+ const amount = 42e4;
+ // amount to send
+ const sendAmount = amount - 1e4;
+ // get faucet
+ const unspent = await regtestUtils.faucetComplex(output!, amount);
+
+ const psbt = new bitcoin.Psbt({ network: regtest });
+ psbt.addInput({
+ hash: unspent.txId,
+ index: 0,
+ witnessUtxo: { value: amount, script: output! },
+ tapInternalKey: toXOnly(internalKey.publicKey),
+ tapMerkleRoot: hash,
+ });
+ psbt.addOutput({ value: sendAmount, address: address! });
+
+ const tweakedSigner = tweakSigner(internalKey!, {
+ tweakHash: hash,
+ network: regtest,
+ });
+ psbt.signInput(0, tweakedSigner);
+
+ psbt.finalizeAllInputs();
+ const tx = psbt.extractTransaction();
+ const rawTx = tx.toBuffer();
+
+ const hex = rawTx.toString('hex');
+
+ await regtestUtils.broadcast(hex);
+ await regtestUtils.verify({
+ txId: tx.getId(),
+ address: address!,
+ vout: 0,
+ value: sendAmount,
+ });
+ });
+
+ it('can create (and broadcast via 3PBP) a taproot script-path spend Transaction - OP_CHECKSIG', async () => {
+ const internalKey = bip32.fromSeed(rng(64), regtest);
+ const leafKey = bip32.fromSeed(rng(64), regtest);
+
+ const leafScriptAsm = `${toXOnly(leafKey.publicKey).toString(
+ 'hex',
+ )} OP_CHECKSIG`;
+ const leafScript = bitcoin.script.fromASM(leafScriptAsm);
+
+ const scriptTree: Taptree = [
+ [
+ {
+ output: bitcoin.script.fromASM(
+ '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG',
+ ),
+ },
+ [
+ {
+ output: bitcoin.script.fromASM(
+ '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac1 OP_CHECKSIG',
+ ),
+ },
+ {
+ output: bitcoin.script.fromASM(
+ '2258b1c3160be0864a541854eec9164a572f094f7562628281a8073bb89173a7 OP_CHECKSIG',
+ ),
+ },
+ ],
+ ],
+ [
+ {
+ output: bitcoin.script.fromASM(
+ '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac2 OP_CHECKSIG',
+ ),
+ },
+ [
+ {
+ output: bitcoin.script.fromASM(
+ '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac3 OP_CHECKSIG',
+ ),
+ },
+ [
+ {
+ output: bitcoin.script.fromASM(
+ '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac4 OP_CHECKSIG',
+ ),
+ },
+ {
+ output: leafScript,
+ },
+ ],
+ ],
+ ],
+ ];
+ const redeem = {
+ output: leafScript,
+ redeemVersion: 192,
+ };
+
+ const { output, witness } = bitcoin.payments.p2tr({
+ internalPubkey: toXOnly(internalKey.publicKey),
+ scriptTree,
+ redeem,
+ network: regtest,
+ });
+
+ // amount from faucet
+ const amount = 42e4;
+ // amount to send
+ const sendAmount = amount - 1e4;
+ // get faucet
+ const unspent = await regtestUtils.faucetComplex(output!, amount);
+
+ const psbt = new bitcoin.Psbt({ network: regtest });
+ psbt.addInput({
+ hash: unspent.txId,
+ index: 0,
+ witnessUtxo: { value: amount, script: output! },
+ });
+ psbt.updateInput(0, {
+ tapLeafScript: [
+ {
+ leafVersion: redeem.redeemVersion,
+ script: redeem.output,
+ controlBlock: witness![witness!.length - 1],
+ },
+ ],
+ });
+
+ const sendInternalKey = bip32.fromSeed(rng(64), regtest);
+ const sendPubKey = toXOnly(sendInternalKey.publicKey);
+ const { address: sendAddress } = bitcoin.payments.p2tr({
+ internalPubkey: sendPubKey,
+ scriptTree,
+ network: regtest,
+ });
+
+ psbt.addOutput({
+ value: sendAmount,
+ address: sendAddress!,
+ tapInternalKey: sendPubKey,
+ tapTree: { leaves: tapTreeToList(scriptTree) },
+ });
+
+ psbt.signInput(0, leafKey);
+ psbt.finalizeInput(0);
+ const tx = psbt.extractTransaction();
+ const rawTx = tx.toBuffer();
+ const hex = rawTx.toString('hex');
+
+ await regtestUtils.broadcast(hex);
+ await regtestUtils.verify({
+ txId: tx.getId(),
+ address: sendAddress!,
+ vout: 0,
+ value: sendAmount,
+ });
+ });
+
+ it('can create (and broadcast via 3PBP) a taproot script-path spend Transaction - OP_CHECKSEQUENCEVERIFY', async () => {
+ const internalKey = bip32.fromSeed(rng(64), regtest);
+ const leafKey = bip32.fromSeed(rng(64), regtest);
+ const leafPubkey = toXOnly(leafKey.publicKey).toString('hex');
+
+ const leafScriptAsm = `OP_10 OP_CHECKSEQUENCEVERIFY OP_DROP ${leafPubkey} OP_CHECKSIG`;
+ const leafScript = bitcoin.script.fromASM(leafScriptAsm);
+
+ const scriptTree: Taptree = [
+ {
+ output: bitcoin.script.fromASM(
+ '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG',
+ ),
+ },
+ [
+ {
+ output: bitcoin.script.fromASM(
+ '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG',
+ ),
+ },
+ {
+ output: leafScript,
+ },
+ ],
+ ];
+ const redeem = {
+ output: leafScript,
+ redeemVersion: 192,
+ };
+
+ const { output, witness } = bitcoin.payments.p2tr({
+ internalPubkey: toXOnly(internalKey.publicKey),
+ scriptTree,
+ redeem,
+ network: regtest,
+ });
+
+ // amount from faucet
+ const amount = 42e4;
+ // amount to send
+ const sendAmount = amount - 1e4;
+ // get faucet
+ const unspent = await regtestUtils.faucetComplex(output!, amount);
+
+ const psbt = new bitcoin.Psbt({ network: regtest });
+ psbt.addInput({
+ hash: unspent.txId,
+ index: 0,
+ sequence: 10,
+ witnessUtxo: { value: amount, script: output! },
+ });
+ psbt.updateInput(0, {
+ tapLeafScript: [
+ {
+ leafVersion: redeem.redeemVersion,
+ script: redeem.output,
+ controlBlock: witness![witness!.length - 1],
+ },
+ ],
+ });
+
+ const sendInternalKey = bip32.fromSeed(rng(64), regtest);
+ const sendPubKey = toXOnly(sendInternalKey.publicKey);
+ const { address: sendAddress } = bitcoin.payments.p2tr({
+ internalPubkey: sendPubKey,
+ scriptTree,
+ network: regtest,
+ });
+
+ psbt.addOutput({ value: sendAmount, address: sendAddress! });
+ // just to test that updateOutput works as expected
+ psbt.updateOutput(0, {
+ tapInternalKey: sendPubKey,
+ tapTree: { leaves: tapTreeToList(scriptTree) },
+ });
+
+ await psbt.signInputAsync(0, leafKey);
+
+ psbt.finalizeInput(0);
+ const tx = psbt.extractTransaction();
+ const rawTx = tx.toBuffer();
+ const hex = rawTx.toString('hex');
+
+ try {
+ // broadcast before the confirmation period has expired
+ await regtestUtils.broadcast(hex);
+ throw new Error('Broadcast should fail.');
+ } catch (err) {
+ if ((err as any).message !== 'non-BIP68-final')
+ throw new Error(
+ 'Expected OP_CHECKSEQUENCEVERIFY validation to fail. But it faild with: ' +
+ err,
+ );
+ }
+ await regtestUtils.mine(10);
+ await regtestUtils.broadcast(hex);
+ await regtestUtils.verify({
+ txId: tx.getId(),
+ address: sendAddress!,
+ vout: 0,
+ value: sendAmount,
+ });
+ });
+
+ it('can create (and broadcast via 3PBP) a taproot script-path spend Transaction - OP_CHECKSIGADD (3-of-3)', async () => {
+ const internalKey = bip32.fromSeed(rng(64), regtest);
+
+ const leafKeys = [];
+ const leafPubkeys = [];
+ for (let i = 0; i < 3; i++) {
+ const leafKey = bip32.fromSeed(rng(64), regtest);
+ leafKeys.push(leafKey);
+ leafPubkeys.push(toXOnly(leafKey.publicKey).toString('hex'));
+ }
+
+ const leafScriptAsm = `${leafPubkeys[2]} OP_CHECKSIG ${
+ leafPubkeys[1]
+ } OP_CHECKSIGADD ${leafPubkeys[0]} OP_CHECKSIGADD OP_3 OP_NUMEQUAL`;
+
+ const leafScript = bitcoin.script.fromASM(leafScriptAsm);
+
+ const scriptTree: Taptree = [
+ {
+ output: bitcoin.script.fromASM(
+ '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG',
+ ),
+ },
+ [
+ {
+ output: bitcoin.script.fromASM(
+ '50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0 OP_CHECKSIG',
+ ),
+ },
+ {
+ output: leafScript,
+ },
+ ],
+ ];
+ const redeem = {
+ output: leafScript,
+ redeemVersion: 192,
+ };
+
+ const { output, address, witness } = bitcoin.payments.p2tr({
+ internalPubkey: toXOnly(internalKey.publicKey),
+ scriptTree,
+ redeem,
+ network: regtest,
+ });
+
+ // amount from faucet
+ const amount = 42e4;
+ // amount to send
+ const sendAmount = amount - 1e4;
+ // get faucet
+ const unspent = await regtestUtils.faucetComplex(output!, amount);
+
+ const psbt = new bitcoin.Psbt({ network: regtest });
+ psbt.addInput({
+ hash: unspent.txId,
+ index: 0,
+ witnessUtxo: { value: amount, script: output! },
+ });
+ psbt.updateInput(0, {
+ tapLeafScript: [
+ {
+ leafVersion: redeem.redeemVersion,
+ script: redeem.output,
+ controlBlock: witness![witness!.length - 1],
+ },
+ ],
+ });
+
+ psbt.addOutput({ value: sendAmount, address: address! });
+
+ // random order for signers
+ psbt.signInput(0, leafKeys[1]);
+ psbt.signInput(0, leafKeys[2]);
+ psbt.signInput(0, leafKeys[0]);
+
+ psbt.finalizeInput(0);
+ const tx = psbt.extractTransaction();
+ const rawTx = tx.toBuffer();
+ const hex = rawTx.toString('hex');
+
+ await regtestUtils.broadcast(hex);
+ await regtestUtils.verify({
+ txId: tx.getId(),
+ address: address!,
+ vout: 0,
+ value: sendAmount,
+ });
+ });
+
+ it('can create (and broadcast via 3PBP) a taproot script-path spend Transaction - custom finalizer', async () => {
+ const leafCount = 8;
+ const leaves = Array.from({ length: leafCount }).map(
+ (_, index) =>
+ ({
+ depth: 3,
+ leafVersion: 192,
+ script: bitcoin.script.fromASM(`OP_ADD OP_${index * 2} OP_EQUAL`),
+ } as TapLeaf),
+ );
+ const scriptTree = tapTreeFromList(leaves);
+
+ for (let leafIndex = 1; leafIndex < leafCount; leafIndex++) {
+ const redeem = {
+ output: bitcoin.script.fromASM(`OP_ADD OP_${leafIndex * 2} OP_EQUAL`),
+ redeemVersion: 192,
+ };
+
+ const internalKey = bip32.fromSeed(rng(64), regtest);
+ const { output, witness } = bitcoin.payments.p2tr({
+ internalPubkey: toXOnly(internalKey.publicKey),
+ scriptTree,
+ redeem,
+ network: regtest,
+ });
+
+ // amount from faucet
+ const amount = 42e4;
+ // amount to send
+ const sendAmount = amount - 1e4;
+ // get faucet
+ const unspent = await regtestUtils.faucetComplex(output!, amount);
+
+ const psbt = new bitcoin.Psbt({ network: regtest });
+ psbt.addInput({
+ hash: unspent.txId,
+ index: 0,
+ witnessUtxo: { value: amount, script: output! },
+ });
+
+ const tapLeafScript: TapLeafScript = {
+ leafVersion: redeem.redeemVersion,
+ script: redeem.output,
+ controlBlock: witness![witness!.length - 1],
+ };
+ psbt.updateInput(0, { tapLeafScript: [tapLeafScript] });
+
+ const sendAddress =
+ 'bcrt1pqknex3jwpsaatu5e5dcjw70nac3fr5k5y3hcxr4hgg6rljzp59nqs6a0vh';
+ psbt.addOutput({
+ value: sendAmount,
+ address: sendAddress,
+ });
+
+ const leafIndexFinalizerFn = buildLeafIndexFinalizer(
+ tapLeafScript,
+ leafIndex,
+ );
+ psbt.finalizeInput(0, leafIndexFinalizerFn);
+ const tx = psbt.extractTransaction();
+ const rawTx = tx.toBuffer();
+ const hex = rawTx.toString('hex');
+
+ await regtestUtils.broadcast(hex);
+ await regtestUtils.verify({
+ txId: tx.getId(),
+ address: sendAddress!,
+ vout: 0,
+ value: sendAmount,
+ });
+ }
+ });
});
+function buildLeafIndexFinalizer(
+ tapLeafScript: TapLeafScript,
+ leafIndex: number,
+): (
+ inputIndex: number,
+ _input: PsbtInput,
+ _tapLeafHashToFinalize?: Buffer,
+) => {
+ finalScriptWitness: Buffer | undefined;
+} {
+ return (
+ inputIndex: number,
+ _input: PsbtInput,
+ _tapLeafHashToFinalize?: Buffer,
+ ): {
+ finalScriptWitness: Buffer | undefined;
+ } => {
+ try {
+ const scriptSolution = [
+ Buffer.from([leafIndex]),
+ Buffer.from([leafIndex]),
+ ];
+ const witness = scriptSolution
+ .concat(tapLeafScript.script)
+ .concat(tapLeafScript.controlBlock);
+ return { finalScriptWitness: witnessStackToScriptWitness(witness) };
+ } catch (err) {
+ throw new Error(`Can not finalize taproot input #${inputIndex}: ${err}`);
+ }
+ };
+}
+
// Order of the curve (N) - 1
const N_LESS_1 = Buffer.from(
'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
@@ -59,7 +610,7 @@ const ONE = Buffer.from(
// (This is recommended by BIP341)
function createKeySpendOutput(publicKey: Buffer): Buffer {
// x-only pubkey (remove 1 byte y parity)
- const myXOnlyPubkey = publicKey.slice(1, 33);
+ const myXOnlyPubkey = toXOnly(publicKey);
const commitHash = bitcoin.crypto.taggedHash('TapTweak', myXOnlyPubkey);
const tweakResult = ecc.xOnlyPointAddTweak(myXOnlyPubkey, commitHash);
if (tweakResult === null) throw new Error('Invalid Tweak');
@@ -86,7 +637,7 @@ function signTweaked(messageHash: Buffer, key: KeyPair): Uint8Array {
: ecc.privateAdd(ecc.privateSub(N_LESS_1, key.privateKey!)!, ONE)!;
const tweakHash = bitcoin.crypto.taggedHash(
'TapTweak',
- key.publicKey.slice(1, 33),
+ toXOnly(key.publicKey),
);
const newPrivateKey = ecc.privateAdd(privateKey!, tweakHash);
if (newPrivateKey === null) throw new Error('Invalid Tweak');
@@ -120,3 +671,34 @@ function createSigned(
tx.ins[0].witness = [signature];
return tx;
}
+
+// This logic will be extracted to ecpair
+function tweakSigner(signer: bitcoin.Signer, opts: any = {}): bitcoin.Signer {
+ // @ts-ignore
+ let privateKey: Uint8Array | undefined = signer.privateKey!;
+ if (!privateKey) {
+ throw new Error('Private key is required for tweaking signer!');
+ }
+ if (signer.publicKey[0] === 3) {
+ privateKey = ecc.privateNegate(privateKey);
+ }
+
+ const tweakedPrivateKey = ecc.privateAdd(
+ privateKey,
+ tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash),
+ );
+ if (!tweakedPrivateKey) {
+ throw new Error('Invalid tweaked private key!');
+ }
+
+ return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), {
+ network: opts.network,
+ });
+}
+
+function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer {
+ return bitcoin.crypto.taggedHash(
+ 'TapTweak',
+ Buffer.concat(h ? [pubKey, h] : [pubKey]),
+ );
+}
diff --git a/test/payments.spec.ts b/test/payments.spec.ts
index bc123cba3..07b1442f9 100644
--- a/test/payments.spec.ts
+++ b/test/payments.spec.ts
@@ -1,113 +1,124 @@
import * as assert from 'assert';
+import * as ecc from 'tiny-secp256k1';
import { describe, it } from 'mocha';
import { PaymentCreator } from '../src/payments';
import * as u from './payments.utils';
-['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh'].forEach(p => {
- describe(p, () => {
- let fn: PaymentCreator;
- const payment = require('../src/payments/' + p);
- if (p === 'embed') {
- fn = payment.p2data;
- } else {
- fn = payment[p];
- }
- const fixtures = require('./fixtures/' + p);
+import { initEccLib } from '../src';
- fixtures.valid.forEach((f: any) => {
- it(f.description + ' as expected', () => {
- const args = u.preform(f.arguments);
- const actual = fn(args, f.options);
-
- u.equate(actual, f.expected, f.arguments);
+['embed', 'p2ms', 'p2pk', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'].forEach(
+ p => {
+ describe(p, () => {
+ beforeEach(() => {
+ initEccLib(p === 'p2tr' ? ecc : undefined);
});
+ let fn: PaymentCreator;
+ const payment = require('../src/payments/' + p);
+ if (p === 'embed') {
+ fn = payment.p2data;
+ } else {
+ fn = payment[p];
+ }
- it(f.description + ' as expected (no validation)', () => {
- const args = u.preform(f.arguments);
- const actual = fn(
- args,
- Object.assign({}, f.options, {
- validate: false,
- }),
- );
+ const fixtures = require('./fixtures/' + p);
- u.equate(actual, f.expected, f.arguments);
- });
- });
-
- fixtures.invalid.forEach((f: any) => {
- it(
- 'throws ' + f.exception + (f.description ? 'for ' + f.description : ''),
- () => {
+ fixtures.valid.forEach((f: any) => {
+ it(f.description + ' as expected', () => {
const args = u.preform(f.arguments);
+ const actual = fn(args, f.options);
- assert.throws(() => {
- fn(args, f.options);
- }, new RegExp(f.exception));
- },
- );
- });
-
- if (p === 'p2sh') {
- const p2wsh = require('../src/payments/p2wsh').p2wsh;
- const p2pk = require('../src/payments/p2pk').p2pk;
- it('properly assembles nested p2wsh with names', () => {
- const actual = fn({
- redeem: p2wsh({
- redeem: p2pk({
- pubkey: Buffer.from(
- '03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058',
- 'hex',
- ),
- }),
- }),
+ u.equate(actual, f.expected, f.arguments);
});
- assert.strictEqual(
- actual.address,
- '3MGbrbye4ttNUXM8WAvBFRKry4fkS9fjuw',
- );
- assert.strictEqual(actual.name, 'p2sh-p2wsh-p2pk');
- assert.strictEqual(actual.redeem!.name, 'p2wsh-p2pk');
- assert.strictEqual(actual.redeem!.redeem!.name, 'p2pk');
- });
- }
- // cross-verify dynamically too
- if (!fixtures.dynamic) return;
- const { depends, details } = fixtures.dynamic;
+ it(f.description + ' as expected (no validation)', () => {
+ const args = u.preform(f.arguments);
+ const actual = fn(
+ args,
+ Object.assign({}, f.options, {
+ validate: false,
+ }),
+ );
- details.forEach((f: any) => {
- const detail = u.preform(f);
- const disabled: any = {};
- if (f.disabled)
- f.disabled.forEach((k: string) => {
- disabled[k] = true;
+ u.equate(actual, f.expected, f.arguments);
});
+ });
- for (const key in depends) {
- if (key in disabled) continue;
- const dependencies = depends[key];
+ fixtures.invalid.forEach((f: any) => {
+ it(
+ 'throws ' +
+ f.exception +
+ (f.description ? 'for ' + f.description : ''),
+ () => {
+ const args = u.preform(f.arguments);
- dependencies.forEach((dependency: any) => {
- if (!Array.isArray(dependency)) dependency = [dependency];
+ assert.throws(() => {
+ fn(args, f.options);
+ }, new RegExp(f.exception));
+ },
+ );
+ });
- const args = {};
- dependency.forEach((d: any) => {
- u.from(d, detail, args);
+ if (p === 'p2sh') {
+ const p2wsh = require('../src/payments/p2wsh').p2wsh;
+ const p2pk = require('../src/payments/p2pk').p2pk;
+ it('properly assembles nested p2wsh with names', () => {
+ const actual = fn({
+ redeem: p2wsh({
+ redeem: p2pk({
+ pubkey: Buffer.from(
+ '03e15819590382a9dd878f01e2f0cbce541564eb415e43b440472d883ecd283058',
+ 'hex',
+ ),
+ }),
+ }),
});
- const expected = u.from(key, detail);
-
- it(
- f.description +
- ', ' +
- key +
- ' derives from ' +
- JSON.stringify(dependency),
- () => {
- u.equate(fn(args), expected);
- },
+ assert.strictEqual(
+ actual.address,
+ '3MGbrbye4ttNUXM8WAvBFRKry4fkS9fjuw',
);
+ assert.strictEqual(actual.name, 'p2sh-p2wsh-p2pk');
+ assert.strictEqual(actual.redeem!.name, 'p2wsh-p2pk');
+ assert.strictEqual(actual.redeem!.redeem!.name, 'p2pk');
});
}
+
+ // cross-verify dynamically too
+ if (!fixtures.dynamic) return;
+ const { depends, details } = fixtures.dynamic;
+
+ details.forEach((f: any) => {
+ const detail = u.preform(f);
+ const disabled: any = {};
+ if (f.disabled)
+ f.disabled.forEach((k: string) => {
+ disabled[k] = true;
+ });
+
+ for (const key in depends) {
+ if (key in disabled) continue;
+ const dependencies = depends[key];
+
+ dependencies.forEach((dependency: any) => {
+ if (!Array.isArray(dependency)) dependency = [dependency];
+
+ const args = {};
+ dependency.forEach((d: any) => {
+ u.from(d, detail, args);
+ });
+ const expected = u.from(key, detail);
+
+ it(
+ f.description +
+ ', ' +
+ key +
+ ' derives from ' +
+ JSON.stringify(dependency),
+ () => {
+ u.equate(fn(args), expected);
+ },
+ );
+ });
+ }
+ });
});
- });
-});
+ },
+);
diff --git a/test/payments.utils.ts b/test/payments.utils.ts
index c0635f3cf..e2599204c 100644
--- a/test/payments.utils.ts
+++ b/test/payments.utils.ts
@@ -52,6 +52,12 @@ function equateBase(a: any, b: any, context: string): void {
tryHex(b.witness),
`Inequal ${context}witness`,
);
+ if ('redeemVersion' in b)
+ t.strictEqual(
+ a.redeemVersion,
+ b.redeemVersion,
+ `Inequal ${context}redeemVersion`,
+ );
}
export function equate(a: any, b: any, args?: any): void {
@@ -62,10 +68,12 @@ export function equate(a: any, b: any, args?: any): void {
if (b.input === null) b.input = undefined;
if (b.output === null) b.output = undefined;
if (b.witness === null) b.witness = undefined;
+ if (b.redeemVersion === null) b.redeemVersion = undefined;
if (b.redeem) {
if (b.redeem.input === null) b.redeem.input = undefined;
if (b.redeem.output === null) b.redeem.output = undefined;
if (b.redeem.witness === null) b.redeem.witness = undefined;
+ if (b.redeem.redeemVersion === null) b.redeem.redeemVersion = undefined;
}
equateBase(a, b, '');
@@ -86,6 +94,12 @@ export function equate(a: any, b: any, args?: any): void {
t.strictEqual(tryHex(a.hash), tryHex(b.hash), 'Inequal *.hash');
if ('pubkey' in b)
t.strictEqual(tryHex(a.pubkey), tryHex(b.pubkey), 'Inequal *.pubkey');
+ if ('internalPubkey' in b)
+ t.strictEqual(
+ tryHex(a.internalPubkey),
+ tryHex(b.internalPubkey),
+ 'Inequal *.internalPubkey',
+ );
if ('signature' in b)
t.strictEqual(
tryHex(a.signature),
@@ -129,6 +143,7 @@ export function preform(x: any): any {
if (x.data) x.data = x.data.map(fromHex);
if (x.hash) x.hash = Buffer.from(x.hash, 'hex');
if (x.pubkey) x.pubkey = Buffer.from(x.pubkey, 'hex');
+ if (x.internalPubkey) x.internalPubkey = Buffer.from(x.internalPubkey, 'hex');
if (x.signature) x.signature = Buffer.from(x.signature, 'hex');
if (x.pubkeys) x.pubkeys = x.pubkeys.map(fromHex);
if (x.signatures)
@@ -147,6 +162,7 @@ export function preform(x: any): any {
x.redeem.network = (BNETWORKS as any)[x.redeem.network];
}
+ if (x.scriptTree) x.scriptTree = convertScriptTree(x.scriptTree);
return x;
}
@@ -169,3 +185,15 @@ export function from(path: string, object: any, result?: any): any {
return result;
}
+
+export function convertScriptTree(scriptTree: any, leafVersion?: number): any {
+ if (Array.isArray(scriptTree))
+ return scriptTree.map(tr => convertScriptTree(tr, leafVersion));
+
+ const script = Object.assign({}, scriptTree);
+ if (typeof script.output === 'string') {
+ script.output = asmToBuffer(scriptTree.output);
+ script.version = script.version || leafVersion;
+ }
+ return script;
+}
diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts
index f583e8068..6d295a80f 100644
--- a/test/psbt.spec.ts
+++ b/test/psbt.spec.ts
@@ -5,12 +5,19 @@ import * as crypto from 'crypto';
import ECPairFactory from 'ecpair';
import { describe, it } from 'mocha';
+import { convertScriptTree } from './payments.utils';
+import { LEAF_VERSION_TAPSCRIPT } from '../src/payments/bip341';
+import { tapTreeToList, tapTreeFromList } from '../src/psbt/bip371';
+import { Taptree } from '../src/types';
+import { initEccLib } from '../src';
+
const bip32 = BIP32Factory(ecc);
const ECPair = ECPairFactory(ecc);
import { networks as NETWORKS, payments, Psbt, Signer, SignerAsync } from '..';
import * as preFixtures from './fixtures/psbt.json';
+import * as taprootFixtures from './fixtures/p2tr.json';
const validator = (
pubkey: Buffer,
@@ -18,6 +25,12 @@ const validator = (
signature: Buffer,
): boolean => ECPair.fromPublicKey(pubkey).verify(msghash, signature);
+const schnorrValidator = (
+ pubkey: Buffer,
+ msghash: Buffer,
+ signature: Buffer,
+): boolean => ecc.verifySchnorr(msghash, pubkey, signature);
+
const initBuffers = (object: any): typeof preFixtures =>
JSON.parse(JSON.stringify(object), (_, value) => {
const regex = new RegExp(/^Buffer.from\(['"](.*)['"], ['"](.*)['"]\)$/);
@@ -72,6 +85,10 @@ const failedAsyncSigner = (publicKey: Buffer): SignerAsync => {
// const b = (hex: string) => Buffer.from(hex, 'hex');
describe(`Psbt`, () => {
+ beforeEach(() => {
+ // provide the ECC lib only when required
+ initEccLib(undefined);
+ });
describe('BIP174 Test Vectors', () => {
fixtures.bip174.invalid.forEach(f => {
it(`Invalid: ${f.description}`, () => {
@@ -115,6 +132,7 @@ describe(`Psbt`, () => {
fixtures.bip174.updater.forEach(f => {
it('Updates PSBT to the expected result', () => {
+ if (f.isTaproot) initEccLib(ecc);
const psbt = Psbt.fromBase64(f.psbt);
for (const inputOrOutput of ['input', 'output']) {
@@ -133,11 +151,19 @@ describe(`Psbt`, () => {
fixtures.bip174.signer.forEach(f => {
it('Signs PSBT to the expected result', () => {
+ if (f.isTaproot) initEccLib(ecc);
const psbt = Psbt.fromBase64(f.psbt);
- f.keys.forEach(({ inputToSign, WIF }) => {
+ // @ts-ignore // cannot find tapLeafHashToSign
+ f.keys.forEach(({ inputToSign, tapLeafHashToSign, WIF }) => {
const keyPair = ECPair.fromWIF(WIF, NETWORKS.testnet);
- psbt.signInput(inputToSign, keyPair);
+ if (tapLeafHashToSign)
+ psbt.signTaprootInput(
+ inputToSign,
+ keyPair,
+ Buffer.from(tapLeafHashToSign, 'hex'),
+ );
+ else psbt.signInput(inputToSign, keyPair);
});
assert.strictEqual(psbt.toBase64(), f.result);
@@ -160,6 +186,7 @@ describe(`Psbt`, () => {
fixtures.bip174.finalizer.forEach(f => {
it('Finalizes inputs and gives the expected PSBT', () => {
+ if (f.isTaproot) initEccLib(ecc);
const psbt = Psbt.fromBase64(f.psbt);
psbt.finalizeAllInputs();
@@ -209,6 +236,7 @@ describe(`Psbt`, () => {
describe('signInputAsync', () => {
fixtures.signInput.checks.forEach(f => {
it(f.description, async () => {
+ if (f.isTaproot) initEccLib(ecc);
if (f.shouldSign) {
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt);
await assert.doesNotReject(async () => {
@@ -217,14 +245,22 @@ describe(`Psbt`, () => {
ECPair.fromWIF(f.shouldSign.WIF),
f.shouldSign.sighashTypes || undefined,
);
+ if (f.shouldSign.result)
+ assert.strictEqual(
+ psbtThatShouldsign.toBase64(),
+ f.shouldSign.result,
+ );
});
+ const failMessage = f.isTaproot
+ ? /Need Schnorr Signer to sign taproot input #0./
+ : /sign failed/;
await assert.rejects(async () => {
await psbtThatShouldsign.signInputAsync(
f.shouldSign.inputToCheck,
failedAsyncSigner(ECPair.fromWIF(f.shouldSign.WIF).publicKey),
f.shouldSign.sighashTypes || undefined,
);
- }, /sign failed/);
+ }, failMessage);
}
if (f.shouldThrow) {
@@ -256,6 +292,7 @@ describe(`Psbt`, () => {
describe('signInput', () => {
fixtures.signInput.checks.forEach(f => {
it(f.description, () => {
+ if (f.isTaproot) initEccLib(ecc);
if (f.shouldSign) {
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt);
assert.doesNotThrow(() => {
@@ -288,6 +325,7 @@ describe(`Psbt`, () => {
fixtures.signInput.checks.forEach(f => {
if (f.description === 'checks the input exists') return;
it(f.description, async () => {
+ if (f.isTaproot) initEccLib(ecc);
if (f.shouldSign) {
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt);
await assert.doesNotReject(async () => {
@@ -318,6 +356,7 @@ describe(`Psbt`, () => {
fixtures.signInput.checks.forEach(f => {
if (f.description === 'checks the input exists') return;
it(f.description, () => {
+ if (f.isTaproot) initEccLib(ecc);
if (f.shouldSign) {
const psbtThatShouldsign = Psbt.fromBase64(f.shouldSign.psbt);
assert.doesNotThrow(() => {
@@ -468,6 +507,42 @@ describe(`Psbt`, () => {
});
});
+ describe('finalizeInput', () => {
+ it(`Finalizes tapleaf by hash`, () => {
+ const f = fixtures.finalizeInput.finalizeTapleafByHash;
+ const psbt = Psbt.fromBase64(f.psbt);
+
+ psbt.finalizeTaprootInput(f.index, Buffer.from(f.leafHash, 'hex'));
+
+ assert.strictEqual(psbt.toBase64(), f.result);
+ });
+
+ it(`fails if tapleaf hash not found`, () => {
+ const f = fixtures.finalizeInput.finalizeTapleafByHash;
+ const psbt = Psbt.fromBase64(f.psbt);
+
+ assert.throws(() => {
+ psbt.finalizeTaprootInput(
+ f.index,
+ Buffer.from(f.leafHash, 'hex').reverse(),
+ );
+ }, new RegExp('Can not finalize taproot input #0. Signature for tapleaf script not found.'));
+ });
+
+ it(`fails if trying to finalzie non-taproot input`, () => {
+ const psbt = new Psbt();
+ psbt.addInput({
+ hash:
+ '0000000000000000000000000000000000000000000000000000000000000000',
+ index: 0,
+ });
+
+ assert.throws(() => {
+ psbt.finalizeTaprootInput(0);
+ }, new RegExp('Cannot finalize input #0. Not Taproot.'));
+ });
+ });
+
describe('finalizeAllInputs', () => {
fixtures.finalizeAllInputs.forEach(f => {
it(`Finalizes inputs of type "${f.type}"`, () => {
@@ -530,10 +605,25 @@ describe(`Psbt`, () => {
});
});
+ describe('updateInput', () => {
+ fixtures.updateInput.checks.forEach(f => {
+ it(f.description, () => {
+ const psbt = Psbt.fromBase64(f.psbt);
+
+ if (f.exception) {
+ assert.throws(() => {
+ psbt.updateInput(f.index, f.inputData as any);
+ }, new RegExp(f.exception));
+ }
+ });
+ });
+ });
+
describe('addOutput', () => {
fixtures.addOutput.checks.forEach(f => {
it(f.description, () => {
- const psbt = new Psbt();
+ if (f.isTaproot) initEccLib(ecc);
+ const psbt = f.psbt ? Psbt.fromBase64(f.psbt) : new Psbt();
if (f.exception) {
assert.throws(() => {
@@ -546,6 +636,9 @@ describe(`Psbt`, () => {
assert.doesNotThrow(() => {
psbt.addOutput(f.outputData as any);
});
+ if (f.result) {
+ assert.strictEqual(psbt.toBase64(), f.result);
+ }
assert.doesNotThrow(() => {
psbt.addOutputs([f.outputData as any]);
});
@@ -952,6 +1045,135 @@ describe(`Psbt`, () => {
});
});
+ describe('validateSignaturesOfTapKeyInput', () => {
+ const f = fixtures.validateSignaturesOfTapKeyInput;
+ it('Correctly validates all signatures', () => {
+ initEccLib(ecc);
+ const psbt = Psbt.fromBase64(f.psbt);
+ assert.strictEqual(
+ psbt.validateSignaturesOfInput(f.index, schnorrValidator),
+ true,
+ );
+ });
+
+ it('Correctly validates a signature against a pubkey', () => {
+ initEccLib(ecc);
+ const psbt = Psbt.fromBase64(f.psbt);
+ assert.strictEqual(
+ psbt.validateSignaturesOfInput(
+ f.index,
+ schnorrValidator,
+ f.pubkey as any,
+ ),
+ true,
+ );
+ assert.throws(() => {
+ psbt.validateSignaturesOfInput(
+ f.index,
+ schnorrValidator,
+ f.incorrectPubkey as any,
+ );
+ }, new RegExp('No signatures for this pubkey'));
+ });
+ });
+
+ describe('validateSignaturesOfTapScriptInput', () => {
+ const f = fixtures.validateSignaturesOfTapScriptInput;
+ it('Correctly validates all signatures', () => {
+ initEccLib(ecc);
+ const psbt = Psbt.fromBase64(f.psbt);
+ assert.strictEqual(
+ psbt.validateSignaturesOfInput(f.index, schnorrValidator),
+ true,
+ );
+ });
+
+ it('Correctly validates a signature against a pubkey', () => {
+ initEccLib(ecc);
+ const psbt = Psbt.fromBase64(f.psbt);
+ assert.strictEqual(
+ psbt.validateSignaturesOfInput(
+ f.index,
+ schnorrValidator,
+ f.pubkey as any,
+ ),
+ true,
+ );
+ assert.throws(() => {
+ psbt.validateSignaturesOfInput(
+ f.index,
+ schnorrValidator,
+ f.incorrectPubkey as any,
+ );
+ }, new RegExp('No signatures for this pubkey'));
+ });
+ });
+
+ describe('tapTreeToList/tapTreeFromList', () => {
+ it('Correctly converts a Taptree to a Tapleaf list and back', () => {
+ taprootFixtures.valid
+ .filter(f => f.arguments.scriptTree)
+ .map(f => f.arguments.scriptTree)
+ .forEach(scriptTree => {
+ const originalTree = convertScriptTree(
+ scriptTree,
+ LEAF_VERSION_TAPSCRIPT,
+ );
+ const list = tapTreeToList(originalTree);
+ const treeFromList = tapTreeFromList(list);
+
+ assert.deepStrictEqual(treeFromList, originalTree);
+ });
+ });
+
+ it('Throws if too many leaves on a given level', () => {
+ const list = Array.from({ length: 5 }).map(() => ({
+ depth: 2,
+ leafVersion: LEAF_VERSION_TAPSCRIPT,
+ script: Buffer.from([]),
+ }));
+ assert.throws(() => {
+ tapTreeFromList(list);
+ }, new RegExp('No room left to insert tapleaf in tree'));
+ });
+
+ it('Throws if taptree depth is exceeded', () => {
+ let tree: Taptree = [
+ { output: Buffer.from([]) },
+ { output: Buffer.from([]) },
+ ];
+ Array.from({ length: 129 }).forEach(
+ () => (tree = [tree, { output: Buffer.from([]) }]),
+ );
+ assert.throws(() => {
+ tapTreeToList(tree as Taptree);
+ }, new RegExp('Max taptree depth exceeded.'));
+ });
+
+ it('Throws if tapleaf depth is to high', () => {
+ const list = [
+ {
+ depth: 129,
+ leafVersion: LEAF_VERSION_TAPSCRIPT,
+ script: Buffer.from([]),
+ },
+ ];
+ assert.throws(() => {
+ tapTreeFromList(list);
+ }, new RegExp('Max taptree depth exceeded.'));
+ });
+
+ it('Throws if not a valid taptree structure', () => {
+ const tree = Array.from({ length: 3 }).map(() => ({
+ output: Buffer.from([]),
+ }));
+
+ assert.throws(() => {
+ tapTreeToList(tree as Taptree);
+ }, new RegExp('Cannot convert taptree to tapleaf list. Expecting a tapree structure.'));
+ });
+ });
+
describe('getFeeRate', () => {
it('Throws error if called before inputs are finalized', () => {
const f = fixtures.getFeeRate;
diff --git a/ts_src/address.ts b/ts_src/address.ts
index 753589d46..8004b2668 100644
--- a/ts_src/address.ts
+++ b/ts_src/address.ts
@@ -2,11 +2,9 @@ import { Network } from './networks';
import * as networks from './networks';
import * as payments from './payments';
import * as bscript from './script';
-import * as types from './types';
+import { typeforce, tuple, Hash160bit, UInt8 } from './types';
import { bech32, bech32m } from 'bech32';
import * as bs58check from 'bs58check';
-const { typeforce } = types;
-
export interface Base58CheckResult {
hash: Buffer;
version: number;
@@ -21,7 +19,7 @@ export interface Bech32Result {
const FUTURE_SEGWIT_MAX_SIZE: number = 40;
const FUTURE_SEGWIT_MIN_SIZE: number = 2;
const FUTURE_SEGWIT_MAX_VERSION: number = 16;
-const FUTURE_SEGWIT_MIN_VERSION: number = 1;
+const FUTURE_SEGWIT_MIN_VERSION: number = 2;
const FUTURE_SEGWIT_VERSION_DIFF: number = 0x50;
const FUTURE_SEGWIT_VERSION_WARNING: string =
'WARNING: Sending to a future segwit version address can lead to loss of funds. ' +
@@ -93,7 +91,7 @@ export function fromBech32(address: string): Bech32Result {
}
export function toBase58Check(hash: Buffer, version: number): string {
- typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments);
+ typeforce(tuple(Hash160bit, UInt8), arguments);
const payload = Buffer.allocUnsafe(21);
payload.writeUInt8(version, 0);
@@ -131,6 +129,9 @@ export function fromOutputScript(output: Buffer, network?: Network): string {
try {
return payments.p2wsh({ output, network }).address as string;
} catch (e) {}
+ try {
+ return payments.p2tr({ output, network }).address as string;
+ } catch (e) {}
try {
return _toFutureSegwitAddress(output, network);
} catch (e) {}
@@ -165,6 +166,9 @@ export function toOutputScript(address: string, network?: Network): Buffer {
return payments.p2wpkh({ hash: decodeBech32.data }).output as Buffer;
if (decodeBech32.data.length === 32)
return payments.p2wsh({ hash: decodeBech32.data }).output as Buffer;
+ } else if (decodeBech32.version === 1) {
+ if (decodeBech32.data.length === 32)
+ return payments.p2tr({ pubkey: decodeBech32.data }).output as Buffer;
} else if (
decodeBech32.version >= FUTURE_SEGWIT_MIN_VERSION &&
decodeBech32.version <= FUTURE_SEGWIT_MAX_VERSION &&
diff --git a/ts_src/ecc_lib.ts b/ts_src/ecc_lib.ts
new file mode 100644
index 000000000..eb4c59eeb
--- /dev/null
+++ b/ts_src/ecc_lib.ts
@@ -0,0 +1,95 @@
+import { TinySecp256k1Interface } from './types';
+
+const _ECCLIB_CACHE: { eccLib?: TinySecp256k1Interface } = {};
+
+export function initEccLib(eccLib: TinySecp256k1Interface | undefined): void {
+ if (!eccLib) {
+ // allow clearing the library
+ _ECCLIB_CACHE.eccLib = eccLib;
+ } else if (eccLib !== _ECCLIB_CACHE.eccLib) {
+ // new instance, verify it
+ verifyEcc(eccLib!);
+ _ECCLIB_CACHE.eccLib = eccLib;
+ }
+}
+
+export function getEccLib(): TinySecp256k1Interface {
+ if (!_ECCLIB_CACHE.eccLib)
+ throw new Error(
+ 'No ECC Library provided. You must call initEccLib() with a valid TinySecp256k1Interface instance',
+ );
+ return _ECCLIB_CACHE.eccLib;
+}
+
+const h = (hex: string): Buffer => Buffer.from(hex, 'hex');
+
+function verifyEcc(ecc: TinySecp256k1Interface): void {
+ assert(typeof ecc.isXOnlyPoint === 'function');
+ assert(
+ ecc.isXOnlyPoint(
+ h('79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798'),
+ ),
+ );
+ assert(
+ ecc.isXOnlyPoint(
+ h('fffffffffffffffffffffffffffffffffffffffffffffffffffffffeeffffc2e'),
+ ),
+ );
+ assert(
+ ecc.isXOnlyPoint(
+ h('f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9'),
+ ),
+ );
+ assert(
+ ecc.isXOnlyPoint(
+ h('0000000000000000000000000000000000000000000000000000000000000001'),
+ ),
+ );
+ assert(
+ !ecc.isXOnlyPoint(
+ h('0000000000000000000000000000000000000000000000000000000000000000'),
+ ),
+ );
+ assert(
+ !ecc.isXOnlyPoint(
+ h('fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f'),
+ ),
+ );
+
+ assert(typeof ecc.xOnlyPointAddTweak === 'function');
+ tweakAddVectors.forEach(t => {
+ const r = ecc.xOnlyPointAddTweak(h(t.pubkey), h(t.tweak));
+ if (t.result === null) {
+ assert(r === null);
+ } else {
+ assert(r !== null);
+ assert(r!.parity === t.parity);
+ assert(Buffer.from(r!.xOnlyPubkey).equals(h(t.result)));
+ }
+ });
+}
+
+function assert(bool: boolean): void {
+ if (!bool) throw new Error('ecc library invalid');
+}
+
+const tweakAddVectors = [
+ {
+ pubkey: '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798',
+ tweak: 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
+ parity: -1,
+ result: null,
+ },
+ {
+ pubkey: '1617d38ed8d8657da4d4761e8057bc396ea9e4b9d29776d4be096016dbd2509b',
+ tweak: 'a8397a935f0dfceba6ba9618f6451ef4d80637abf4e6af2669fbc9de6a8fd2ac',
+ parity: 1,
+ result: 'e478f99dab91052ab39a33ea35fd5e6e4933f4d28023cd597c9a1f6760346adf',
+ },
+ {
+ pubkey: '2c0b7cf95324a07d05398b240174dc0c2be444d96b159aa6c7f7b1e668680991',
+ tweak: '823c3cd2142744b075a87eade7e1b8678ba308d566226a0056ca2b7a76f86b47',
+ parity: 0,
+ result: '9534f8dc8c6deda2dc007655981c78b49c5d96c778fbf363462a11ec9dfd948c',
+ },
+];
diff --git a/ts_src/index.ts b/ts_src/index.ts
index d8b8619d1..64c4294c2 100644
--- a/ts_src/index.ts
+++ b/ts_src/index.ts
@@ -29,3 +29,4 @@ export {
StackElement,
} from './payments';
export { Input as TxInput, Output as TxOutput } from './transaction';
+export { initEccLib } from './ecc_lib';
diff --git a/ts_src/ops.ts b/ts_src/ops.ts
index 8e2c41c11..dd8b1e6da 100644
--- a/ts_src/ops.ts
+++ b/ts_src/ops.ts
@@ -127,6 +127,8 @@ const OPS: { [key: string]: number } = {
OP_NOP9: 184,
OP_NOP10: 185,
+ OP_CHECKSIGADD: 186,
+
OP_PUBKEYHASH: 253,
OP_PUBKEY: 254,
OP_INVALIDOPCODE: 255,
diff --git a/ts_src/payments/bip341.ts b/ts_src/payments/bip341.ts
new file mode 100644
index 000000000..5a1dd068d
--- /dev/null
+++ b/ts_src/payments/bip341.ts
@@ -0,0 +1,148 @@
+import { Buffer as NBuffer } from 'buffer';
+import { getEccLib } from '../ecc_lib';
+import * as bcrypto from '../crypto';
+
+import { varuint } from '../bufferutils';
+import { Tapleaf, Taptree, isTapleaf } from '../types';
+
+export const LEAF_VERSION_TAPSCRIPT = 0xc0;
+export const MAX_TAPTREE_DEPTH = 128;
+
+interface HashLeaf {
+ hash: Buffer;
+}
+
+interface HashBranch {
+ hash: Buffer;
+ left: HashTree;
+ right: HashTree;
+}
+
+interface TweakedPublicKey {
+ parity: number;
+ x: Buffer;
+}
+
+const isHashBranch = (ht: HashTree): ht is HashBranch =>
+ 'left' in ht && 'right' in ht;
+
+/**
+ * Binary tree representing leaf, branch, and root node hashes of a Taptree.
+ * Each node contains a hash, and potentially left and right branch hashes.
+ * This tree is used for 2 purposes: Providing the root hash for tweaking,
+ * and calculating merkle inclusion proofs when constructing a control block.
+ */
+export type HashTree = HashLeaf | HashBranch;
+
+export function rootHashFromPath(
+ controlBlock: Buffer,
+ leafHash: Buffer,
+): Buffer {
+ if (controlBlock.length < 33)
+ throw new TypeError(
+ `The control-block length is too small. Got ${
+ controlBlock.length
+ }, expected min 33.`,
+ );
+ const m = (controlBlock.length - 33) / 32;
+
+ let kj = leafHash;
+ for (let j = 0; j < m; j++) {
+ const ej = controlBlock.slice(33 + 32 * j, 65 + 32 * j);
+ if (kj.compare(ej) < 0) {
+ kj = tapBranchHash(kj, ej);
+ } else {
+ kj = tapBranchHash(ej, kj);
+ }
+ }
+
+ return kj;
+}
+
+/**
+ * Build a hash tree of merkle nodes from the scripts binary tree.
+ * @param scriptTree - the tree of scripts to pairwise hash.
+ */
+export function toHashTree(scriptTree: Taptree): HashTree {
+ if (isTapleaf(scriptTree)) return { hash: tapleafHash(scriptTree) };
+
+ const hashes = [toHashTree(scriptTree[0]), toHashTree(scriptTree[1])];
+ hashes.sort((a, b) => a.hash.compare(b.hash));
+ const [left, right] = hashes;
+
+ return {
+ hash: tapBranchHash(left.hash, right.hash),
+ left,
+ right,
+ };
+}
+
+/**
+ * Given a HashTree, finds the path from a particular hash to the root.
+ * @param node - the root of the tree
+ * @param hash - the hash to search for
+ * @returns - array of sibling hashes, from leaf (inclusive) to root
+ * (exclusive) needed to prove inclusion of the specified hash. undefined if no
+ * path is found
+ */
+export function findScriptPath(
+ node: HashTree,
+ hash: Buffer,
+): Buffer[] | undefined {
+ if (isHashBranch(node)) {
+ const leftPath = findScriptPath(node.left, hash);
+ if (leftPath !== undefined) return [...leftPath, node.right.hash];
+
+ const rightPath = findScriptPath(node.right, hash);
+ if (rightPath !== undefined) return [...rightPath, node.left.hash];
+ } else if (node.hash.equals(hash)) {
+ return [];
+ }
+
+ return undefined;
+}
+
+export function tapleafHash(leaf: Tapleaf): Buffer {
+ const version = leaf.version || LEAF_VERSION_TAPSCRIPT;
+ return bcrypto.taggedHash(
+ 'TapLeaf',
+ NBuffer.concat([NBuffer.from([version]), serializeScript(leaf.output)]),
+ );
+}
+
+export function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer {
+ return bcrypto.taggedHash(
+ 'TapTweak',
+ NBuffer.concat(h ? [pubKey, h] : [pubKey]),
+ );
+}
+
+export function tweakKey(
+ pubKey: Buffer,
+ h: Buffer | undefined,
+): TweakedPublicKey | null {
+ if (!NBuffer.isBuffer(pubKey)) return null;
+ if (pubKey.length !== 32) return null;
+ if (h && h.length !== 32) return null;
+
+ const tweakHash = tapTweakHash(pubKey, h);
+
+ const res = getEccLib().xOnlyPointAddTweak(pubKey, tweakHash);
+ if (!res || res.xOnlyPubkey === null) return null;
+
+ return {
+ parity: res.parity,
+ x: NBuffer.from(res.xOnlyPubkey),
+ };
+}
+
+function tapBranchHash(a: Buffer, b: Buffer): Buffer {
+ return bcrypto.taggedHash('TapBranch', NBuffer.concat([a, b]));
+}
+
+function serializeScript(s: Buffer): Buffer {
+ const varintLen = varuint.encodingLength(s.length);
+ const buffer = NBuffer.allocUnsafe(varintLen); // better
+ varuint.encode(s.length, buffer);
+ return NBuffer.concat([buffer, s]);
+}
diff --git a/ts_src/payments/index.ts b/ts_src/payments/index.ts
index 4b7f1117e..ca72f72cb 100644
--- a/ts_src/payments/index.ts
+++ b/ts_src/payments/index.ts
@@ -1,4 +1,5 @@
import { Network } from '../networks';
+import { Taptree } from '../types';
import { p2data as embed } from './embed';
import { p2ms } from './p2ms';
import { p2pk } from './p2pk';
@@ -6,6 +7,7 @@ import { p2pkh } from './p2pkh';
import { p2sh } from './p2sh';
import { p2wpkh } from './p2wpkh';
import { p2wsh } from './p2wsh';
+import { p2tr } from './p2tr';
export interface Payment {
name?: string;
@@ -17,11 +19,14 @@ export interface Payment {
pubkeys?: Buffer[];
input?: Buffer;
signatures?: Buffer[];
+ internalPubkey?: Buffer;
pubkey?: Buffer;
signature?: Buffer;
address?: string;
hash?: Buffer;
redeem?: Payment;
+ redeemVersion?: number;
+ scriptTree?: Taptree;
witness?: Buffer[];
}
@@ -38,7 +43,7 @@ export type StackElement = Buffer | number;
export type Stack = StackElement[];
export type StackFunction = () => Stack;
-export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh };
+export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, p2tr };
// TODO
// witness commitment
diff --git a/ts_src/payments/p2tr.ts b/ts_src/payments/p2tr.ts
new file mode 100644
index 000000000..64c591ffd
--- /dev/null
+++ b/ts_src/payments/p2tr.ts
@@ -0,0 +1,326 @@
+import { Buffer as NBuffer } from 'buffer';
+import { bitcoin as BITCOIN_NETWORK } from '../networks';
+import * as bscript from '../script';
+import { typeforce as typef, isTaptree, TAPLEAF_VERSION_MASK } from '../types';
+import { getEccLib } from '../ecc_lib';
+import {
+ toHashTree,
+ rootHashFromPath,
+ findScriptPath,
+ tapleafHash,
+ tweakKey,
+ LEAF_VERSION_TAPSCRIPT,
+} from './bip341';
+import { Payment, PaymentOpts } from './index';
+import * as lazy from './lazy';
+import { bech32m } from 'bech32';
+
+const OPS = bscript.OPS;
+const TAPROOT_WITNESS_VERSION = 0x01;
+const ANNEX_PREFIX = 0x50;
+
+export function p2tr(a: Payment, opts?: PaymentOpts): Payment {
+ if (
+ !a.address &&
+ !a.output &&
+ !a.pubkey &&
+ !a.internalPubkey &&
+ !(a.witness && a.witness.length > 1)
+ )
+ throw new TypeError('Not enough data');
+
+ opts = Object.assign({ validate: true }, opts || {});
+
+ typef(
+ {
+ address: typef.maybe(typef.String),
+ input: typef.maybe(typef.BufferN(0)),
+ network: typef.maybe(typef.Object),
+ output: typef.maybe(typef.BufferN(34)),
+ internalPubkey: typef.maybe(typef.BufferN(32)),
+ hash: typef.maybe(typef.BufferN(32)), // merkle root hash, the tweak
+ pubkey: typef.maybe(typef.BufferN(32)), // tweaked with `hash` from `internalPubkey`
+ signature: typef.maybe(typef.BufferN(64)),
+ witness: typef.maybe(typef.arrayOf(typef.Buffer)),
+ scriptTree: typef.maybe(isTaptree),
+ redeem: typef.maybe({
+ output: typef.maybe(typef.Buffer), // tapleaf script
+ redeemVersion: typef.maybe(typef.Number), // tapleaf version
+ witness: typef.maybe(typef.arrayOf(typef.Buffer)),
+ }),
+ redeemVersion: typef.maybe(typef.Number),
+ },
+ a,
+ );
+
+ const _address = lazy.value(() => {
+ const result = bech32m.decode(a.address!);
+ const version = result.words.shift();
+ const data = bech32m.fromWords(result.words);
+ return {
+ version,
+ prefix: result.prefix,
+ data: NBuffer.from(data),
+ };
+ });
+
+ // remove annex if present, ignored by taproot
+ const _witness = lazy.value(() => {
+ if (!a.witness || !a.witness.length) return;
+ if (
+ a.witness.length >= 2 &&
+ a.witness[a.witness.length - 1][0] === ANNEX_PREFIX
+ ) {
+ return a.witness.slice(0, -1);
+ }
+ return a.witness.slice();
+ });
+
+ const _hashTree = lazy.value(() => {
+ if (a.scriptTree) return toHashTree(a.scriptTree);
+ if (a.hash) return { hash: a.hash };
+ return;
+ });
+
+ const network = a.network || BITCOIN_NETWORK;
+ const o: Payment = { name: 'p2tr', network };
+
+ lazy.prop(o, 'address', () => {
+ if (!o.pubkey) return;
+
+ const words = bech32m.toWords(o.pubkey);
+ words.unshift(TAPROOT_WITNESS_VERSION);
+ return bech32m.encode(network.bech32, words);
+ });
+
+ lazy.prop(o, 'hash', () => {
+ const hashTree = _hashTree();
+ if (hashTree) return hashTree.hash;
+ const w = _witness();
+ if (w && w.length > 1) {
+ const controlBlock = w[w.length - 1];
+ const leafVersion = controlBlock[0] & TAPLEAF_VERSION_MASK;
+ const script = w[w.length - 2];
+ const leafHash = tapleafHash({ output: script, version: leafVersion });
+ return rootHashFromPath(controlBlock, leafHash);
+ }
+ return null;
+ });
+ lazy.prop(o, 'output', () => {
+ if (!o.pubkey) return;
+ return bscript.compile([OPS.OP_1, o.pubkey]);
+ });
+ lazy.prop(o, 'redeemVersion', () => {
+ if (a.redeemVersion) return a.redeemVersion;
+ if (
+ a.redeem &&
+ a.redeem.redeemVersion !== undefined &&
+ a.redeem.redeemVersion !== null
+ ) {
+ return a.redeem.redeemVersion;
+ }
+
+ return LEAF_VERSION_TAPSCRIPT;
+ });
+ lazy.prop(o, 'redeem', () => {
+ const witness = _witness(); // witness without annex
+ if (!witness || witness.length < 2) return;
+
+ return {
+ output: witness[witness.length - 2],
+ witness: witness.slice(0, -2),
+ redeemVersion: witness[witness.length - 1][0] & TAPLEAF_VERSION_MASK,
+ };
+ });
+ lazy.prop(o, 'pubkey', () => {
+ if (a.pubkey) return a.pubkey;
+ if (a.output) return a.output.slice(2);
+ if (a.address) return _address().data;
+ if (o.internalPubkey) {
+ const tweakedKey = tweakKey(o.internalPubkey, o.hash);
+ if (tweakedKey) return tweakedKey.x;
+ }
+ });
+ lazy.prop(o, 'internalPubkey', () => {
+ if (a.internalPubkey) return a.internalPubkey;
+ const witness = _witness();
+ if (witness && witness.length > 1)
+ return witness[witness.length - 1].slice(1, 33);
+ });
+ lazy.prop(o, 'signature', () => {
+ if (a.signature) return a.signature;
+ const witness = _witness(); // witness without annex
+ if (!witness || witness.length !== 1) return;
+ return witness[0];
+ });
+
+ lazy.prop(o, 'witness', () => {
+ if (a.witness) return a.witness;
+ const hashTree = _hashTree();
+ if (hashTree && a.redeem && a.redeem.output && a.internalPubkey) {
+ const leafHash = tapleafHash({
+ output: a.redeem.output,
+ version: o.redeemVersion,
+ });
+ const path = findScriptPath(hashTree, leafHash);
+ if (!path) return;
+ const outputKey = tweakKey(a.internalPubkey, hashTree.hash);
+ if (!outputKey) return;
+ const controlBock = NBuffer.concat(
+ [
+ NBuffer.from([o.redeemVersion! | outputKey.parity]),
+ a.internalPubkey,
+ ].concat(path),
+ );
+ return [a.redeem.output, controlBock];
+ }
+ if (a.signature) return [a.signature];
+ });
+
+ // extended validation
+ if (opts.validate) {
+ let pubkey: Buffer = NBuffer.from([]);
+ if (a.address) {
+ if (network && network.bech32 !== _address().prefix)
+ throw new TypeError('Invalid prefix or Network mismatch');
+ if (_address().version !== TAPROOT_WITNESS_VERSION)
+ throw new TypeError('Invalid address version');
+ if (_address().data.length !== 32)
+ throw new TypeError('Invalid address data');
+ pubkey = _address().data;
+ }
+
+ if (a.pubkey) {
+ if (pubkey.length > 0 && !pubkey.equals(a.pubkey))
+ throw new TypeError('Pubkey mismatch');
+ else pubkey = a.pubkey;
+ }
+
+ if (a.output) {
+ if (
+ a.output.length !== 34 ||
+ a.output[0] !== OPS.OP_1 ||
+ a.output[1] !== 0x20
+ )
+ throw new TypeError('Output is invalid');
+ if (pubkey.length > 0 && !pubkey.equals(a.output.slice(2)))
+ throw new TypeError('Pubkey mismatch');
+ else pubkey = a.output.slice(2);
+ }
+
+ if (a.internalPubkey) {
+ const tweakedKey = tweakKey(a.internalPubkey, o.hash);
+ if (pubkey.length > 0 && !pubkey.equals(tweakedKey!.x))
+ throw new TypeError('Pubkey mismatch');
+ else pubkey = tweakedKey!.x;
+ }
+
+ if (pubkey && pubkey.length) {
+ if (!getEccLib().isXOnlyPoint(pubkey))
+ throw new TypeError('Invalid pubkey for p2tr');
+ }
+
+ const hashTree = _hashTree();
+
+ if (a.hash && hashTree) {
+ if (!a.hash.equals(hashTree.hash)) throw new TypeError('Hash mismatch');
+ }
+
+ if (a.redeem && a.redeem.output && hashTree) {
+ const leafHash = tapleafHash({
+ output: a.redeem.output,
+ version: o.redeemVersion,
+ });
+ if (!findScriptPath(hashTree, leafHash))
+ throw new TypeError('Redeem script not in tree');
+ }
+
+ const witness = _witness();
+
+ // compare the provided redeem data with the one computed from witness
+ if (a.redeem && o.redeem) {
+ if (a.redeem.redeemVersion) {
+ if (a.redeem.redeemVersion !== o.redeem.redeemVersion)
+ throw new TypeError('Redeem.redeemVersion and witness mismatch');
+ }
+
+ if (a.redeem.output) {
+ if (bscript.decompile(a.redeem.output)!.length === 0)
+ throw new TypeError('Redeem.output is invalid');
+
+ // output redeem is constructed from the witness
+ if (o.redeem.output && !a.redeem.output.equals(o.redeem.output))
+ throw new TypeError('Redeem.output and witness mismatch');
+ }
+ if (a.redeem.witness) {
+ if (
+ o.redeem.witness &&
+ !stacksEqual(a.redeem.witness, o.redeem.witness)
+ )
+ throw new TypeError('Redeem.witness and witness mismatch');
+ }
+ }
+
+ if (witness && witness.length) {
+ if (witness.length === 1) {
+ // key spending
+ if (a.signature && !a.signature.equals(witness[0]))
+ throw new TypeError('Signature mismatch');
+ } else {
+ // script path spending
+ const controlBlock = witness[witness.length - 1];
+ if (controlBlock.length < 33)
+ throw new TypeError(
+ `The control-block length is too small. Got ${
+ controlBlock.length
+ }, expected min 33.`,
+ );
+
+ if ((controlBlock.length - 33) % 32 !== 0)
+ throw new TypeError(
+ `The control-block length of ${controlBlock.length} is incorrect!`,
+ );
+
+ const m = (controlBlock.length - 33) / 32;
+ if (m > 128)
+ throw new TypeError(
+ `The script path is too long. Got ${m}, expected max 128.`,
+ );
+
+ const internalPubkey = controlBlock.slice(1, 33);
+ if (a.internalPubkey && !a.internalPubkey.equals(internalPubkey))
+ throw new TypeError('Internal pubkey mismatch');
+
+ if (!getEccLib().isXOnlyPoint(internalPubkey))
+ throw new TypeError('Invalid internalPubkey for p2tr witness');
+
+ const leafVersion = controlBlock[0] & TAPLEAF_VERSION_MASK;
+ const script = witness[witness.length - 2];
+
+ const leafHash = tapleafHash({ output: script, version: leafVersion });
+ const hash = rootHashFromPath(controlBlock, leafHash);
+
+ const outputKey = tweakKey(internalPubkey, hash);
+ if (!outputKey)
+ // todo: needs test data
+ throw new TypeError('Invalid outputKey for p2tr witness');
+
+ if (pubkey.length && !pubkey.equals(outputKey.x))
+ throw new TypeError('Pubkey mismatch for p2tr witness');
+
+ if (outputKey.parity !== (controlBlock[0] & 1))
+ throw new Error('Incorrect parity');
+ }
+ }
+ }
+
+ return Object.assign(o, a);
+}
+
+function stacksEqual(a: Buffer[], b: Buffer[]): boolean {
+ if (a.length !== b.length) return false;
+
+ return a.every((x, i) => {
+ return x.equals(b[i]);
+ });
+}
diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts
index b9af10fcf..aa546bc71 100644
--- a/ts_src/psbt.ts
+++ b/ts_src/psbt.ts
@@ -11,15 +11,38 @@ import {
PsbtOutputUpdate,
Transaction as ITransaction,
TransactionFromBuffer,
+ TapKeySig,
+ TapScriptSig,
} from 'bip174/src/lib/interfaces';
import { checkForInput, checkForOutput } from 'bip174/src/lib/utils';
import { fromOutputScript, toOutputScript } from './address';
import { cloneBuffer, reverseBuffer } from './bufferutils';
-import { hash160 } from './crypto';
import { bitcoin as btcNetwork, Network } from './networks';
import * as payments from './payments';
+import { tapleafHash } from './payments/bip341';
import * as bscript from './script';
import { Output, Transaction } from './transaction';
+import {
+ toXOnly,
+ tapScriptFinalizer,
+ serializeTaprootSignature,
+ isTaprootInput,
+ checkTaprootInputFields,
+ checkTaprootOutputFields,
+ tweakInternalPubKey,
+ checkTaprootInputForSigs,
+} from './psbt/bip371';
+import {
+ witnessStackToScriptWitness,
+ checkInputForSig,
+ pubkeyInScript,
+ isP2MS,
+ isP2PK,
+ isP2PKH,
+ isP2WPKH,
+ isP2WSHScript,
+ isP2SHScript,
+} from './psbt/psbtutils';
export interface TransactionInput {
hash: string | Buffer;
@@ -261,6 +284,7 @@ export class Psbt {
`Requires single object with at least [hash] and [index]`,
);
}
+ checkTaprootInputFields(inputData, inputData, 'addInput');
checkInputsForPartialSig(this.data.inputs, 'addInput');
if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript);
const c = this.__CACHE;
@@ -304,6 +328,8 @@ export class Psbt {
const script = toOutputScript(address, network);
outputData = Object.assign(outputData, { script });
}
+ checkTaprootOutputFields(outputData, outputData, 'addOutput');
+
const c = this.__CACHE;
this.data.addOutput(outputData);
c.__FEE = undefined;
@@ -345,9 +371,44 @@ export class Psbt {
finalizeInput(
inputIndex: number,
- finalScriptsFunc: FinalScriptsFunc = getFinalScripts,
+ finalScriptsFunc?: FinalScriptsFunc | FinalTaprootScriptsFunc,
): this {
const input = checkForInput(this.data.inputs, inputIndex);
+ if (isTaprootInput(input))
+ return this._finalizeTaprootInput(
+ inputIndex,
+ input,
+ undefined,
+ finalScriptsFunc as FinalTaprootScriptsFunc,
+ );
+ return this._finalizeInput(
+ inputIndex,
+ input,
+ finalScriptsFunc as FinalScriptsFunc,
+ );
+ }
+
+ finalizeTaprootInput(
+ inputIndex: number,
+ tapLeafHashToFinalize?: Buffer,
+ finalScriptsFunc: FinalTaprootScriptsFunc = tapScriptFinalizer,
+ ): this {
+ const input = checkForInput(this.data.inputs, inputIndex);
+ if (isTaprootInput(input))
+ return this._finalizeTaprootInput(
+ inputIndex,
+ input,
+ tapLeafHashToFinalize,
+ finalScriptsFunc,
+ );
+ throw new Error(`Cannot finalize input #${inputIndex}. Not Taproot.`);
+ }
+
+ private _finalizeInput(
+ inputIndex: number,
+ input: PsbtInput,
+ finalScriptsFunc: FinalScriptsFunc = getFinalScripts,
+ ): this {
const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
inputIndex,
input,
@@ -376,6 +437,39 @@ export class Psbt {
return this;
}
+ private _finalizeTaprootInput(
+ inputIndex: number,
+ input: PsbtInput,
+ tapLeafHashToFinalize?: Buffer,
+ finalScriptsFunc = tapScriptFinalizer,
+ ): this {
+ if (!input.witnessUtxo)
+ throw new Error(
+ `Cannot finalize input #${inputIndex}. Missing withness utxo.`,
+ );
+
+ // Check key spend first. Increased privacy and reduced block space.
+ if (input.tapKeySig) {
+ const payment = payments.p2tr({
+ output: input.witnessUtxo.script,
+ signature: input.tapKeySig,
+ });
+ const finalScriptWitness = witnessStackToScriptWitness(payment.witness!);
+ this.data.updateInput(inputIndex, { finalScriptWitness });
+ } else {
+ const { finalScriptWitness } = finalScriptsFunc(
+ inputIndex,
+ input,
+ tapLeafHashToFinalize,
+ );
+ this.data.updateInput(inputIndex, { finalScriptWitness });
+ }
+
+ this.data.clearFinalizedInput(inputIndex);
+
+ return this;
+ }
+
getInputType(inputIndex: number): AllScriptType {
const input = checkForInput(this.data.inputs, inputIndex);
const script = getScriptFromUtxo(inputIndex, input, this.__CACHE);
@@ -430,6 +524,22 @@ export class Psbt {
inputIndex: number,
validator: ValidateSigFunction,
pubkey?: Buffer,
+ ): boolean {
+ const input = this.data.inputs[inputIndex];
+ if (isTaprootInput(input))
+ return this.validateSignaturesOfTaprootInput(
+ inputIndex,
+ validator,
+ pubkey,
+ );
+
+ return this._validateSignaturesOfInput(inputIndex, validator, pubkey);
+ }
+
+ private _validateSignaturesOfInput(
+ inputIndex: number,
+ validator: ValidateSigFunction,
+ pubkey?: Buffer,
): boolean {
const input = this.data.inputs[inputIndex];
const partialSig = (input || {}).partialSig;
@@ -465,6 +575,64 @@ export class Psbt {
return results.every(res => res === true);
}
+ private validateSignaturesOfTaprootInput(
+ inputIndex: number,
+ validator: ValidateSigFunction,
+ pubkey?: Buffer,
+ ): boolean {
+ const input = this.data.inputs[inputIndex];
+ const tapKeySig = (input || {}).tapKeySig;
+ const tapScriptSig = (input || {}).tapScriptSig;
+ if (!input && !tapKeySig && !(tapScriptSig && !tapScriptSig.length))
+ throw new Error('No signatures to validate');
+ if (typeof validator !== 'function')
+ throw new Error('Need validator function to validate signatures');
+
+ pubkey = pubkey && toXOnly(pubkey);
+ const allHashses = pubkey
+ ? getTaprootHashesForSig(
+ inputIndex,
+ input,
+ this.data.inputs,
+ pubkey,
+ this.__CACHE,
+ )
+ : getAllTaprootHashesForSig(
+ inputIndex,
+ input,
+ this.data.inputs,
+ this.__CACHE,
+ );
+
+ if (!allHashses.length) throw new Error('No signatures for this pubkey');
+
+ const tapKeyHash = allHashses.find(h => !!h.leafHash);
+ if (tapKeySig && tapKeyHash) {
+ const isValidTapkeySig = validator(
+ tapKeyHash.pubkey,
+ tapKeyHash.hash,
+ tapKeySig,
+ );
+ if (!isValidTapkeySig) return false;
+ }
+
+ if (tapScriptSig) {
+ for (const tapSig of tapScriptSig) {
+ const tapSigHash = allHashses.find(h => tapSig.pubkey.equals(h.pubkey));
+ if (tapSigHash) {
+ const isValidTapScriptSig = validator(
+ tapSig.pubkey,
+ tapSigHash.hash,
+ tapSig.signature,
+ );
+ if (!isValidTapScriptSig) return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
signAllInputsHD(
hdKeyPair: HDSigner,
sighashTypes: number[] = [Transaction.SIGHASH_ALL],
@@ -566,10 +734,7 @@ export class Psbt {
);
}
- signAllInputs(
- keyPair: Signer,
- sighashTypes: number[] = [Transaction.SIGHASH_ALL],
- ): this {
+ signAllInputs(keyPair: Signer, sighashTypes?: number[]): this {
if (!keyPair || !keyPair.publicKey)
throw new Error('Need Signer to sign input');
@@ -593,7 +758,7 @@ export class Psbt {
signAllInputsAsync(
keyPair: Signer | SignerAsync,
- sighashTypes: number[] = [Transaction.SIGHASH_ALL],
+ sighashTypes?: number[],
): Promise {
return new Promise(
(resolve, reject): any => {
@@ -630,10 +795,51 @@ export class Psbt {
signInput(
inputIndex: number,
keyPair: Signer,
- sighashTypes: number[] = [Transaction.SIGHASH_ALL],
+ sighashTypes?: number[],
+ ): this {
+ if (!keyPair || !keyPair.publicKey)
+ throw new Error('Need Signer to sign input');
+
+ const input = checkForInput(this.data.inputs, inputIndex);
+
+ if (isTaprootInput(input)) {
+ return this._signTaprootInput(
+ inputIndex,
+ input,
+ keyPair,
+ undefined,
+ sighashTypes,
+ );
+ }
+ return this._signInput(inputIndex, keyPair, sighashTypes);
+ }
+
+ signTaprootInput(
+ inputIndex: number,
+ keyPair: Signer,
+ tapLeafHashToSign?: Buffer,
+ sighashTypes?: number[],
): this {
if (!keyPair || !keyPair.publicKey)
throw new Error('Need Signer to sign input');
+ const input = checkForInput(this.data.inputs, inputIndex);
+
+ if (isTaprootInput(input))
+ return this._signTaprootInput(
+ inputIndex,
+ input,
+ keyPair,
+ tapLeafHashToSign,
+ sighashTypes,
+ );
+ throw new Error(`Input #${inputIndex} is not of type Taproot.`);
+ }
+
+ private _signInput(
+ inputIndex: number,
+ keyPair: Signer,
+ sighashTypes: number[] = [Transaction.SIGHASH_ALL],
+ ): this {
const { hash, sighashType } = getHashAndSighashType(
this.data.inputs,
inputIndex,
@@ -653,35 +859,214 @@ export class Psbt {
return this;
}
+ private _signTaprootInput(
+ inputIndex: number,
+ input: PsbtInput,
+ keyPair: Signer,
+ tapLeafHashToSign?: Buffer,
+ allowedSighashTypes: number[] = [Transaction.SIGHASH_DEFAULT],
+ ): this {
+ const hashesForSig = this.checkTaprootHashesForSig(
+ inputIndex,
+ input,
+ keyPair,
+ tapLeafHashToSign,
+ allowedSighashTypes,
+ );
+
+ const tapKeySig: TapKeySig = hashesForSig
+ .filter(h => !h.leafHash)
+ .map(h =>
+ serializeTaprootSignature(
+ keyPair.signSchnorr!(h.hash),
+ input.sighashType,
+ ),
+ )[0];
+
+ const tapScriptSig: TapScriptSig[] = hashesForSig
+ .filter(h => !!h.leafHash)
+ .map(
+ h =>
+ ({
+ pubkey: toXOnly(keyPair.publicKey),
+ signature: serializeTaprootSignature(
+ keyPair.signSchnorr!(h.hash),
+ input.sighashType,
+ ),
+ leafHash: h.leafHash,
+ } as TapScriptSig),
+ );
+
+ if (tapKeySig) {
+ this.data.updateInput(inputIndex, { tapKeySig });
+ }
+
+ if (tapScriptSig.length) {
+ this.data.updateInput(inputIndex, { tapScriptSig });
+ }
+
+ return this;
+ }
+
signInputAsync(
inputIndex: number,
keyPair: Signer | SignerAsync,
- sighashTypes: number[] = [Transaction.SIGHASH_ALL],
+ sighashTypes?: number[],
): Promise {
return Promise.resolve().then(() => {
if (!keyPair || !keyPair.publicKey)
throw new Error('Need Signer to sign input');
- const { hash, sighashType } = getHashAndSighashType(
- this.data.inputs,
- inputIndex,
- keyPair.publicKey,
- this.__CACHE,
- sighashTypes,
- );
- return Promise.resolve(keyPair.sign(hash)).then(signature => {
- const partialSig = [
- {
- pubkey: keyPair.publicKey,
- signature: bscript.signature.encode(signature, sighashType),
- },
- ];
+ const input = checkForInput(this.data.inputs, inputIndex);
+ if (isTaprootInput(input))
+ return this._signTaprootInputAsync(
+ inputIndex,
+ input,
+ keyPair,
+ undefined,
+ sighashTypes,
+ );
+
+ return this._signInputAsync(inputIndex, keyPair, sighashTypes);
+ });
+ }
+
+ signTaprootInputAsync(
+ inputIndex: number,
+ keyPair: Signer | SignerAsync,
+ tapLeafHash?: Buffer,
+ sighashTypes?: number[],
+ ): Promise {
+ return Promise.resolve().then(() => {
+ if (!keyPair || !keyPair.publicKey)
+ throw new Error('Need Signer to sign input');
+
+ const input = checkForInput(this.data.inputs, inputIndex);
+ if (isTaprootInput(input))
+ return this._signTaprootInputAsync(
+ inputIndex,
+ input,
+ keyPair,
+ tapLeafHash,
+ sighashTypes,
+ );
+
+ throw new Error(`Input #${inputIndex} is not of type Taproot.`);
+ });
+ }
- this.data.updateInput(inputIndex, { partialSig });
+ private _signInputAsync(
+ inputIndex: number,
+ keyPair: Signer | SignerAsync,
+ sighashTypes: number[] = [Transaction.SIGHASH_ALL],
+ ): Promise {
+ const { hash, sighashType } = getHashAndSighashType(
+ this.data.inputs,
+ inputIndex,
+ keyPair.publicKey,
+ this.__CACHE,
+ sighashTypes,
+ );
+
+ return Promise.resolve(keyPair.sign(hash)).then(signature => {
+ const partialSig = [
+ {
+ pubkey: keyPair.publicKey,
+ signature: bscript.signature.encode(signature, sighashType),
+ },
+ ];
+
+ this.data.updateInput(inputIndex, { partialSig });
+ });
+ }
+
+ private async _signTaprootInputAsync(
+ inputIndex: number,
+ input: PsbtInput,
+ keyPair: Signer | SignerAsync,
+ tapLeafHash?: Buffer,
+ sighashTypes: number[] = [Transaction.SIGHASH_DEFAULT],
+ ): Promise {
+ const hashesForSig = this.checkTaprootHashesForSig(
+ inputIndex,
+ input,
+ keyPair,
+ tapLeafHash,
+ sighashTypes,
+ );
+
+ const signaturePromises: Promise<
+ { tapKeySig: Buffer } | { tapScriptSig: TapScriptSig[] }
+ >[] = [];
+ const tapKeyHash = hashesForSig.filter(h => !h.leafHash)[0];
+ if (tapKeyHash) {
+ const tapKeySigPromise = Promise.resolve(
+ keyPair.signSchnorr!(tapKeyHash.hash),
+ ).then(sig => {
+ return { tapKeySig: serializeTaprootSignature(sig, input.sighashType) };
+ });
+ signaturePromises.push(tapKeySigPromise);
+ }
+
+ const tapScriptHashes = hashesForSig.filter(h => !!h.leafHash);
+ if (tapScriptHashes.length) {
+ const tapScriptSigPromises = tapScriptHashes.map(tsh => {
+ return Promise.resolve(keyPair.signSchnorr!(tsh.hash)).then(
+ signature => {
+ const tapScriptSig = [
+ {
+ pubkey: toXOnly(keyPair.publicKey),
+ signature: serializeTaprootSignature(
+ signature,
+ input.sighashType,
+ ),
+ leafHash: tsh.leafHash,
+ } as TapScriptSig,
+ ];
+ return { tapScriptSig };
+ },
+ );
});
+ signaturePromises.push(...tapScriptSigPromises);
+ }
+
+ return Promise.all(signaturePromises).then(results => {
+ results.forEach(v => this.data.updateInput(inputIndex, v));
});
}
+ private checkTaprootHashesForSig(
+ inputIndex: number,
+ input: PsbtInput,
+ keyPair: Signer | SignerAsync,
+ tapLeafHashToSign?: Buffer,
+ allowedSighashTypes?: number[],
+ ): { hash: Buffer; leafHash?: Buffer }[] {
+ if (typeof keyPair.signSchnorr !== 'function')
+ throw new Error(
+ `Need Schnorr Signer to sign taproot input #${inputIndex}.`,
+ );
+
+ const hashesForSig = getTaprootHashesForSig(
+ inputIndex,
+ input,
+ this.data.inputs,
+ keyPair.publicKey,
+ this.__CACHE,
+ tapLeafHashToSign,
+ allowedSighashTypes,
+ );
+
+ if (!hashesForSig || !hashesForSig.length)
+ throw new Error(
+ `Can not sign for input #${inputIndex} with the key ${keyPair.publicKey.toString(
+ 'hex',
+ )}`,
+ );
+
+ return hashesForSig;
+ }
+
toBuffer(): Buffer {
checkCache(this.__CACHE);
return this.data.toBuffer();
@@ -704,6 +1089,11 @@ export class Psbt {
updateInput(inputIndex: number, updateData: PsbtInputUpdate): this {
if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript);
+ checkTaprootInputFields(
+ this.data.inputs[inputIndex],
+ updateData,
+ 'updateInput',
+ );
this.data.updateInput(inputIndex, updateData);
if (updateData.nonWitnessUtxo) {
addNonWitnessTxCache(
@@ -716,6 +1106,9 @@ export class Psbt {
}
updateOutput(outputIndex: number, updateData: PsbtOutputUpdate): this {
+ const outputData = this.data.outputs[outputIndex];
+ checkTaprootOutputFields(outputData, updateData, 'updateOutput');
+
this.data.updateOutput(outputIndex, updateData);
return this;
}
@@ -812,6 +1205,7 @@ export interface Signer {
publicKey: Buffer;
network?: any;
sign(hash: Buffer, lowR?: boolean): Buffer;
+ signSchnorr?(hash: Buffer): Buffer;
getPublicKey?(): Buffer;
}
@@ -819,6 +1213,7 @@ export interface SignerAsync {
publicKey: Buffer;
network?: any;
sign(hash: Buffer, lowR?: boolean): Promise;
+ signSchnorr?(hash: Buffer): Promise;
getPublicKey?(): Buffer;
}
@@ -939,23 +1334,6 @@ function isFinalized(input: PsbtInput): boolean {
return !!input.finalScriptSig || !!input.finalScriptWitness;
}
-function isPaymentFactory(payment: any): (script: Buffer) => boolean {
- return (script: Buffer): boolean => {
- try {
- payment({ output: script });
- return true;
- } catch (err) {
- return false;
- }
- };
-}
-const isP2MS = isPaymentFactory(payments.p2ms);
-const isP2PK = isPaymentFactory(payments.p2pk);
-const isP2PKH = isPaymentFactory(payments.p2pkh);
-const isP2WPKH = isPaymentFactory(payments.p2wpkh);
-const isP2WSHScript = isPaymentFactory(payments.p2wsh);
-const isP2SHScript = isPaymentFactory(payments.p2sh);
-
function bip32DerivationIsMine(
root: HDSigner,
): (d: Bip32Derivation) => boolean {
@@ -994,36 +1372,11 @@ function checkFees(psbt: Psbt, cache: PsbtCache, opts: PsbtOpts): void {
function checkInputsForPartialSig(inputs: PsbtInput[], action: string): void {
inputs.forEach(input => {
- let throws = false;
- let pSigs: PartialSig[] = [];
- if ((input.partialSig || []).length === 0) {
- if (!input.finalScriptSig && !input.finalScriptWitness) return;
- pSigs = getPsigsFromInputFinalScripts(input);
- } else {
- pSigs = input.partialSig!;
- }
- pSigs.forEach(pSig => {
- const { hashType } = bscript.signature.decode(pSig.signature);
- const whitelist: string[] = [];
- const isAnyoneCanPay = hashType & Transaction.SIGHASH_ANYONECANPAY;
- if (isAnyoneCanPay) whitelist.push('addInput');
- const hashMod = hashType & 0x1f;
- switch (hashMod) {
- case Transaction.SIGHASH_ALL:
- break;
- case Transaction.SIGHASH_SINGLE:
- case Transaction.SIGHASH_NONE:
- whitelist.push('addOutput');
- whitelist.push('setInputSequence');
- break;
- }
- if (whitelist.indexOf(action) === -1) {
- throws = true;
- }
- });
- if (throws) {
+ const throws = isTaprootInput(input)
+ ? checkTaprootInputForSigs(input, action)
+ : checkInputForSig(input, action);
+ if (throws)
throw new Error('Can not modify transaction, signatures exist.');
- }
});
}
@@ -1147,6 +1500,13 @@ type FinalScriptsFunc = (
finalScriptSig: Buffer | undefined;
finalScriptWitness: Buffer | undefined;
};
+type FinalTaprootScriptsFunc = (
+ inputIndex: number, // Which input is it?
+ input: PsbtInput, // The PSBT input contents
+ tapLeafHashToFinalize?: Buffer, // Only finalize this specific leaf
+) => {
+ finalScriptWitness: Buffer | undefined;
+};
function getFinalScripts(
inputIndex: number,
@@ -1251,13 +1611,8 @@ function getHashForSig(
} {
const unsignedTx = cache.__TX;
const sighashType = input.sighashType || Transaction.SIGHASH_ALL;
- if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) {
- const str = sighashTypeToString(sighashType);
- throw new Error(
- `Sighash type is not allowed. Retry the sign method passing the ` +
- `sighashTypes array of whitelisted types. Sighash type: ${str}`,
- );
- }
+ checkSighashTypeAllowed(sighashType, sighashTypes);
+
let hash: Buffer;
let prevout: Output;
@@ -1345,6 +1700,108 @@ function getHashForSig(
};
}
+function getAllTaprootHashesForSig(
+ inputIndex: number,
+ input: PsbtInput,
+ inputs: PsbtInput[],
+ cache: PsbtCache,
+): { pubkey: Buffer; hash: Buffer; leafHash?: Buffer }[] {
+ const allPublicKeys = [];
+ if (input.tapInternalKey) {
+ const outputKey = tweakInternalPubKey(inputIndex, input);
+ allPublicKeys.push(outputKey);
+ }
+
+ if (input.tapScriptSig) {
+ const tapScriptPubkeys = input.tapScriptSig.map(tss => tss.pubkey);
+ allPublicKeys.push(...tapScriptPubkeys);
+ }
+
+ const allHashes = allPublicKeys.map(pubicKey =>
+ getTaprootHashesForSig(inputIndex, input, inputs, pubicKey, cache),
+ );
+
+ return allHashes.flat();
+}
+
+function getTaprootHashesForSig(
+ inputIndex: number,
+ input: PsbtInput,
+ inputs: PsbtInput[],
+ pubkey: Buffer,
+ cache: PsbtCache,
+ tapLeafHashToSign?: Buffer,
+ allowedSighashTypes?: number[],
+): { pubkey: Buffer; hash: Buffer; leafHash?: Buffer }[] {
+ const unsignedTx = cache.__TX;
+
+ const sighashType = input.sighashType || Transaction.SIGHASH_DEFAULT;
+ checkSighashTypeAllowed(sighashType, allowedSighashTypes);
+
+ const prevOuts: Output[] = inputs.map((i, index) =>
+ getScriptAndAmountFromUtxo(index, i, cache),
+ );
+ const signingScripts = prevOuts.map(o => o.script);
+ const values = prevOuts.map(o => o.value);
+
+ const hashes = [];
+ if (input.tapInternalKey && !tapLeafHashToSign) {
+ const outputKey = tweakInternalPubKey(inputIndex, input);
+ if (toXOnly(pubkey).equals(outputKey)) {
+ const tapKeyHash = unsignedTx.hashForWitnessV1(
+ inputIndex,
+ signingScripts,
+ values,
+ sighashType,
+ );
+ hashes.push({ pubkey, hash: tapKeyHash });
+ }
+ }
+
+ const tapLeafHashes = (input.tapLeafScript || [])
+ .filter(tapLeaf => pubkeyInScript(pubkey, tapLeaf.script))
+ .map(tapLeaf => {
+ const hash = tapleafHash({
+ output: tapLeaf.script,
+ version: tapLeaf.leafVersion,
+ });
+ return Object.assign({ hash }, tapLeaf);
+ })
+ .filter(
+ tapLeaf => !tapLeafHashToSign || tapLeafHashToSign.equals(tapLeaf.hash),
+ )
+ .map(tapLeaf => {
+ const tapScriptHash = unsignedTx.hashForWitnessV1(
+ inputIndex,
+ signingScripts,
+ values,
+ Transaction.SIGHASH_DEFAULT,
+ tapLeaf.hash,
+ );
+
+ return {
+ pubkey,
+ hash: tapScriptHash,
+ leafHash: tapLeaf.hash,
+ };
+ });
+
+ return hashes.concat(tapLeafHashes);
+}
+
+function checkSighashTypeAllowed(
+ sighashType: number,
+ sighashTypes?: number[],
+): void {
+ if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) {
+ const str = sighashTypeToString(sighashType);
+ throw new Error(
+ `Sighash type is not allowed. Retry the sign method passing the ` +
+ `sighashTypes array of whitelisted types. Sighash type: ${str}`,
+ );
+ }
+}
+
function getPayment(
script: Buffer,
scriptType: string,
@@ -1383,21 +1840,6 @@ function getPayment(
return payment!;
}
-function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] {
- const scriptItems = !input.finalScriptSig
- ? []
- : bscript.decompile(input.finalScriptSig) || [];
- const witnessItems = !input.finalScriptWitness
- ? []
- : bscript.decompile(input.finalScriptWitness) || [];
- return scriptItems
- .concat(witnessItems)
- .filter(item => {
- return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item);
- })
- .map(sig => ({ signature: sig })) as PartialSig[];
-}
-
interface GetScriptReturn {
script: Buffer | null;
isSegwit: boolean;
@@ -1539,36 +1981,6 @@ function sighashTypeToString(sighashType: number): string {
return text;
}
-function witnessStackToScriptWitness(witness: Buffer[]): Buffer {
- let buffer = Buffer.allocUnsafe(0);
-
- function writeSlice(slice: Buffer): void {
- buffer = Buffer.concat([buffer, Buffer.from(slice)]);
- }
-
- function writeVarInt(i: number): void {
- const currentLen = buffer.length;
- const varintLen = varuint.encodingLength(i);
-
- buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
- varuint.encode(i, buffer, currentLen);
- }
-
- function writeVarSlice(slice: Buffer): void {
- writeVarInt(slice.length);
- writeSlice(slice);
- }
-
- function writeVector(vector: Buffer[]): void {
- writeVarInt(vector.length);
- vector.forEach(writeVarSlice);
- }
-
- writeVector(witness);
-
- return buffer;
-}
-
function addNonWitnessTxCache(
cache: PsbtCache,
input: PsbtInput,
@@ -1656,15 +2068,28 @@ function getScriptFromUtxo(
input: PsbtInput,
cache: PsbtCache,
): Buffer {
+ const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache);
+ return script;
+}
+
+function getScriptAndAmountFromUtxo(
+ inputIndex: number,
+ input: PsbtInput,
+ cache: PsbtCache,
+): { script: Buffer; value: number } {
if (input.witnessUtxo !== undefined) {
- return input.witnessUtxo.script;
+ return {
+ script: input.witnessUtxo.script,
+ value: input.witnessUtxo.value,
+ };
} else if (input.nonWitnessUtxo !== undefined) {
const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
cache,
input,
inputIndex,
);
- return nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index].script;
+ const o = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index];
+ return { script: o.script, value: o.value };
} else {
throw new Error("Can't find pubkey in input without Utxo data");
}
@@ -1808,18 +2233,6 @@ function checkInvalidP2WSH(script: Buffer): void {
}
}
-function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {
- const pubkeyHash = hash160(pubkey);
-
- const decompiled = bscript.decompile(script);
- if (decompiled === null) throw new Error('Unknown script error');
-
- return decompiled.some(element => {
- if (typeof element === 'number') return false;
- return element.equals(pubkey) || element.equals(pubkeyHash);
- });
-}
-
type AllScriptType =
| 'witnesspubkeyhash'
| 'pubkeyhash'
diff --git a/ts_src/psbt/bip371.ts b/ts_src/psbt/bip371.ts
new file mode 100644
index 000000000..d4eaaa5f3
--- /dev/null
+++ b/ts_src/psbt/bip371.ts
@@ -0,0 +1,458 @@
+import { Taptree, Tapleaf, isTapleaf, isTaptree } from '../types';
+import {
+ PsbtInput,
+ PsbtOutput,
+ TapLeafScript,
+ TapScriptSig,
+ TapLeaf,
+ TapTree,
+ TapInternalKey,
+} from 'bip174/src/lib/interfaces';
+
+import { Transaction } from '../transaction';
+
+import {
+ witnessStackToScriptWitness,
+ pubkeyPositionInScript,
+ isP2TR,
+} from './psbtutils';
+import {
+ tweakKey,
+ tapleafHash,
+ rootHashFromPath,
+ LEAF_VERSION_TAPSCRIPT,
+ MAX_TAPTREE_DEPTH,
+} from '../payments/bip341';
+import { p2tr } from '../payments';
+
+import { signatureBlocksAction } from './psbtutils';
+
+export const toXOnly = (pubKey: Buffer) =>
+ pubKey.length === 32 ? pubKey : pubKey.slice(1, 33);
+
+/**
+ * Default tapscript finalizer. It searches for the `tapLeafHashToFinalize` if provided.
+ * Otherwise it will search for the tapleaf that has at least one signature and has the shortest path.
+ * @param inputIndex the position of the PSBT input.
+ * @param input the PSBT input.
+ * @param tapLeafHashToFinalize optional, if provided the finalizer will search for a tapleaf that has this hash
+ * and will try to build the finalScriptWitness.
+ * @returns the finalScriptWitness or throws an exception if no tapleaf found.
+ */
+export function tapScriptFinalizer(
+ inputIndex: number,
+ input: PsbtInput,
+ tapLeafHashToFinalize?: Buffer,
+): {
+ finalScriptWitness: Buffer | undefined;
+} {
+ const tapLeaf = findTapLeafToFinalize(
+ input,
+ inputIndex,
+ tapLeafHashToFinalize,
+ );
+
+ try {
+ const sigs = sortSignatures(input, tapLeaf);
+ const witness = sigs.concat(tapLeaf.script).concat(tapLeaf.controlBlock);
+ return { finalScriptWitness: witnessStackToScriptWitness(witness) };
+ } catch (err) {
+ throw new Error(`Can not finalize taproot input #${inputIndex}: ${err}`);
+ }
+}
+
+export function serializeTaprootSignature(
+ sig: Buffer,
+ sighashType?: number,
+): Buffer {
+ const sighashTypeByte = sighashType
+ ? Buffer.from([sighashType!])
+ : Buffer.from([]);
+
+ return Buffer.concat([sig, sighashTypeByte]);
+}
+
+export function isTaprootInput(input: PsbtInput): boolean {
+ return (
+ input &&
+ !!(
+ input.tapInternalKey ||
+ input.tapMerkleRoot ||
+ (input.tapLeafScript && input.tapLeafScript.length) ||
+ (input.tapBip32Derivation && input.tapBip32Derivation.length) ||
+ (input.witnessUtxo && isP2TR(input.witnessUtxo.script))
+ )
+ );
+}
+
+export function isTaprootOutput(output: PsbtOutput, script?: Buffer): boolean {
+ return (
+ output &&
+ !!(
+ output.tapInternalKey ||
+ output.tapTree ||
+ (output.tapBip32Derivation && output.tapBip32Derivation.length) ||
+ (script && isP2TR(script))
+ )
+ );
+}
+
+export function checkTaprootInputFields(
+ inputData: PsbtInput,
+ newInputData: PsbtInput,
+ action: string,
+): void {
+ checkMixedTaprootAndNonTaprootInputFields(inputData, newInputData, action);
+ checkIfTapLeafInTree(inputData, newInputData, action);
+}
+
+export function checkTaprootOutputFields(
+ outputData: PsbtOutput,
+ newOutputData: PsbtOutput,
+ action: string,
+): void {
+ checkMixedTaprootAndNonTaprootOutputFields(outputData, newOutputData, action);
+ checkTaprootScriptPubkey(outputData, newOutputData);
+}
+
+function checkTaprootScriptPubkey(
+ outputData: PsbtOutput,
+ newOutputData: PsbtOutput,
+): void {
+ if (!newOutputData.tapTree && !newOutputData.tapInternalKey) return;
+
+ const tapInternalKey =
+ newOutputData.tapInternalKey || outputData.tapInternalKey;
+ const tapTree = newOutputData.tapTree || outputData.tapTree;
+
+ if (tapInternalKey) {
+ const { script: scriptPubkey } = outputData as any;
+ const script = getTaprootScripPubkey(tapInternalKey, tapTree);
+ if (scriptPubkey && !scriptPubkey.equals(script))
+ throw new Error('Error adding output. Script or address missmatch.');
+ }
+}
+
+function getTaprootScripPubkey(
+ tapInternalKey: TapInternalKey,
+ tapTree?: TapTree,
+): Buffer {
+ const scriptTree = tapTree && tapTreeFromList(tapTree.leaves);
+ const { output } = p2tr({
+ internalPubkey: tapInternalKey,
+ scriptTree,
+ });
+ return output!;
+}
+
+export function tweakInternalPubKey(
+ inputIndex: number,
+ input: PsbtInput,
+): Buffer {
+ const tapInternalKey = input.tapInternalKey;
+ const outputKey =
+ tapInternalKey && tweakKey(tapInternalKey, input.tapMerkleRoot);
+
+ if (!outputKey)
+ throw new Error(
+ `Cannot tweak tap internal key for input #${inputIndex}. Public key: ${tapInternalKey &&
+ tapInternalKey.toString('hex')}`,
+ );
+ return outputKey.x;
+}
+
+/**
+ * Convert a binary tree to a BIP371 type list. Each element of the list is (according to BIP371):
+ * One or more tuples representing the depth, leaf version, and script for a leaf in the Taproot tree,
+ * allowing the entire tree to be reconstructed. The tuples must be in depth first search order so that
+ * the tree is correctly reconstructed.
+ * @param tree the binary tap tree
+ * @returns a list of BIP 371 tapleaves
+ */
+export function tapTreeToList(tree: Taptree): TapLeaf[] {
+ if (!isTaptree(tree))
+ throw new Error(
+ 'Cannot convert taptree to tapleaf list. Expecting a tapree structure.',
+ );
+ return _tapTreeToList(tree);
+}
+
+/**
+ * Convert a BIP371 TapLeaf list to a TapTree (binary).
+ * @param leaves a list of tapleaves where each element of the list is (according to BIP371):
+ * One or more tuples representing the depth, leaf version, and script for a leaf in the Taproot tree,
+ * allowing the entire tree to be reconstructed. The tuples must be in depth first search order so that
+ * the tree is correctly reconstructed.
+ * @returns the corresponding taptree, or throws an exception if the tree cannot be reconstructed
+ */
+export function tapTreeFromList(leaves: TapLeaf[] = []): Taptree {
+ if (leaves.length === 1 && leaves[0].depth === 0)
+ return {
+ output: leaves[0].script,
+ version: leaves[0].leafVersion,
+ };
+
+ return instertLeavesInTree(leaves);
+}
+
+export function checkTaprootInputForSigs(
+ input: PsbtInput,
+ action: string,
+): boolean {
+ const sigs = extractTaprootSigs(input);
+ return sigs.some(sig =>
+ signatureBlocksAction(sig, decodeSchnorrSignature, action),
+ );
+}
+
+function decodeSchnorrSignature(
+ signature: Buffer,
+): {
+ signature: Buffer;
+ hashType: number;
+} {
+ return {
+ signature: signature.slice(0, 64),
+ hashType: signature.slice(64)[0] || Transaction.SIGHASH_DEFAULT,
+ };
+}
+
+function extractTaprootSigs(input: PsbtInput): Buffer[] {
+ const sigs: Buffer[] = [];
+ if (input.tapKeySig) sigs.push(input.tapKeySig);
+ if (input.tapScriptSig)
+ sigs.push(...input.tapScriptSig.map(s => s.signature));
+ if (!sigs.length) {
+ const finalTapKeySig = getTapKeySigFromWithness(input.finalScriptWitness);
+ if (finalTapKeySig) sigs.push(finalTapKeySig);
+ }
+
+ return sigs;
+}
+
+function getTapKeySigFromWithness(
+ finalScriptWitness?: Buffer,
+): Buffer | undefined {
+ if (!finalScriptWitness) return;
+ const witness = finalScriptWitness.slice(2);
+ // todo: add schnorr signature validation
+ if (witness.length === 64 || witness.length === 65) return witness;
+}
+
+function _tapTreeToList(
+ tree: Taptree,
+ leaves: TapLeaf[] = [],
+ depth = 0,
+): TapLeaf[] {
+ if (depth > MAX_TAPTREE_DEPTH) throw new Error('Max taptree depth exceeded.');
+ if (!tree) return [];
+ if (isTapleaf(tree)) {
+ leaves.push({
+ depth,
+ leafVersion: tree.version || LEAF_VERSION_TAPSCRIPT,
+ script: tree.output,
+ });
+ return leaves;
+ }
+ if (tree[0]) _tapTreeToList(tree[0], leaves, depth + 1);
+ if (tree[1]) _tapTreeToList(tree[1], leaves, depth + 1);
+ return leaves;
+}
+
+// Just like Taptree, but it accepts empty branches
+type PartialTaptree =
+ | [PartialTaptree | Tapleaf, PartialTaptree | Tapleaf]
+ | Tapleaf
+ | undefined;
+function instertLeavesInTree(leaves: TapLeaf[]): Taptree {
+ let tree: PartialTaptree;
+ for (const leaf of leaves) {
+ tree = instertLeafInTree(leaf, tree);
+ if (!tree) throw new Error(`No room left to insert tapleaf in tree`);
+ }
+
+ return tree as Taptree;
+}
+
+function instertLeafInTree(
+ leaf: TapLeaf,
+ tree?: PartialTaptree,
+ depth = 0,
+): PartialTaptree {
+ if (depth > MAX_TAPTREE_DEPTH) throw new Error('Max taptree depth exceeded.');
+ if (leaf.depth === depth) {
+ if (!tree)
+ return {
+ output: leaf.script,
+ version: leaf.leafVersion,
+ };
+ return;
+ }
+
+ if (isTapleaf(tree)) return;
+ const leftSide = instertLeafInTree(leaf, tree && tree[0], depth + 1);
+ if (leftSide) return [leftSide, tree && tree[1]];
+
+ const rightSide = instertLeafInTree(leaf, tree && tree[1], depth + 1);
+ if (rightSide) return [tree && tree[0], rightSide];
+}
+
+function checkMixedTaprootAndNonTaprootInputFields(
+ inputData: PsbtOutput,
+ newInputData: PsbtInput,
+ action: string,
+): void {
+ const isBadTaprootUpdate =
+ isTaprootInput(inputData) && hasNonTaprootFields(newInputData);
+ const isBadNonTaprootUpdate =
+ hasNonTaprootFields(inputData) && isTaprootInput(newInputData);
+ const hasMixedFields =
+ inputData === newInputData &&
+ (isTaprootInput(newInputData) && hasNonTaprootFields(newInputData)); // todo: bad? use !===
+
+ if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields)
+ throw new Error(
+ `Invalid arguments for Psbt.${action}. ` +
+ `Cannot use both taproot and non-taproot fields.`,
+ );
+}
+function checkMixedTaprootAndNonTaprootOutputFields(
+ inputData: PsbtOutput,
+ newInputData: PsbtOutput,
+ action: string,
+): void {
+ const isBadTaprootUpdate =
+ isTaprootOutput(inputData) && hasNonTaprootFields(newInputData);
+ const isBadNonTaprootUpdate =
+ hasNonTaprootFields(inputData) && isTaprootOutput(newInputData);
+ const hasMixedFields =
+ inputData === newInputData &&
+ (isTaprootOutput(newInputData) && hasNonTaprootFields(newInputData));
+
+ if (isBadTaprootUpdate || isBadNonTaprootUpdate || hasMixedFields)
+ throw new Error(
+ `Invalid arguments for Psbt.${action}. ` +
+ `Cannot use both taproot and non-taproot fields.`,
+ );
+}
+
+function checkIfTapLeafInTree(
+ inputData: PsbtInput,
+ newInputData: PsbtInput,
+ action: string,
+): void {
+ if (newInputData.tapMerkleRoot) {
+ const newLeafsInTree = (newInputData.tapLeafScript || []).every(l =>
+ isTapLeafInTree(l, newInputData.tapMerkleRoot),
+ );
+ const oldLeafsInTree = (inputData.tapLeafScript || []).every(l =>
+ isTapLeafInTree(l, newInputData.tapMerkleRoot),
+ );
+ if (!newLeafsInTree || !oldLeafsInTree)
+ throw new Error(
+ `Invalid arguments for Psbt.${action}. Tapleaf not part of taptree.`,
+ );
+ } else if (inputData.tapMerkleRoot) {
+ const newLeafsInTree = (newInputData.tapLeafScript || []).every(l =>
+ isTapLeafInTree(l, inputData.tapMerkleRoot),
+ );
+ if (!newLeafsInTree)
+ throw new Error(
+ `Invalid arguments for Psbt.${action}. Tapleaf not part of taptree.`,
+ );
+ }
+}
+
+function isTapLeafInTree(tapLeaf: TapLeafScript, merkleRoot?: Buffer): boolean {
+ if (!merkleRoot) return true;
+
+ const leafHash = tapleafHash({
+ output: tapLeaf.script,
+ version: tapLeaf.leafVersion,
+ });
+
+ const rootHash = rootHashFromPath(tapLeaf.controlBlock, leafHash);
+ return rootHash.equals(merkleRoot);
+}
+
+function sortSignatures(input: PsbtInput, tapLeaf: TapLeafScript): Buffer[] {
+ const leafHash = tapleafHash({
+ output: tapLeaf.script,
+ version: tapLeaf.leafVersion,
+ });
+
+ return (input.tapScriptSig || [])
+ .filter(tss => tss.leafHash.equals(leafHash))
+ .map(tss => addPubkeyPositionInScript(tapLeaf.script, tss))
+ .sort((t1, t2) => t2.positionInScript - t1.positionInScript)
+ .map(t => t.signature) as Buffer[];
+}
+
+function addPubkeyPositionInScript(
+ script: Buffer,
+ tss: TapScriptSig,
+): TapScriptSigWitPosition {
+ return Object.assign(
+ {
+ positionInScript: pubkeyPositionInScript(tss.pubkey, script),
+ },
+ tss,
+ ) as TapScriptSigWitPosition;
+}
+
+/**
+ * Find tapleaf by hash, or get the signed tapleaf with the shortest path.
+ */
+function findTapLeafToFinalize(
+ input: PsbtInput,
+ inputIndex: number,
+ leafHashToFinalize?: Buffer,
+): TapLeafScript {
+ if (!input.tapScriptSig || !input.tapScriptSig.length)
+ throw new Error(
+ `Can not finalize taproot input #${inputIndex}. No tapleaf script signature provided.`,
+ );
+ const tapLeaf = (input.tapLeafScript || [])
+ .sort((a, b) => a.controlBlock.length - b.controlBlock.length)
+ .find(leaf =>
+ canFinalizeLeaf(leaf, input.tapScriptSig!, leafHashToFinalize),
+ );
+
+ if (!tapLeaf)
+ throw new Error(
+ `Can not finalize taproot input #${inputIndex}. Signature for tapleaf script not found.`,
+ );
+
+ return tapLeaf;
+}
+
+function canFinalizeLeaf(
+ leaf: TapLeafScript,
+ tapScriptSig: TapScriptSig[],
+ hash?: Buffer,
+): boolean {
+ const leafHash = tapleafHash({
+ output: leaf.script,
+ version: leaf.leafVersion,
+ });
+ const whiteListedHash = !hash || hash.equals(leafHash);
+ return (
+ whiteListedHash &&
+ tapScriptSig!.find(tss => tss.leafHash.equals(leafHash)) !== undefined
+ );
+}
+
+function hasNonTaprootFields(io: PsbtInput | PsbtOutput): boolean {
+ return (
+ io &&
+ !!(
+ io.redeemScript ||
+ io.witnessScript ||
+ (io.bip32Derivation && io.bip32Derivation.length)
+ )
+ );
+}
+
+interface TapScriptSigWitPosition extends TapScriptSig {
+ positionInScript: number;
+}
diff --git a/ts_src/psbt/psbtutils.ts b/ts_src/psbt/psbtutils.ts
new file mode 100644
index 000000000..d51e2df54
--- /dev/null
+++ b/ts_src/psbt/psbtutils.ts
@@ -0,0 +1,139 @@
+import * as varuint from 'bip174/src/lib/converter/varint';
+import { PartialSig, PsbtInput } from 'bip174/src/lib/interfaces';
+import * as bscript from '../script';
+import { Transaction } from '../transaction';
+import { hash160 } from '../crypto';
+import * as payments from '../payments';
+
+function isPaymentFactory(payment: any): (script: Buffer) => boolean {
+ return (script: Buffer): boolean => {
+ try {
+ payment({ output: script });
+ return true;
+ } catch (err) {
+ return false;
+ }
+ };
+}
+export const isP2MS = isPaymentFactory(payments.p2ms);
+export const isP2PK = isPaymentFactory(payments.p2pk);
+export const isP2PKH = isPaymentFactory(payments.p2pkh);
+export const isP2WPKH = isPaymentFactory(payments.p2wpkh);
+export const isP2WSHScript = isPaymentFactory(payments.p2wsh);
+export const isP2SHScript = isPaymentFactory(payments.p2sh);
+export const isP2TR = isPaymentFactory(payments.p2tr);
+
+export function witnessStackToScriptWitness(witness: Buffer[]): Buffer {
+ let buffer = Buffer.allocUnsafe(0);
+
+ function writeSlice(slice: Buffer): void {
+ buffer = Buffer.concat([buffer, Buffer.from(slice)]);
+ }
+
+ function writeVarInt(i: number): void {
+ const currentLen = buffer.length;
+ const varintLen = varuint.encodingLength(i);
+
+ buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
+ varuint.encode(i, buffer, currentLen);
+ }
+
+ function writeVarSlice(slice: Buffer): void {
+ writeVarInt(slice.length);
+ writeSlice(slice);
+ }
+
+ function writeVector(vector: Buffer[]): void {
+ writeVarInt(vector.length);
+ vector.forEach(writeVarSlice);
+ }
+
+ writeVector(witness);
+
+ return buffer;
+}
+
+export function pubkeyPositionInScript(pubkey: Buffer, script: Buffer): number {
+ const pubkeyHash = hash160(pubkey);
+ const pubkeyXOnly = pubkey.slice(1, 33); // slice before calling?
+
+ const decompiled = bscript.decompile(script);
+ if (decompiled === null) throw new Error('Unknown script error');
+
+ return decompiled.findIndex(element => {
+ if (typeof element === 'number') return false;
+ return (
+ element.equals(pubkey) ||
+ element.equals(pubkeyHash) ||
+ element.equals(pubkeyXOnly)
+ );
+ });
+}
+
+export function pubkeyInScript(pubkey: Buffer, script: Buffer): boolean {
+ return pubkeyPositionInScript(pubkey, script) !== -1;
+}
+
+export function checkInputForSig(input: PsbtInput, action: string): boolean {
+ const pSigs = extractPartialSigs(input);
+ return pSigs.some(pSig =>
+ signatureBlocksAction(pSig, bscript.signature.decode, action),
+ );
+}
+
+type SignatureDecodeFunc = (
+ buffer: Buffer,
+) => {
+ signature: Buffer;
+ hashType: number;
+};
+export function signatureBlocksAction(
+ signature: Buffer,
+ signatureDecodeFn: SignatureDecodeFunc,
+ action: string,
+): boolean {
+ const { hashType } = signatureDecodeFn(signature);
+ const whitelist: string[] = [];
+ const isAnyoneCanPay = hashType & Transaction.SIGHASH_ANYONECANPAY;
+ if (isAnyoneCanPay) whitelist.push('addInput');
+ const hashMod = hashType & 0x1f;
+ switch (hashMod) {
+ case Transaction.SIGHASH_ALL:
+ break;
+ case Transaction.SIGHASH_SINGLE:
+ case Transaction.SIGHASH_NONE:
+ whitelist.push('addOutput');
+ whitelist.push('setInputSequence');
+ break;
+ }
+ if (whitelist.indexOf(action) === -1) {
+ return true;
+ }
+ return false;
+}
+
+function extractPartialSigs(input: PsbtInput): Buffer[] {
+ let pSigs: PartialSig[] = [];
+ if ((input.partialSig || []).length === 0) {
+ if (!input.finalScriptSig && !input.finalScriptWitness) return [];
+ pSigs = getPsigsFromInputFinalScripts(input);
+ } else {
+ pSigs = input.partialSig!;
+ }
+ return pSigs.map(p => p.signature);
+}
+
+function getPsigsFromInputFinalScripts(input: PsbtInput): PartialSig[] {
+ const scriptItems = !input.finalScriptSig
+ ? []
+ : bscript.decompile(input.finalScriptSig) || [];
+ const witnessItems = !input.finalScriptWitness
+ ? []
+ : bscript.decompile(input.finalScriptWitness) || [];
+ return scriptItems
+ .concat(witnessItems)
+ .filter(item => {
+ return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item);
+ })
+ .map(sig => ({ signature: sig })) as PartialSig[];
+}
diff --git a/ts_src/types.ts b/ts_src/types.ts
index c035b4008..b904fd49b 100644
--- a/ts_src/types.ts
+++ b/ts_src/types.ts
@@ -1,4 +1,5 @@
import { Buffer as NBuffer } from 'buffer';
+
export const typeforce = require('typeforce');
const ZERO32 = NBuffer.alloc(32, 0);
@@ -6,6 +7,7 @@ const EC_P = NBuffer.from(
'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f',
'hex',
);
+
export function isPoint(p: Buffer | number | undefined | null): boolean {
if (!NBuffer.isBuffer(p)) return false;
if (p.length < 33) return false;
@@ -65,6 +67,48 @@ export const Network = typeforce.compile({
wif: typeforce.UInt8,
});
+export interface XOnlyPointAddTweakResult {
+ parity: 1 | 0;
+ xOnlyPubkey: Uint8Array;
+}
+
+export interface Tapleaf {
+ output: Buffer;
+ version?: number;
+}
+
+export const TAPLEAF_VERSION_MASK = 0xfe;
+export function isTapleaf(o: any): o is Tapleaf {
+ if (!o || !('output' in o)) return false;
+ if (!NBuffer.isBuffer(o.output)) return false;
+ if (o.version !== undefined)
+ return (o.version & TAPLEAF_VERSION_MASK) === o.version;
+ return true;
+}
+
+/**
+ * Binary tree repsenting script path spends for a Taproot input.
+ * Each node is either a single Tapleaf, or a pair of Tapleaf | Taptree.
+ * The tree has no balancing requirements.
+ */
+export type Taptree = [Taptree | Tapleaf, Taptree | Tapleaf] | Tapleaf;
+
+export function isTaptree(scriptTree: any): scriptTree is Taptree {
+ if (!Array(scriptTree)) return isTapleaf(scriptTree);
+ if (scriptTree.length !== 2) return false;
+ return scriptTree.every((t: any) => isTaptree(t));
+}
+
+export interface TinySecp256k1Interface {
+ isXOnlyPoint(p: Uint8Array): boolean;
+ xOnlyPointAddTweak(
+ p: Uint8Array,
+ tweak: Uint8Array,
+ ): XOnlyPointAddTweakResult | null;
+ privateAdd(d: Uint8Array, tweak: Uint8Array): Uint8Array | null;
+ privateNegate(d: Uint8Array): Uint8Array;
+}
+
export const Buffer256bit = typeforce.BufferN(32);
export const Hash160bit = typeforce.BufferN(20);
export const Hash256bit = typeforce.BufferN(32);