├── .env-tpl ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── bitvm.ts ├── blockstream_utils.ts ├── index.ts ├── savm.ts ├── script_help.ts ├── utils.ts └── witness_stack_to_script_witness.ts ├── tsconfig.json └── yarn.lock /.env-tpl: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY=${YOUR_PRIVATE_KEY} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Oghenovo Usiwoma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BitVM-Research 2 | ## Overview 3 | Currently, this repository has implemented the complete processes of BitVM, which can help you understand them faster. 4 | 5 | ## Article link 6 | 7 | https://bitlayerlabs.notion.site/Experiment-of-BitVM-White-Paper-ef87e719001e4e2d83765c68f1bb8443 8 | 9 | ## Usage 10 | 11 | * node version 12 | > use nvm to switch node version 13 | ``` 14 | nvm install v12.22.9 15 | nvm use v12.22.9 16 | ``` 17 | 18 | * .env file 19 | 20 | ***REMEMBER* to modify the private key** 21 | 22 | ``` 23 | cp .env-tpl .env 24 | ``` 25 | 26 | testnet faucet: 27 | ``` 28 | https://faucet.bitvmcn.xyz/ 29 | https://testnet.help/en/btcfaucet/testnet#log 30 | https://coinfaucet.eu/en/ 31 | https://bitcoinfaucet.uo1.net/send.php 32 | ``` 33 | 34 | ### BitVM 35 | If you want to run the normal process of BitVM, you can execute the following command: 36 | ``` 37 | yarn run start_process_trace 38 | ``` 39 | If you want to run the case where the Prover is penalized for equivocation in BitVM, you can execute the following command: 40 | ``` 41 | yarn run start_process_trace_with_equivocation 42 | ``` 43 | If you want to execute a single bitvalue_commitment in BitVM, you can run the following command: 44 | ``` 45 | yarn run start_bitvm_bitvalue_commitment 46 | ``` 47 | If you want to execute a single NAND_gate in bitvm, you can run the following command: 48 | ``` 49 | yarn run start_bitvm_NAND_gate 50 | ``` 51 | 52 | #### Transfer sats to the taproot address corresponding to the program 53 | 54 | When you meeting `Waiting till UTXO is detected at addr: tb1pqnvkahjv98sl9m28mpyhejx23jequp4ehvsy844yrd6au8q88hxquk3zxl`, 55 | you need to transfer some satoishs to this account. 56 | 57 | The consumption of each transaction is 250 Sats. 58 | 59 | The consumption of programs: 60 | start_process_trace: 2000 sats. 61 | start_process_trace_with_equivocation: 2500 sats 62 | start_bitvm_nand_gate: 500 sats 63 | start_bitvm_bitvalue_commitment: 500 sats 64 | 65 | 66 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taproot-with-bitcoinjs", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/dotenv": { 8 | "version": "8.2.0", 9 | "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-8.2.0.tgz", 10 | "integrity": "sha512-ylSC9GhfRH7m1EUXBXofhgx4lUWmFeQDINW5oLuS+gxWdfUeW4zJdeVTYVkexEW+e2VUvlZR2kGnGGipAWR7kw==", 11 | "dev": true, 12 | "requires": { 13 | "dotenv": "*" 14 | } 15 | }, 16 | "@types/node": { 17 | "version": "18.13.0", 18 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz", 19 | "integrity": "sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==", 20 | "dev": true 21 | }, 22 | "asynckit": { 23 | "version": "0.4.0", 24 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 25 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 26 | }, 27 | "axios": { 28 | "version": "1.3.2", 29 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.2.tgz", 30 | "integrity": "sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==", 31 | "requires": { 32 | "follow-redirects": "^1.15.0", 33 | "form-data": "^4.0.0", 34 | "proxy-from-env": "^1.1.0" 35 | } 36 | }, 37 | "base-x": { 38 | "version": "3.0.9", 39 | "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", 40 | "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", 41 | "requires": { 42 | "safe-buffer": "^5.0.1" 43 | } 44 | }, 45 | "bech32": { 46 | "version": "2.0.0", 47 | "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", 48 | "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" 49 | }, 50 | "bip174": { 51 | "version": "2.1.0", 52 | "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.0.tgz", 53 | "integrity": "sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA==" 54 | }, 55 | "bitcoinjs-lib": { 56 | "version": "6.1.0", 57 | "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.0.tgz", 58 | "integrity": "sha512-eupi1FBTJmPuAZdChnzTXLv2HBqFW2AICpzXZQLniP0V9FWWeeUQSMKES6sP8isy/xO0ijDexbgkdEyFVrsuJw==", 59 | "requires": { 60 | "bech32": "^2.0.0", 61 | "bip174": "^2.1.0", 62 | "bs58check": "^2.1.2", 63 | "create-hash": "^1.1.0", 64 | "ripemd160": "^2.0.2", 65 | "typeforce": "^1.11.3", 66 | "varuint-bitcoin": "^1.1.2", 67 | "wif": "^2.0.1" 68 | } 69 | }, 70 | "bs58": { 71 | "version": "4.0.1", 72 | "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", 73 | "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", 74 | "requires": { 75 | "base-x": "^3.0.2" 76 | } 77 | }, 78 | "bs58check": { 79 | "version": "2.1.2", 80 | "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", 81 | "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", 82 | "requires": { 83 | "bs58": "^4.0.0", 84 | "create-hash": "^1.1.0", 85 | "safe-buffer": "^5.1.2" 86 | } 87 | }, 88 | "cipher-base": { 89 | "version": "1.0.4", 90 | "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", 91 | "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", 92 | "requires": { 93 | "inherits": "^2.0.1", 94 | "safe-buffer": "^5.0.1" 95 | } 96 | }, 97 | "combined-stream": { 98 | "version": "1.0.8", 99 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 100 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 101 | "requires": { 102 | "delayed-stream": "~1.0.0" 103 | } 104 | }, 105 | "create-hash": { 106 | "version": "1.2.0", 107 | "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", 108 | "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", 109 | "requires": { 110 | "cipher-base": "^1.0.1", 111 | "inherits": "^2.0.1", 112 | "md5.js": "^1.3.4", 113 | "ripemd160": "^2.0.1", 114 | "sha.js": "^2.4.0" 115 | } 116 | }, 117 | "delayed-stream": { 118 | "version": "1.0.0", 119 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 120 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" 121 | }, 122 | "dotenv": { 123 | "version": "16.4.4", 124 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz", 125 | "integrity": "sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg==", 126 | "dev": true 127 | }, 128 | "ecpair": { 129 | "version": "2.1.0", 130 | "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz", 131 | "integrity": "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==", 132 | "requires": { 133 | "randombytes": "^2.1.0", 134 | "typeforce": "^1.18.0", 135 | "wif": "^2.0.6" 136 | } 137 | }, 138 | "follow-redirects": { 139 | "version": "1.15.2", 140 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 141 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" 142 | }, 143 | "form-data": { 144 | "version": "4.0.0", 145 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 146 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 147 | "requires": { 148 | "asynckit": "^0.4.0", 149 | "combined-stream": "^1.0.8", 150 | "mime-types": "^2.1.12" 151 | } 152 | }, 153 | "hash-base": { 154 | "version": "3.1.0", 155 | "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", 156 | "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", 157 | "requires": { 158 | "inherits": "^2.0.4", 159 | "readable-stream": "^3.6.0", 160 | "safe-buffer": "^5.2.0" 161 | } 162 | }, 163 | "inherits": { 164 | "version": "2.0.4", 165 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 166 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 167 | }, 168 | "md5.js": { 169 | "version": "1.3.5", 170 | "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", 171 | "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", 172 | "requires": { 173 | "hash-base": "^3.0.0", 174 | "inherits": "^2.0.1", 175 | "safe-buffer": "^5.1.2" 176 | } 177 | }, 178 | "mime-db": { 179 | "version": "1.52.0", 180 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 181 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 182 | }, 183 | "mime-types": { 184 | "version": "2.1.35", 185 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 186 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 187 | "requires": { 188 | "mime-db": "1.52.0" 189 | } 190 | }, 191 | "prettier": { 192 | "version": "3.0.3", 193 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", 194 | "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", 195 | "dev": true 196 | }, 197 | "proxy-from-env": { 198 | "version": "1.1.0", 199 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 200 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 201 | }, 202 | "randombytes": { 203 | "version": "2.1.0", 204 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 205 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 206 | "requires": { 207 | "safe-buffer": "^5.1.0" 208 | } 209 | }, 210 | "readable-stream": { 211 | "version": "3.6.0", 212 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", 213 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", 214 | "requires": { 215 | "inherits": "^2.0.3", 216 | "string_decoder": "^1.1.1", 217 | "util-deprecate": "^1.0.1" 218 | } 219 | }, 220 | "ripemd160": { 221 | "version": "2.0.2", 222 | "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", 223 | "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", 224 | "requires": { 225 | "hash-base": "^3.0.0", 226 | "inherits": "^2.0.1" 227 | } 228 | }, 229 | "safe-buffer": { 230 | "version": "5.2.1", 231 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 232 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 233 | }, 234 | "sha.js": { 235 | "version": "2.4.11", 236 | "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", 237 | "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", 238 | "requires": { 239 | "inherits": "^2.0.1", 240 | "safe-buffer": "^5.0.1" 241 | } 242 | }, 243 | "string_decoder": { 244 | "version": "1.3.0", 245 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 246 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 247 | "requires": { 248 | "safe-buffer": "~5.2.0" 249 | } 250 | }, 251 | "tiny-secp256k1": { 252 | "version": "2.2.1", 253 | "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.1.tgz", 254 | "integrity": "sha512-/U4xfVqnVxJXN4YVsru0E6t5wVncu2uunB8+RVR40fYUxkKYUPS10f+ePQZgFBoE/Jbf9H1NBveupF2VmB58Ng==", 255 | "requires": { 256 | "uint8array-tools": "0.0.7" 257 | } 258 | }, 259 | "typeforce": { 260 | "version": "1.18.0", 261 | "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", 262 | "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" 263 | }, 264 | "typescript": { 265 | "version": "4.9.5", 266 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", 267 | "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", 268 | "dev": true 269 | }, 270 | "uint8array-tools": { 271 | "version": "0.0.7", 272 | "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz", 273 | "integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==" 274 | }, 275 | "util-deprecate": { 276 | "version": "1.0.2", 277 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 278 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 279 | }, 280 | "varuint-bitcoin": { 281 | "version": "1.1.2", 282 | "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", 283 | "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", 284 | "requires": { 285 | "safe-buffer": "^5.1.1" 286 | } 287 | }, 288 | "wif": { 289 | "version": "2.0.6", 290 | "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", 291 | "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", 292 | "requires": { 293 | "bs58check": "<3.0.0" 294 | } 295 | } 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taproot-with-bitcoinjs", 3 | "version": "1.0.0", 4 | "main": "dist/index.js", 5 | "repository": "git@github.com:Eunovo/taproot-with-bitcoinjs.git", 6 | "author": "Eunovo ", 7 | "license": "MIT", 8 | "devDependencies": { 9 | "@types/dotenv": "^8.2.0", 10 | "@types/node": "^18.13.0", 11 | "prettier": "^3.0.3", 12 | "typescript": "^4.9.5" 13 | }, 14 | "dependencies": { 15 | "axios": "^1.3.2", 16 | "bitcoinjs-lib": "^6.1.0", 17 | "ecpair": "^2.1.0", 18 | "tiny-secp256k1": "^2.2.1", 19 | "varuint-bitcoin": "^1.1.2" 20 | }, 21 | "scripts": { 22 | "build": "tsc", 23 | "start": "yarn build && node dist/index.js", 24 | "start_process_trace": "yarn build && node dist/index.js process_trace", 25 | "start_process_trace_with_equivocation": "yarn build && node dist/index.js process_trace_with_equivocation", 26 | "start_bitvm_NAND_gate": "yarn build && node dist/index.js bitvm_NAND_gate", 27 | "start_bitvm_bitvalue_commitment": "yarn build && node dist/index.js bitvm_bitvalue_commitment", 28 | "start_savm_bit_commitment_tx": "yarn build && node dist/index.js savm_bit_commitment_tx" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/bitvm.ts: -------------------------------------------------------------------------------- 1 | import { 2 | initEccLib, 3 | networks, 4 | script, 5 | Signer, 6 | payments, 7 | crypto, 8 | Psbt, 9 | } from "bitcoinjs-lib"; 10 | import { broadcast, waitUntilUTXO } from "./blockstream_utils"; 11 | import { ECPairFactory, ECPairAPI, TinySecp256k1Interface } from "ecpair"; 12 | import { Taptree, Tapleaf } from "bitcoinjs-lib/src/types"; 13 | import { witnessStackToScriptWitness } from "./witness_stack_to_script_witness"; 14 | 15 | const tinysecp: TinySecp256k1Interface = require("tiny-secp256k1"); 16 | initEccLib(tinysecp as any); 17 | const ECPair: ECPairAPI = ECPairFactory(tinysecp); 18 | const network = networks.testnet; 19 | import { 20 | construct_challenge_taptree, 21 | construct_response_taptree, 22 | construct_scripts_taptree, 23 | generate_hash_lock_script, 24 | generate_equivocation_script, 25 | generate_bitcommitment_script, 26 | generate_NAND_gate_script, 27 | one_round_challenge_and_response, 28 | send_tx, 29 | } from "./script_help"; 30 | 31 | import { toXOnly } from "./utils"; 32 | 33 | export async function bitvm_NAND_gate(keypair: Signer) { 34 | const C0 = Buffer.from([0x64]); // set to 35 | const C1 = Buffer.from([0x65]); // set to 36 | 37 | const B0 = Buffer.from([0x66]); 38 | const B1 = Buffer.from([0x67]); 39 | 40 | const A0 = Buffer.from([0x68]); 41 | const A1 = Buffer.from([0x69]); 42 | 43 | const C0_hash = crypto.hash160(C0); 44 | const C1_hash = crypto.hash160(C1); 45 | 46 | const B0_hash = crypto.hash160(B0); 47 | const B1_hash = crypto.hash160(B1); 48 | 49 | const A0_hash = crypto.hash160(A0); 50 | const A1_hash = crypto.hash160(A1); 51 | 52 | // construct NAND gate script 53 | const C_bitvalue_script = generate_bitcommitment_script(C0_hash, C1_hash); 54 | const B_bitvalue_script = generate_bitcommitment_script(B0_hash, B1_hash); 55 | const A_bitvalue_script = generate_bitcommitment_script(A0_hash, A1_hash); 56 | 57 | // OP_BOOLAND if both a and b are not 0, the output is 1.Otherwise 0. 58 | // 1 1 1 59 | // 0 1 0 60 | // 0 0 0 61 | // 0 1 0 62 | 63 | // NAND gate 64 | // 1 1 0 65 | // 1 0 1 66 | // 0 1 1 67 | // 0 0 1 68 | 69 | // we can use OP_BOOLAND OP_NOT to implement NAND gate 70 | const complete_script_asm = `${C_bitvalue_script} OP_TOALTSTACK ${B_bitvalue_script} OP_TOALTSTACK ${A_bitvalue_script} OP_FROMALTSTACK OP_BOOLAND OP_NOT OP_FROMALTSTACK OP_EQUALVERIFY OP_1`; 71 | console.log(`complete script:${complete_script_asm}`); 72 | const complete_script = script.fromASM(complete_script_asm); 73 | 74 | // construct p2pk script 75 | const p2pk_script_asm = `${toXOnly(keypair.publicKey).toString( 76 | "hex", 77 | )} OP_CHECKSIG`; 78 | console.log(`p2pk_script_asm:${p2pk_script_asm}`); 79 | const p2pk_script = script.fromASM(p2pk_script_asm); 80 | 81 | const scriptTree: Taptree = [ 82 | { 83 | output: p2pk_script, 84 | }, 85 | { 86 | output: complete_script, 87 | }, 88 | ]; 89 | 90 | const NAND_script_redeem = { 91 | output: complete_script, 92 | redeemVersion: 192, 93 | }; 94 | 95 | const NAND_p2tr = payments.p2tr({ 96 | internalPubkey: toXOnly(keypair.publicKey), 97 | scriptTree, 98 | redeem: NAND_script_redeem, 99 | network, 100 | }); 101 | 102 | // taproot address generated by taptree + keypair 103 | const script_addr = NAND_p2tr.address ?? ""; 104 | 105 | console.log( 106 | `bitvalue_script_redeem.output (p2pk_script):${NAND_script_redeem.output.toString( 107 | "hex", 108 | )}`, 109 | ); 110 | console.log(`witnessUtxo: ${NAND_p2tr.output!.toString("hex")}`); 111 | 112 | console.log(`Waiting till UTXO is detected at this Address: ${script_addr}`); 113 | let utxos = await waitUntilUTXO(script_addr); 114 | console.log( 115 | `Trying the Hash lock spend path with UTXO ${utxos[0].txid}:${utxos[0].vout}`, 116 | ); 117 | 118 | const tapLeafScript = { 119 | leafVersion: NAND_script_redeem.redeemVersion, 120 | script: NAND_script_redeem.output, 121 | controlBlock: NAND_p2tr.witness![NAND_p2tr.witness!.length - 1], 122 | }; 123 | 124 | const psbt = new Psbt({ network }); 125 | psbt.addInput({ 126 | hash: utxos[0].txid, 127 | index: utxos[0].vout, 128 | witnessUtxo: { value: utxos[0].value, script: NAND_p2tr.output! }, 129 | tapLeafScript: [tapLeafScript], 130 | }); 131 | 132 | psbt.addOutput({ 133 | address: "mscxdTxVSoR8VyRkZEGJ4dxECJXcXfQqVz", // acount1 address 134 | value: utxos[0].value - 150, 135 | }); 136 | 137 | const customFinalizer = (_inputIndex: number, input: any) => { 138 | const scriptSolution = [ 139 | // A = 1 140 | A1, 141 | Buffer.from([0x01]), // OP_IF solution 142 | //B = 0 143 | B0, 144 | Buffer.from([]), // OP_ELSE solution 145 | //C = 1 146 | C1, 147 | Buffer.from([0x01]), // OP_IF solution 148 | ]; 149 | const witness = scriptSolution 150 | .concat(tapLeafScript.script) 151 | .concat(tapLeafScript.controlBlock); 152 | 153 | return { 154 | finalScriptWitness: witnessStackToScriptWitness(witness), 155 | }; 156 | }; 157 | 158 | psbt.finalizeInput(0, customFinalizer); 159 | let tx = psbt.extractTransaction(); 160 | console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); 161 | let txid = await broadcast(tx.toHex()); 162 | console.log(`Success! Txid is ${txid}`); 163 | } 164 | 165 | export async function bitvm_bitvalue_commitment(keypair: Signer) { 166 | console.log(`Running Bitvalue Commitment`); 167 | 168 | const secret_bytes_1 = Buffer.from([0x62]); 169 | const secret_bytes_2 = Buffer.from([0x63]); 170 | const hash_1 = crypto.hash160(secret_bytes_1); 171 | const hash_2 = crypto.hash160(secret_bytes_2); 172 | const const_value_1 = Buffer.from([0x1]); 173 | const const_value_0 = Buffer.from([0x0]); 174 | 175 | // Construct bitvalue script 176 | const bitvalue_keypair = ECPair.makeRandom({ network }); 177 | const bitvalue_script_asm = `OP_IF OP_HASH160 ${hash_1.toString( 178 | "hex", 179 | )} OP_EQUALVERIFY ${const_value_1.toString( 180 | "hex", 181 | )} OP_ELSE OP_HASH160 ${hash_2.toString( 182 | "hex", 183 | )} OP_EQUALVERIFY ${const_value_0.toString("hex")} OP_ENDIF`; 184 | const bitvalue_script = script.fromASM(bitvalue_script_asm); 185 | 186 | // construct p2pk script 187 | const p2pk_script_asm = `${toXOnly(keypair.publicKey).toString( 188 | "hex", 189 | )} OP_CHECKSIG`; 190 | const p2pk_script = script.fromASM(p2pk_script_asm); 191 | 192 | const scriptTree: Taptree = [ 193 | { 194 | output: p2pk_script, 195 | }, 196 | { 197 | output: bitvalue_script, 198 | }, 199 | ]; 200 | 201 | const bitvalue_script_redeem = { 202 | output: bitvalue_script, 203 | redeemVersion: 192, 204 | }; 205 | 206 | const bitvalue_p2tr = payments.p2tr({ 207 | internalPubkey: toXOnly(keypair.publicKey), 208 | scriptTree, 209 | redeem: bitvalue_script_redeem, 210 | network, 211 | }); 212 | 213 | // taproot address generated by taptree + keypair 214 | const script_addr = bitvalue_p2tr.address ?? ""; 215 | 216 | console.log( 217 | `bitvalue_script_redeem.output (p2pk_script):${bitvalue_script_redeem.output.toString( 218 | "hex", 219 | )}`, 220 | ); 221 | console.log(`witnessUtxo: ${bitvalue_p2tr.output!.toString("hex")}`); 222 | 223 | console.log(`Waiting till UTXO is detected at this Address: ${script_addr}`); 224 | let utxos = await waitUntilUTXO(script_addr); 225 | console.log( 226 | `Trying the Hash lock spend path with UTXO ${utxos[0].txid}:${utxos[0].vout}`, 227 | ); 228 | 229 | const tapLeafScript = { 230 | leafVersion: bitvalue_script_redeem.redeemVersion, 231 | script: bitvalue_script_redeem.output, 232 | controlBlock: bitvalue_p2tr.witness![bitvalue_p2tr.witness!.length - 1], 233 | }; 234 | 235 | const psbt = new Psbt({ network }); 236 | psbt.addInput({ 237 | hash: utxos[0].txid, 238 | index: utxos[0].vout, 239 | witnessUtxo: { value: utxos[0].value, script: bitvalue_p2tr.output! }, 240 | tapLeafScript: [tapLeafScript], 241 | }); 242 | 243 | psbt.addOutput({ 244 | address: "mscxdTxVSoR8VyRkZEGJ4dxECJXcXfQqVz", // acount1 address 245 | value: utxos[0].value - 150, 246 | }); 247 | 248 | // psbt.signInput(0, bitvalue_keypair); 249 | 250 | // We have to construct our witness script in a custom finalizer 251 | 252 | const customFinalizer = (_inputIndex: number, input: any) => { 253 | const scriptSolution = [secret_bytes_1, Buffer.from([0x1])]; 254 | const witness = scriptSolution 255 | .concat(tapLeafScript.script) 256 | .concat(tapLeafScript.controlBlock); 257 | 258 | return { 259 | finalScriptWitness: witnessStackToScriptWitness(witness), 260 | }; 261 | }; 262 | 263 | psbt.finalizeInput(0, customFinalizer); 264 | 265 | let tx = psbt.extractTransaction(); 266 | console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); 267 | let txid = await broadcast(tx.toHex()); 268 | console.log(`Success! Txid is ${txid}`); 269 | } 270 | 271 | export async function bitvm_bitvalue_commitment_with_sig(keypair: Signer) { 272 | console.log(`Running Bitvalue Commitment`); 273 | 274 | const secret_bytes_1 = Buffer.from([0x62]); 275 | const secret_bytes_2 = Buffer.from([0x63]); 276 | const hash_1 = crypto.hash160(secret_bytes_1); 277 | const hash_2 = crypto.hash160(secret_bytes_2); 278 | const const_value_1 = Buffer.from([0x1]); 279 | const const_value_0 = Buffer.from([0x0]); 280 | 281 | // Construct bitvalue script 282 | const bitvalue_keypair = ECPair.makeRandom({ network }); 283 | const bitvalue_script_asm = `OP_IF OP_HASH160 ${hash_1.toString( 284 | "hex", 285 | )} OP_EQUALVERIFY ${const_value_1.toString( 286 | "hex", 287 | )} OP_ELSE OP_HASH160 ${hash_2.toString( 288 | "hex", 289 | )} OP_EQUALVERIFY ${const_value_0.toString("hex")} OP_ENDIF ${toXOnly( 290 | bitvalue_keypair.publicKey, 291 | ).toString("hex")} OP_CHECKSIG`; 292 | const bitvalue_script = script.fromASM(bitvalue_script_asm); 293 | 294 | // construct p2pk script 295 | const p2pk_script_asm = `${toXOnly(keypair.publicKey).toString( 296 | "hex", 297 | )} OP_CHECKSIG`; 298 | const p2pk_script = script.fromASM(p2pk_script_asm); 299 | 300 | const scriptTree: Taptree = [ 301 | { 302 | output: p2pk_script, 303 | }, 304 | { 305 | output: bitvalue_script, 306 | }, 307 | ]; 308 | 309 | const bitvalue_script_redeem = { 310 | output: bitvalue_script, 311 | redeemVersion: 192, 312 | }; 313 | 314 | const bitvalue_p2tr = payments.p2tr({ 315 | internalPubkey: toXOnly(keypair.publicKey), 316 | scriptTree, 317 | redeem: bitvalue_script_redeem, 318 | network, 319 | }); 320 | 321 | // taproot address generated by taptree + keypair 322 | const script_addr = bitvalue_p2tr.address ?? ""; 323 | 324 | console.log( 325 | `bitvalue_script_redeem.output (p2pk_script):${bitvalue_script_redeem.output.toString( 326 | "hex", 327 | )}`, 328 | ); 329 | console.log(`witnessUtxo: ${bitvalue_p2tr.output!.toString("hex")}`); 330 | 331 | console.log(`Waiting till UTXO is detected at this Address: ${script_addr}`); 332 | let utxos = await waitUntilUTXO(script_addr); 333 | console.log( 334 | `Trying the Hash lock spend path with UTXO ${utxos[0].txid}:${utxos[0].vout}`, 335 | ); 336 | 337 | const tapLeafScript = { 338 | leafVersion: bitvalue_script_redeem.redeemVersion, 339 | script: bitvalue_script_redeem.output, 340 | controlBlock: bitvalue_p2tr.witness![bitvalue_p2tr.witness!.length - 1], 341 | }; 342 | 343 | const psbt = new Psbt({ network }); 344 | psbt.addInput({ 345 | hash: utxos[0].txid, 346 | index: utxos[0].vout, 347 | witnessUtxo: { value: utxos[0].value, script: bitvalue_p2tr.output! }, 348 | tapLeafScript: [tapLeafScript], 349 | }); 350 | 351 | psbt.addOutput({ 352 | address: "mscxdTxVSoR8VyRkZEGJ4dxECJXcXfQqVz", // acount1 address 353 | value: utxos[0].value - 150, 354 | }); 355 | 356 | psbt.signInput(0, bitvalue_keypair); 357 | 358 | // We have to construct our witness script in a custom finalizer 359 | 360 | const customFinalizer = (_inputIndex: number, input: any) => { 361 | const scriptSolution = [ 362 | input.tapScriptSig[0].signature, 363 | secret_bytes_1, 364 | Buffer.from([0x1]), 365 | ]; 366 | const witness = scriptSolution 367 | .concat(tapLeafScript.script) 368 | .concat(tapLeafScript.controlBlock); 369 | 370 | return { 371 | finalScriptWitness: witnessStackToScriptWitness(witness), 372 | }; 373 | }; 374 | 375 | psbt.finalizeInput(0, customFinalizer); 376 | 377 | let tx = psbt.extractTransaction(); 378 | console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); 379 | let txid = await broadcast(tx.toHex()); 380 | console.log(`Success! Txid is ${txid}`); 381 | } 382 | 383 | // A NAND B = E; C NAND D = F; E NAND F = H; 384 | // 1 NAND 0 = 1; 1 NAND 1 = 0; 0 NAND 1 = 1; 385 | export async function process_trace(keypair: Signer) { 386 | const A0 = Buffer.from([0x68]); 387 | const A1 = Buffer.from([0x69]); 388 | 389 | const B0 = Buffer.from([0x66]); 390 | const B1 = Buffer.from([0x67]); 391 | 392 | const C0 = Buffer.from([0x64]); // set to 393 | const C1 = Buffer.from([0x65]); // set to 394 | 395 | const D0 = Buffer.from([0x63]); // set to 396 | const D1 = Buffer.from([0x62]); // set to 397 | 398 | const E0 = Buffer.from([0x70]); // set to 399 | const E1 = Buffer.from([0x71]); // set to 400 | 401 | const F0 = Buffer.from([0x72]); // set to 402 | const F1 = Buffer.from([0x73]); // set to 403 | 404 | const G0 = Buffer.from([0x74]); // set to 405 | const G1 = Buffer.from([0x75]); // set to 406 | 407 | const A0_hash = crypto.hash160(A0); 408 | const A1_hash = crypto.hash160(A1); 409 | 410 | const B0_hash = crypto.hash160(B0); 411 | const B1_hash = crypto.hash160(B1); 412 | 413 | const C0_hash = crypto.hash160(C0); 414 | const C1_hash = crypto.hash160(C1); 415 | 416 | const D0_hash = crypto.hash160(D0); 417 | const D1_hash = crypto.hash160(D1); 418 | 419 | const E0_hash = crypto.hash160(E0); 420 | const E1_hash = crypto.hash160(E1); 421 | 422 | const F0_hash = crypto.hash160(F0); 423 | const F1_hash = crypto.hash160(F1); 424 | 425 | const G0_hash = crypto.hash160(G0); 426 | const G1_hash = crypto.hash160(G1); 427 | 428 | // NAND script construct 429 | let NAND_1_script = generate_NAND_gate_script( 430 | A0_hash, 431 | A1_hash, 432 | B0_hash, 433 | B1_hash, 434 | E0_hash, 435 | E1_hash, 436 | ); 437 | let NAND_2_script = generate_NAND_gate_script( 438 | C0_hash, 439 | C1_hash, 440 | D0_hash, 441 | D1_hash, 442 | F0_hash, 443 | F1_hash, 444 | ); 445 | let NAND_3_script = generate_NAND_gate_script( 446 | E0_hash, 447 | E1_hash, 448 | F0_hash, 449 | F1_hash, 450 | G0_hash, 451 | G1_hash, 452 | ); 453 | 454 | // equivocation_script 455 | let A_equivocation_script = generate_equivocation_script(A0_hash, A1_hash); 456 | let B_equivocation_script = generate_equivocation_script(B0_hash, B1_hash); 457 | let C_equivocation_script = generate_equivocation_script(C0_hash, C1_hash); 458 | let D_equivocation_script = generate_equivocation_script(D0_hash, D1_hash); 459 | let E_equivocation_script = generate_equivocation_script(E0_hash, E1_hash); 460 | let F_equivocation_script = generate_equivocation_script(F0_hash, F1_hash); 461 | let G_equivocation_script = generate_equivocation_script(G0_hash, G1_hash); 462 | 463 | // hash lock script 464 | const NAND_1_challenge_preimage = Buffer.from([0x1]); 465 | const NAND_1_challenge_hash = crypto.hash160(NAND_1_challenge_preimage); 466 | 467 | const NAND_2_challenge_preimage = Buffer.from([0x2]); 468 | const NAND_2_challenge_hash = crypto.hash160(NAND_2_challenge_preimage); 469 | 470 | const NAND_3_challenge_preimage = Buffer.from([0x3]); 471 | const NAND_3_challenge_hash = crypto.hash160(NAND_3_challenge_preimage); 472 | 473 | const NAND_1_hash_lock_script = generate_hash_lock_script( 474 | NAND_1_challenge_hash, 475 | ); 476 | const NAND_2_hash_lock_script = generate_hash_lock_script( 477 | NAND_2_challenge_hash, 478 | ); 479 | const NAND_3_hash_lock_script = generate_hash_lock_script( 480 | NAND_3_challenge_hash, 481 | ); 482 | 483 | // todo: construct challenge taproot tree 484 | let challenge_tree = construct_challenge_taptree( 485 | NAND_1_hash_lock_script, 486 | NAND_2_hash_lock_script, 487 | NAND_3_hash_lock_script, 488 | ); 489 | 490 | // todo: construct Responses taproot tree 491 | let hash_lock_and_NAND_1_script = `${NAND_1_hash_lock_script} ${NAND_1_script}`; 492 | let hash_lock_and_NAND_2_script = `${NAND_2_hash_lock_script} ${NAND_2_script}`; 493 | let hash_lock_and_NAND_3_script = `${NAND_3_hash_lock_script} ${NAND_3_script}`; 494 | let response_taptree = construct_response_taptree( 495 | hash_lock_and_NAND_1_script, 496 | hash_lock_and_NAND_2_script, 497 | hash_lock_and_NAND_3_script, 498 | ); 499 | 500 | // todo: construct equivocation_script taproot tree 501 | let equivocation_taptree = construct_scripts_taptree([ 502 | A_equivocation_script, 503 | B_equivocation_script, 504 | C_equivocation_script, 505 | D_equivocation_script, 506 | E_equivocation_script, 507 | F_equivocation_script, 508 | G_equivocation_script, 509 | ]); 510 | 511 | // --- challenge and response process --- 512 | // A,B,C,D is the inputs for the program and the G is the output for the program 513 | // 1. verifer challenge NAND1 through revealing the `NAND_1_challenge_preimage` 514 | // 2. prover response NAND1 through enter the `NAND_1_challenge_preimage` and `reveal the input and output for NAND1` 515 | // 3. verifer challenge NAND2 through revealing the `NAND_2_challenge_preimage` 516 | // 4. prover response NAND2 through enter the `NAND_2_challenge_preimage` and `reveal the input and output for NAND2` 517 | // 5. verifer challenge NAND3 through revealing the `NAND_3_challenge_preimage` 518 | // 6. prover response NAND3 through enter the `NAND_3_challenge_preimage` and `reveal the input and output for NAND3` 519 | 520 | // ======== the first round ======= 521 | // send the challenge NAND1 transaction 522 | const NAND_1_challenge_inputs = [ 523 | Buffer.from([0x01]), // we need to confirm that the stack size equals to 1 after the program execution. 524 | NAND_1_challenge_preimage, 525 | ]; 526 | 527 | // prover can response the challenge by entering the preimage which can be found from the and reveal the input and output for the NAND gate 528 | const NAND1_response_inputs = [ 529 | // A = 1 530 | A1, 531 | Buffer.from([0x01]), // OP_IF solution 532 | //B = 0 533 | B0, 534 | Buffer.from([]), // OP_ELSE solution 535 | //C = 1 536 | E1, 537 | Buffer.from([0x01]), // OP_IF solution 538 | NAND_1_challenge_preimage, // we can get this data from the verifier input_utxo of challenge tx. 539 | ]; 540 | let gate1_p2trs = one_round_challenge_and_response( 541 | NAND_1_hash_lock_script, 542 | challenge_tree, 543 | hash_lock_and_NAND_1_script, 544 | response_taptree, 545 | keypair, 546 | ); 547 | 548 | let gate1 = { 549 | p2tr: gate1_p2trs, 550 | inputs: { 551 | challenge_inputs: NAND_1_challenge_inputs, 552 | response_inputs: NAND1_response_inputs, 553 | }, 554 | }; 555 | 556 | // ======== the second round ======= 557 | let gate2_p2trs = one_round_challenge_and_response( 558 | NAND_2_hash_lock_script, 559 | challenge_tree, 560 | hash_lock_and_NAND_2_script, 561 | response_taptree, 562 | keypair, 563 | ); 564 | let gate2 = { 565 | p2tr: gate2_p2trs, 566 | inputs: { 567 | challenge_inputs: [ 568 | Buffer.from([0x01]), // we need to confirm that the stack size equals to 1 after the program execution. 569 | NAND_2_challenge_preimage, 570 | ], 571 | response_inputs: [ 572 | C1, 573 | Buffer.from([0x01]), // OP_IF solution 574 | D1, 575 | Buffer.from([0x01]), // OP_IF solution 576 | F0, 577 | Buffer.from([]), // OP_ELSE solution 578 | NAND_2_challenge_preimage, // we can get this data from the verifier input_utxo of challenge tx. 579 | ], 580 | }, 581 | }; 582 | 583 | // ======== the third round ======= 584 | let gate3_p2trs = one_round_challenge_and_response( 585 | NAND_3_hash_lock_script, 586 | challenge_tree, 587 | hash_lock_and_NAND_3_script, 588 | response_taptree, 589 | keypair, 590 | ); 591 | let gate3 = { 592 | p2tr: gate3_p2trs, 593 | inputs: { 594 | challenge_inputs: [ 595 | Buffer.from([0x01]), // we need to confirm that the stack size equals to 1 after the program execution. 596 | NAND_3_challenge_preimage, 597 | ], 598 | response_inputs: [ 599 | E1, 600 | Buffer.from([0x01]), // OP_IF solution 601 | F0, 602 | Buffer.from([]), // OP_ELSE solution 603 | G1, 604 | Buffer.from([0x01]), // OP_IF solution 605 | NAND_3_challenge_preimage, // we can get this data from the verifier input_utxo of challenge tx. 606 | ], 607 | }, 608 | }; 609 | 610 | await send_tx( 611 | "First Round Challenge", 612 | gate1.p2tr.challenge_p2tr, 613 | gate1.p2tr.response_p2tr.address!, 614 | NAND_1_challenge_inputs, 615 | keypair, 616 | ); 617 | await send_tx( 618 | "First Round Response", 619 | gate1.p2tr.response_p2tr, 620 | gate2.p2tr.challenge_p2tr.address!, 621 | NAND1_response_inputs, 622 | keypair, 623 | ); 624 | 625 | await send_tx( 626 | "Second Round Challenge", 627 | gate2.p2tr.challenge_p2tr, 628 | gate2.p2tr.response_p2tr.address!, 629 | gate2.inputs.challenge_inputs, 630 | keypair, 631 | ); 632 | await send_tx( 633 | "Second Round Response", 634 | gate2.p2tr.response_p2tr, 635 | gate3.p2tr.challenge_p2tr.address!, 636 | gate2.inputs.response_inputs, 637 | keypair, 638 | ); 639 | 640 | await send_tx( 641 | "Third Round Challenge", 642 | gate3.p2tr.challenge_p2tr, 643 | gate3.p2tr.response_p2tr.address!, 644 | gate3.inputs.challenge_inputs, 645 | keypair, 646 | ); 647 | await send_tx( 648 | "Third Round Response", 649 | gate3.p2tr.response_p2tr, 650 | "mscxdTxVSoR8VyRkZEGJ4dxECJXcXfQqVz", // prover addr 651 | gate3.inputs.response_inputs, 652 | keypair, 653 | ); 654 | } 655 | 656 | // A NAND B = E; C NAND D = F; E NAND F = G; G NAND D = H; 657 | // 1 NAND 0 = 1; 1 NAND 1 = 0; 0 NAND 1 = 1; 658 | export async function process_trace_with_equivocation(keypair: Signer) { 659 | const A0 = Buffer.from([0x68]); 660 | const A1 = Buffer.from([0x69]); 661 | 662 | const B0 = Buffer.from([0x66]); 663 | const B1 = Buffer.from([0x67]); 664 | 665 | const C0 = Buffer.from([0x64]); // set to 666 | const C1 = Buffer.from([0x65]); // set to 667 | 668 | const D0 = Buffer.from([0x63]); // set to 669 | const D1 = Buffer.from([0x62]); // set to 670 | 671 | const E0 = Buffer.from([0x70]); // set to 672 | const E1 = Buffer.from([0x71]); // set to 673 | 674 | const F0 = Buffer.from([0x72]); // set to 675 | const F1 = Buffer.from([0x73]); // set to 676 | 677 | const G0 = Buffer.from([0x74]); // set to 678 | const G1 = Buffer.from([0x75]); // set to 679 | 680 | const H0 = Buffer.from([0x76]); // set to 681 | const H1 = Buffer.from([0x77]); // set to 682 | 683 | const A0_hash = crypto.hash160(A0); 684 | const A1_hash = crypto.hash160(A1); 685 | 686 | const B0_hash = crypto.hash160(B0); 687 | const B1_hash = crypto.hash160(B1); 688 | 689 | const C0_hash = crypto.hash160(C0); 690 | const C1_hash = crypto.hash160(C1); 691 | 692 | const D0_hash = crypto.hash160(D0); 693 | const D1_hash = crypto.hash160(D1); 694 | 695 | const E0_hash = crypto.hash160(E0); 696 | const E1_hash = crypto.hash160(E1); 697 | 698 | const F0_hash = crypto.hash160(F0); 699 | const F1_hash = crypto.hash160(F1); 700 | 701 | const G0_hash = crypto.hash160(G0); 702 | const G1_hash = crypto.hash160(G1); 703 | 704 | const H0_hash = crypto.hash160(H0); 705 | const H1_hash = crypto.hash160(H1); 706 | 707 | // NAND script construct 708 | let NAND_1_script = generate_NAND_gate_script( 709 | A0_hash, 710 | A1_hash, 711 | B0_hash, 712 | B1_hash, 713 | E0_hash, 714 | E1_hash, 715 | ); 716 | let NAND_2_script = generate_NAND_gate_script( 717 | C0_hash, 718 | C1_hash, 719 | D0_hash, 720 | D1_hash, 721 | F0_hash, 722 | F1_hash, 723 | ); 724 | let NAND_3_script = generate_NAND_gate_script( 725 | E0_hash, 726 | E1_hash, 727 | F0_hash, 728 | F1_hash, 729 | G0_hash, 730 | G1_hash, 731 | ); 732 | let NAND_4_script = generate_NAND_gate_script( 733 | G0_hash, 734 | G1_hash, 735 | F0_hash, 736 | F1_hash, 737 | H0_hash, 738 | H1_hash, 739 | ); 740 | 741 | // equivocation_script 742 | let A_equivocation_script = generate_equivocation_script(A0_hash, A1_hash); 743 | let B_equivocation_script = generate_equivocation_script(B0_hash, B1_hash); 744 | let C_equivocation_script = generate_equivocation_script(C0_hash, C1_hash); 745 | let D_equivocation_script = generate_equivocation_script(D0_hash, D1_hash); 746 | let E_equivocation_script = generate_equivocation_script(E0_hash, E1_hash); 747 | let F_equivocation_script = generate_equivocation_script(F0_hash, F1_hash); 748 | let G_equivocation_script = generate_equivocation_script(G0_hash, G1_hash); 749 | let H_equivocation_script = generate_equivocation_script(H0_hash, H1_hash); 750 | 751 | // hash lock script 752 | const NAND_1_challenge_preimage = Buffer.from([0x1]); 753 | const NAND_1_challenge_hash = crypto.hash160(NAND_1_challenge_preimage); 754 | 755 | const NAND_2_challenge_preimage = Buffer.from([0x2]); 756 | const NAND_2_challenge_hash = crypto.hash160(NAND_2_challenge_preimage); 757 | 758 | const NAND_3_challenge_preimage = Buffer.from([0x3]); 759 | const NAND_3_challenge_hash = crypto.hash160(NAND_3_challenge_preimage); 760 | 761 | const NAND_4_challenge_preimage = Buffer.from([0x4]); 762 | const NAND_4_challenge_hash = crypto.hash160(NAND_4_challenge_preimage); 763 | 764 | const NAND_1_hash_lock_script = generate_hash_lock_script( 765 | NAND_1_challenge_hash, 766 | ); 767 | const NAND_2_hash_lock_script = generate_hash_lock_script( 768 | NAND_2_challenge_hash, 769 | ); 770 | const NAND_3_hash_lock_script = generate_hash_lock_script( 771 | NAND_3_challenge_hash, 772 | ); 773 | const NAND_4_hash_lock_script = generate_hash_lock_script( 774 | NAND_4_challenge_hash, 775 | ); 776 | 777 | // combine challenge taproot tree with equivocation taproot tree 778 | let challenge_tree = construct_scripts_taptree([ 779 | NAND_1_hash_lock_script, 780 | NAND_2_hash_lock_script, 781 | NAND_3_hash_lock_script, 782 | NAND_4_hash_lock_script, 783 | A_equivocation_script, 784 | B_equivocation_script, 785 | C_equivocation_script, 786 | D_equivocation_script, 787 | E_equivocation_script, 788 | F_equivocation_script, 789 | G_equivocation_script, 790 | H_equivocation_script, 791 | ]); 792 | 793 | // construct Responses taproot tree 794 | let hash_lock_and_NAND_1_script = `${NAND_1_hash_lock_script} ${NAND_1_script}`; 795 | let hash_lock_and_NAND_2_script = `${NAND_2_hash_lock_script} ${NAND_2_script}`; 796 | let hash_lock_and_NAND_3_script = `${NAND_3_hash_lock_script} ${NAND_3_script}`; 797 | let hash_lock_and_NAND_4_script = `${NAND_4_hash_lock_script} ${NAND_4_script}`; 798 | let response_taptree = construct_scripts_taptree([ 799 | hash_lock_and_NAND_1_script, 800 | hash_lock_and_NAND_2_script, 801 | hash_lock_and_NAND_3_script, 802 | hash_lock_and_NAND_4_script, 803 | ]); 804 | 805 | // construct equivcation taptree 806 | let equivocation_tree = construct_scripts_taptree([ 807 | A_equivocation_script, 808 | B_equivocation_script, 809 | C_equivocation_script, 810 | D_equivocation_script, 811 | E_equivocation_script, 812 | F_equivocation_script, 813 | G_equivocation_script, 814 | H_equivocation_script, 815 | ]); 816 | 817 | // --- challenge and response process with equivocation_happen --- 818 | // A,B,C,D is the inputs for the program and the G is the output for the program 819 | // 1. verifer challenge NAND1 through revealing the `NAND_1_challenge_preimage` 820 | // 2. prover response NAND1 through enter the `NAND_1_challenge_preimage` and `reveal the input and output for NAND1` 821 | // 3. verifer challenge NAND2 through revealing the `NAND_2_challenge_preimage` 822 | // 4. prover response NAND2 through enter the `NAND_2_challenge_preimage` and `reveal the input and output for NAND2` 823 | // >>> equivocation_happen <<< 824 | // 5. verifer challenge NAND4 through revealing the `NAND_4_challenge_preimage` 825 | // 6. prover response NAND4 through enter the `NAND_4_challenge_preimage` and `reveal the input and output for NAND4` 826 | // 7. verifer challenge NAND3 through revealing the `NAND_3_challenge_preimage` 827 | // 8. prover response NAND3 through enter the `NAND_3_challenge_preimage` and `reveal the input and output for NAND3` 828 | 829 | // ======== the first round ======= 830 | // send the challenge NAND1 transaction 831 | const NAND_1_challenge_inputs = [ 832 | Buffer.from([0x01]), // we need to confirm that the stack size equals to 1 after the program execution. 833 | NAND_1_challenge_preimage, 834 | ]; 835 | 836 | // prover can response the challenge by entering the preimage which can be found from the and reveal the input and output for the NAND gate 837 | const NAND1_response_inputs = [ 838 | A1, 839 | Buffer.from([0x01]), // OP_IF solution 840 | B0, 841 | Buffer.from([]), // OP_ELSE solution 842 | E1, 843 | Buffer.from([0x01]), // OP_IF solution 844 | NAND_1_challenge_preimage, // we can get this data from the verifier input_utxo of challenge tx. 845 | ]; 846 | let gate1_p2trs = one_round_challenge_and_response( 847 | NAND_1_hash_lock_script, 848 | challenge_tree, 849 | hash_lock_and_NAND_1_script, 850 | response_taptree, 851 | keypair, 852 | ); 853 | 854 | let gate1 = { 855 | p2tr: gate1_p2trs, 856 | inputs: { 857 | challenge_inputs: NAND_1_challenge_inputs, 858 | response_inputs: NAND1_response_inputs, 859 | }, 860 | }; 861 | 862 | // ======== the second round ======= 863 | let gate2_p2trs = one_round_challenge_and_response( 864 | NAND_2_hash_lock_script, 865 | challenge_tree, 866 | hash_lock_and_NAND_2_script, 867 | response_taptree, 868 | keypair, 869 | ); 870 | let gate2 = { 871 | p2tr: gate2_p2trs, 872 | inputs: { 873 | challenge_inputs: [ 874 | Buffer.from([0x01]), // we need to confirm that the stack size equals to 1 after the program execution. 875 | NAND_2_challenge_preimage, 876 | ], 877 | response_inputs: [ 878 | C1, 879 | Buffer.from([0x01]), // OP_IF solution 880 | D1, 881 | Buffer.from([0x01]), // OP_IF solution 882 | F0, 883 | Buffer.from([]), // OP_ELSE solution 884 | NAND_2_challenge_preimage, // we can get this data from the verifier input_utxo of challenge tx. 885 | ], 886 | }, 887 | }; 888 | 889 | // ======== the third round ======= 890 | let gate4_p2trs = one_round_challenge_and_response( 891 | NAND_4_hash_lock_script, 892 | challenge_tree, 893 | hash_lock_and_NAND_4_script, 894 | response_taptree, 895 | keypair, 896 | ); 897 | let gate4 = { 898 | p2tr: gate4_p2trs, 899 | inputs: { 900 | challenge_inputs: [ 901 | Buffer.from([0x01]), // we need to confirm that the stack size equals to 1 after the program execution. 902 | NAND_4_challenge_preimage, 903 | ], 904 | response_inputs: [ 905 | G0, // Notice: the correct value for G should be 1 but we deliberately changed its value to 0 906 | Buffer.from([]), // OP_ELSE solution 907 | F0, 908 | Buffer.from([]), // OP_ELSE solution 909 | H1, 910 | Buffer.from([0x01]), // OP_IF solution 911 | NAND_4_challenge_preimage, // we can get this data from the verifier input_utxo of challenge tx. 912 | ], 913 | }, 914 | }; 915 | 916 | // ========= the fourth round ========= 917 | let gate3_p2trs = one_round_challenge_and_response( 918 | NAND_3_hash_lock_script, 919 | challenge_tree, 920 | hash_lock_and_NAND_3_script, 921 | response_taptree, 922 | keypair, 923 | ); 924 | 925 | let gate3 = { 926 | p2tr: gate3_p2trs, 927 | inputs: { 928 | challenge_inputs: [ 929 | Buffer.from([0x01]), // we need to confirm that the stack size equals to 1 after the program execution. 930 | NAND_3_challenge_preimage, 931 | ], 932 | response_inputs: [ 933 | E1, // Notice: the correct value for G should be 1 but we deliberately changed its value to 0 934 | Buffer.from([0x01]), // OP_ELSE solution 935 | F0, 936 | Buffer.from([]), // OP_ELSE solution 937 | G1, 938 | Buffer.from([0x01]), // OP_IF solution 939 | NAND_3_challenge_preimage, // we can get this data from the verifier input_utxo of challenge tx. 940 | ], 941 | }, 942 | }; 943 | 944 | // ======== The fifth round ========= 945 | // verifier already known G0 and G1, and then he can unlock the utxo by providing the inputs[G0,G1] and `G_equivocation_script` 946 | let equivocation_happen_p2trs = one_round_challenge_and_response( 947 | G_equivocation_script, 948 | equivocation_tree, 949 | G_equivocation_script, // we do not need to care this param 950 | equivocation_tree, // we do not need to care this param 951 | keypair, 952 | ); 953 | 954 | await send_tx( 955 | "First Round Challenge", 956 | gate1.p2tr.challenge_p2tr, 957 | gate1.p2tr.response_p2tr.address!, 958 | NAND_1_challenge_inputs, 959 | keypair, 960 | ); 961 | await send_tx( 962 | "First Round Response", 963 | gate1.p2tr.response_p2tr, 964 | gate2.p2tr.challenge_p2tr.address!, 965 | NAND1_response_inputs, 966 | keypair, 967 | ); 968 | 969 | await send_tx( 970 | "Second Round Challenge", 971 | gate2.p2tr.challenge_p2tr, 972 | gate2.p2tr.response_p2tr.address!, 973 | gate2.inputs.challenge_inputs, 974 | keypair, 975 | ); 976 | await send_tx( 977 | "Second Round Response", 978 | gate2.p2tr.response_p2tr, 979 | gate4.p2tr.challenge_p2tr.address!, 980 | gate2.inputs.response_inputs, 981 | keypair, 982 | ); 983 | 984 | await send_tx( 985 | "Third Round Challenge", 986 | gate4.p2tr.challenge_p2tr, 987 | gate4.p2tr.response_p2tr.address!, 988 | gate4.inputs.challenge_inputs, 989 | keypair, 990 | ); 991 | await send_tx( 992 | "Third Round Response", 993 | gate4.p2tr.response_p2tr, 994 | gate3.p2tr.challenge_p2tr.address!, 995 | gate4.inputs.response_inputs, 996 | keypair, 997 | ); 998 | 999 | await send_tx( 1000 | "Fourth Round Challenge", 1001 | gate3.p2tr.challenge_p2tr, 1002 | gate3.p2tr.response_p2tr.address!, 1003 | gate3.inputs.challenge_inputs, 1004 | keypair, 1005 | ); 1006 | await send_tx( 1007 | "Fourth Round Response", 1008 | gate3.p2tr.response_p2tr, 1009 | equivocation_happen_p2trs.challenge_p2tr.address!, 1010 | gate3.inputs.response_inputs, 1011 | keypair, 1012 | ); 1013 | 1014 | // verifier reveal the equivocation abount G0 and G1 to unlock the UTXO 1015 | await send_tx( 1016 | "Fifth Round Challenge", 1017 | equivocation_happen_p2trs.challenge_p2tr, 1018 | `mscxdTxVSoR8VyRkZEGJ4dxECJXcXfQqVz`, 1019 | [G0, G1], 1020 | keypair, 1021 | ); 1022 | } 1023 | -------------------------------------------------------------------------------- /src/blockstream_utils.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse } from "axios"; 2 | 3 | const blockstream = new axios.Axios({ 4 | baseURL: `https://blockstream.info/testnet/api`, 5 | }); 6 | 7 | export async function waitUntilUTXO(address: string, minValue?: number) { 8 | return new Promise((resolve, reject) => { 9 | let intervalId: any; 10 | const checkForUtxo = async () => { 11 | try { 12 | const response: AxiosResponse = await blockstream.get( 13 | `/address/${address}/utxo`, 14 | ); 15 | const data: IUTXO[] = response.data 16 | ? JSON.parse(response.data) 17 | : undefined; 18 | console.log(`unspent utxos:`, data); 19 | if (data.length > 0) { 20 | if (minValue !== undefined) { 21 | const utxo = data.find((utxo) => utxo.value >= minValue); 22 | if (utxo) { 23 | resolve([utxo]); 24 | clearInterval(intervalId); 25 | } 26 | } else { 27 | resolve(data); 28 | clearInterval(intervalId); 29 | } 30 | } 31 | } catch (error) { 32 | reject(error); 33 | clearInterval(intervalId); 34 | } 35 | }; 36 | intervalId = setInterval(checkForUtxo, 10000); 37 | }); 38 | } 39 | 40 | export async function broadcast(txHex: string) { 41 | const response: AxiosResponse = await blockstream.post("/tx", txHex); 42 | return response.data; 43 | } 44 | 45 | interface IUTXO { 46 | txid: string; 47 | vout: number; 48 | status: { 49 | confirmed: boolean; 50 | block_height: number; 51 | block_hash: string; 52 | block_time: number; 53 | }; 54 | value: number; 55 | } 56 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | initEccLib, 3 | networks, 4 | script, 5 | Signer, 6 | payments, 7 | crypto, 8 | Psbt, 9 | } from "bitcoinjs-lib"; 10 | import { savm_bit_commitment_tx } from "./savm"; 11 | import { broadcast, waitUntilUTXO } from "./blockstream_utils"; 12 | import { ECPairFactory, ECPairAPI, TinySecp256k1Interface } from "ecpair"; 13 | import { Taptree, Tapleaf } from "bitcoinjs-lib/src/types"; 14 | import { witnessStackToScriptWitness } from "./witness_stack_to_script_witness"; 15 | 16 | const tinysecp: TinySecp256k1Interface = require("tiny-secp256k1"); 17 | initEccLib(tinysecp as any); 18 | const ECPair: ECPairAPI = ECPairFactory(tinysecp); 19 | const network = networks.testnet; 20 | 21 | import * as dotenv from "dotenv"; 22 | dotenv.config(); 23 | const prikey = process.env.PRIVATE_KEY!; 24 | console.log(prikey); 25 | 26 | import { 27 | bitvm_NAND_gate, 28 | bitvm_bitvalue_commitment, 29 | bitvm_bitvalue_commitment_with_sig, 30 | process_trace_with_equivocation, 31 | process_trace, 32 | } from "./bitvm"; 33 | 34 | import { toXOnly } from "./utils"; 35 | import { exit } from "process"; 36 | 37 | async function start(args: string[]) { 38 | const keypair = ECPair.fromPrivateKey(Buffer.from(prikey, "hex"), { 39 | network, 40 | }); 41 | 42 | switch (args[0]) { 43 | case "start_taptree": 44 | await start_taptree(keypair); 45 | break; 46 | case "start_taptree_p2pk_script": 47 | await start_taptree_p2pk_script(keypair); 48 | break; 49 | case "start_taptree_hash_lock_script": 50 | await start_taptree_hash_lock_script(keypair); 51 | break; 52 | case "start_p2pktr": 53 | await start_p2pktr(keypair); 54 | break; 55 | case "process_trace_with_equivocation": 56 | await process_trace_with_equivocation(keypair); 57 | break; 58 | case "process_trace": 59 | await process_trace(keypair); 60 | break; 61 | case "bitvm_NAND_gate": 62 | await bitvm_NAND_gate(keypair); 63 | case "bitvm_bitvalue_commitment": 64 | await bitvm_bitvalue_commitment(keypair); 65 | case "bitvm_bitvalue_commitment_with_sig": 66 | await bitvm_bitvalue_commitment_with_sig(keypair); 67 | case "savm_bit_commitment_tx": 68 | await savm_bit_commitment_tx(keypair); 69 | default: 70 | console.log( 71 | "No function is executed. Please specify a function to run (A, B, C, or D).", 72 | ); 73 | } 74 | } 75 | 76 | const args = process.argv.slice(2); 77 | start(args).then(() => exit()); 78 | 79 | async function start_p2pktr(keypair: Signer) { 80 | console.log(`Running "Pay to Pubkey with taproot example"`); 81 | // Tweak the original keypair 82 | const tweakedSigner = tweakSigner(keypair, { network }); 83 | // Generate an address from the tweaked public key 84 | const p2pktr = payments.p2tr({ 85 | pubkey: toXOnly(tweakedSigner.publicKey), 86 | network, 87 | }); 88 | const p2pktr_addr = p2pktr.address ?? ""; 89 | console.log(`Waiting till UTXO is detected at this Address: ${p2pktr_addr}`); 90 | 91 | const utxos = await waitUntilUTXO(p2pktr_addr); 92 | console.log(`Using UTXO ${utxos[0].txid}:${utxos[0].vout}`); 93 | 94 | const psbt = new Psbt({ network }); 95 | psbt.addInput({ 96 | hash: utxos[0].txid, 97 | index: utxos[0].vout, 98 | witnessUtxo: { value: utxos[0].value, script: p2pktr.output! }, 99 | tapInternalKey: toXOnly(keypair.publicKey), 100 | }); 101 | 102 | psbt.addOutput({ 103 | address: "mohjSavDdQYHRYXcS3uS6ttaHP8amyvX78", // faucet address 104 | value: utxos[0].value - 150, 105 | }); 106 | 107 | psbt.signInput(0, tweakedSigner); 108 | 109 | psbt.finalizeAllInputs(); 110 | 111 | const tx = psbt.extractTransaction(); 112 | console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); 113 | const txid = await broadcast(tx.toHex()); 114 | console.log(`Success! Txid is ${txid}`); 115 | } 116 | 117 | async function start_taptree_p2pk_script(keypair: Signer) { 118 | // TapTree example 119 | console.log(`Running "Taptree example"`); 120 | 121 | // Create a tap tree with two spend paths 122 | // One path should allow spending using secret 123 | // The other path should pay to another pubkey 124 | 125 | // Make random key for hash_lock 126 | const hash_lock_keypair = ECPair.makeRandom({ network }); 127 | 128 | const secret_bytes = Buffer.from("SECRET"); 129 | const hash = crypto.hash160(secret_bytes); 130 | // Construct script to pay to hash_lock_keypair if the correct preimage/secret is provided 131 | const hash_script_asm = `OP_HASH160 ${hash.toString( 132 | "hex", 133 | )} OP_EQUALVERIFY ${toXOnly(hash_lock_keypair.publicKey).toString( 134 | "hex", 135 | )} OP_CHECKSIG`; 136 | const hash_lock_script = script.fromASM(hash_script_asm); 137 | 138 | const p2pk_script_asm = `${toXOnly(keypair.publicKey).toString( 139 | "hex", 140 | )} OP_CHECKSIG`; 141 | const p2pk_script = script.fromASM(p2pk_script_asm); 142 | 143 | const scriptTree: Taptree = [ 144 | { 145 | output: hash_lock_script, 146 | }, 147 | { 148 | output: p2pk_script, 149 | }, 150 | ]; 151 | 152 | const hash_lock_redeem = { 153 | output: hash_lock_script, 154 | redeemVersion: 192, 155 | }; 156 | const p2pk_redeem = { 157 | output: p2pk_script, 158 | redeemVersion: 192, 159 | }; 160 | 161 | // three p2tr 162 | const script_p2tr = payments.p2tr({ 163 | internalPubkey: toXOnly(keypair.publicKey), 164 | scriptTree, 165 | network, 166 | }); 167 | const script_addr = script_p2tr.address ?? ""; 168 | 169 | const p2pk_p2tr = payments.p2tr({ 170 | internalPubkey: toXOnly(keypair.publicKey), 171 | scriptTree, 172 | redeem: p2pk_redeem, 173 | network, 174 | }); 175 | 176 | console.log( 177 | `p2pk_redeem.output (p2pk_script):${p2pk_redeem.output.toString("hex")}`, 178 | ); 179 | console.log(`witnessUtxo: ${p2pk_p2tr.output!.toString("hex")}`); 180 | 181 | console.log(`Waiting till UTXO is detected at this Address: ${script_addr}`); 182 | let utxos = await waitUntilUTXO(script_addr); 183 | console.log( 184 | `Trying the P2PK path with UTXO ${utxos[0].txid}:${utxos[0].vout}`, 185 | ); 186 | //// 接下来的程序会构造三种 PSBT来 花费这个 script_addr 对应的UTXO 187 | const p2pk_psbt = new Psbt({ network }); 188 | // input 是真正执行程序的地方 189 | p2pk_psbt.addInput({ 190 | hash: utxos[0].txid, 191 | index: utxos[0].vout, 192 | witnessUtxo: { value: utxos[0].value, script: p2pk_p2tr.output! }, 193 | tapLeafScript: [ 194 | { 195 | leafVersion: p2pk_redeem.redeemVersion, 196 | script: p2pk_redeem.output, // =p2pk_script 197 | controlBlock: p2pk_p2tr.witness![p2pk_p2tr.witness!.length - 1], // question: controlBlock how to work? 198 | }, 199 | ], 200 | }); 201 | 202 | p2pk_psbt.addOutput({ 203 | address: "mscxdTxVSoR8VyRkZEGJ4dxECJXcXfQqVz", // account1 address 204 | value: utxos[0].value - 150, 205 | }); 206 | 207 | p2pk_psbt.signInput(0, keypair); 208 | p2pk_psbt.finalizeAllInputs(); 209 | 210 | let tx = p2pk_psbt.extractTransaction(); 211 | console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); 212 | let txid = await broadcast(tx.toHex()); 213 | console.log(`Success! Txid is ${txid}`); 214 | } 215 | 216 | async function start_taptree_hash_lock_script(keypair: Signer) { 217 | // TapTree example 218 | console.log(`Running "Taptree example"`); 219 | 220 | // Create a tap tree with two spend paths 221 | // One path should allow spending using secret 222 | // The other path should pay to another pubkey 223 | 224 | // Make random key for hash_lock 225 | const hash_lock_keypair = ECPair.makeRandom({ network }); 226 | 227 | const secret_bytes = Buffer.from("SECRET"); 228 | const hash = crypto.hash160(secret_bytes); 229 | // Construct script to pay to hash_lock_keypair if the correct preimage/secret is provided 230 | const hash_script_asm = `OP_HASH160 ${hash.toString( 231 | "hex", 232 | )} OP_EQUALVERIFY ${toXOnly(hash_lock_keypair.publicKey).toString( 233 | "hex", 234 | )} OP_CHECKSIG`; 235 | const hash_lock_script = script.fromASM(hash_script_asm); 236 | 237 | const p2pk_script_asm = `${toXOnly(keypair.publicKey).toString( 238 | "hex", 239 | )} OP_CHECKSIG`; 240 | const p2pk_script = script.fromASM(p2pk_script_asm); 241 | 242 | const scriptTree: Taptree = [ 243 | { 244 | output: hash_lock_script, 245 | }, 246 | { 247 | output: p2pk_script, 248 | }, 249 | ]; 250 | 251 | const hash_lock_redeem = { 252 | output: hash_lock_script, 253 | redeemVersion: 192, 254 | }; 255 | 256 | // three p2tr 257 | const script_p2tr = payments.p2tr({ 258 | internalPubkey: toXOnly(keypair.publicKey), 259 | scriptTree, 260 | network, 261 | }); 262 | const script_addr = script_p2tr.address ?? ""; 263 | 264 | const hash_lock_p2tr = payments.p2tr({ 265 | internalPubkey: toXOnly(keypair.publicKey), 266 | scriptTree, 267 | redeem: hash_lock_redeem, 268 | network, 269 | }); 270 | 271 | console.log( 272 | `hash_lock_redeem.output (p2pk_script):${hash_lock_redeem.output.toString( 273 | "hex", 274 | )}`, 275 | ); 276 | console.log(`witnessUtxo: ${hash_lock_p2tr.output!.toString("hex")}`); 277 | 278 | console.log(`Waiting till UTXO is detected at this Address: ${script_addr}`); 279 | let utxos = await waitUntilUTXO(script_addr); 280 | console.log( 281 | `Trying the Hash lock spend path with UTXO ${utxos[0].txid}:${utxos[0].vout}`, 282 | ); 283 | 284 | const tapLeafScript = { 285 | leafVersion: hash_lock_redeem.redeemVersion, 286 | script: hash_lock_redeem.output, 287 | controlBlock: hash_lock_p2tr.witness![hash_lock_p2tr.witness!.length - 1], 288 | }; 289 | 290 | const psbt = new Psbt({ network }); 291 | psbt.addInput({ 292 | hash: utxos[0].txid, 293 | index: utxos[0].vout, 294 | witnessUtxo: { value: utxos[0].value, script: hash_lock_p2tr.output! }, 295 | tapLeafScript: [tapLeafScript], 296 | }); 297 | 298 | psbt.addOutput({ 299 | address: "mscxdTxVSoR8VyRkZEGJ4dxECJXcXfQqVz", // acount1 address 300 | value: utxos[0].value - 150, 301 | }); 302 | 303 | psbt.signInput(0, hash_lock_keypair); 304 | 305 | // We have to construct our witness script in a custom finalizer 306 | 307 | const customFinalizer = (_inputIndex: number, input: any) => { 308 | const scriptSolution = [input.tapScriptSig[0].signature, secret_bytes]; 309 | const witness = scriptSolution 310 | .concat(tapLeafScript.script) 311 | .concat(tapLeafScript.controlBlock); 312 | 313 | return { 314 | finalScriptWitness: witnessStackToScriptWitness(witness), 315 | }; 316 | }; 317 | 318 | psbt.finalizeInput(0, customFinalizer); 319 | 320 | let tx = psbt.extractTransaction(); 321 | console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); 322 | let txid = await broadcast(tx.toHex()); 323 | console.log(`Success! Txid is ${txid}`); 324 | } 325 | 326 | async function start_taptree(keypair: Signer) { 327 | // TapTree example 328 | console.log(`Running "Taptree example"`); 329 | 330 | // Create a tap tree with two spend paths 331 | // One path should allow spending using secret 332 | // The other path should pay to another pubkey 333 | 334 | // Make random key for hash_lock 335 | const hash_lock_keypair = ECPair.makeRandom({ network }); 336 | 337 | const secret_bytes = Buffer.from("SECRET"); 338 | const hash = crypto.hash160(secret_bytes); 339 | // Construct script to pay to hash_lock_keypair if the correct preimage/secret is provided 340 | const hash_script_asm = `OP_HASH160 ${hash.toString( 341 | "hex", 342 | )} OP_EQUALVERIFY ${toXOnly(hash_lock_keypair.publicKey).toString( 343 | "hex", 344 | )} OP_CHECKSIG`; 345 | const hash_lock_script = script.fromASM(hash_script_asm); 346 | 347 | const p2pk_script_asm = `${toXOnly(keypair.publicKey).toString( 348 | "hex", 349 | )} OP_CHECKSIG`; 350 | const p2pk_script = script.fromASM(p2pk_script_asm); 351 | 352 | const scriptTree: Taptree = [ 353 | { 354 | output: hash_lock_script, 355 | }, 356 | { 357 | output: p2pk_script, 358 | }, 359 | ]; 360 | // three p2tr 361 | const script_p2tr = payments.p2tr({ 362 | internalPubkey: toXOnly(keypair.publicKey), 363 | scriptTree, 364 | network, 365 | }); 366 | const script_addr = script_p2tr.address ?? ""; 367 | // We can also spend from this address without using the script tree 368 | 369 | console.log(`Waiting till UTXO is detected at this Address: ${script_addr}`); 370 | let utxos = await waitUntilUTXO(script_addr); 371 | console.log( 372 | `Trying the Hash lock spend path with UTXO ${utxos[0].txid}:${utxos[0].vout}`, 373 | ); 374 | 375 | const key_spend_psbt = new Psbt({ network }); 376 | key_spend_psbt.addInput({ 377 | hash: utxos[0].txid, 378 | index: utxos[0].vout, 379 | witnessUtxo: { value: utxos[0].value, script: script_p2tr.output! }, 380 | tapInternalKey: toXOnly(keypair.publicKey), 381 | tapMerkleRoot: script_p2tr.hash, 382 | }); 383 | key_spend_psbt.addOutput({ 384 | address: "mohjSavDdQYHRYXcS3uS6ttaHP8amyvX78", // account1 address 385 | value: utxos[0].value - 150, 386 | }); 387 | // We need to create a signer tweaked by script tree's merkle root 388 | const tweakedSigner = tweakSigner(keypair, { tweakHash: script_p2tr.hash }); 389 | key_spend_psbt.signInput(0, tweakedSigner); 390 | key_spend_psbt.finalizeAllInputs(); 391 | 392 | let tx = key_spend_psbt.extractTransaction(); 393 | console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); 394 | let txid = await broadcast(tx.toHex()); 395 | console.log(`Success! Txid is ${txid}`); 396 | } 397 | 398 | function tweakSigner(signer: Signer, opts: any = {}): Signer { 399 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 400 | // @ts-ignore 401 | let privateKey: Uint8Array | undefined = signer.privateKey!; 402 | if (!privateKey) { 403 | throw new Error("Private key is required for tweaking signer!"); 404 | } 405 | if (signer.publicKey[0] === 3) { 406 | privateKey = tinysecp.privateNegate(privateKey); 407 | } 408 | 409 | const tweakedPrivateKey = tinysecp.privateAdd( 410 | privateKey, 411 | tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash), 412 | ); 413 | if (!tweakedPrivateKey) { 414 | throw new Error("Invalid tweaked private key!"); 415 | } 416 | 417 | return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { 418 | network: opts.network, 419 | }); 420 | } 421 | 422 | function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 423 | return crypto.taggedHash( 424 | "TapTweak", 425 | Buffer.concat(h ? [pubKey, h] : [pubKey]), 426 | ); 427 | } 428 | -------------------------------------------------------------------------------- /src/savm.ts: -------------------------------------------------------------------------------- 1 | import { 2 | savm_generate_specific_gate_fail_script, 3 | // savm_gates_script, 4 | savm_generate_all_bit_commitment_scripts_inputs, 5 | savm_generate_all_gates_bit_commitment_scripts, 6 | generate_relate_time_lock_script, 7 | construct_scripts_taptree, 8 | send_tx, 9 | } from "./script_help"; 10 | import { getRandomNumbers } from "./utils"; 11 | import { 12 | initEccLib, 13 | networks, 14 | script, 15 | Signer, 16 | payments, 17 | crypto, 18 | Psbt, 19 | } from "bitcoinjs-lib"; 20 | import { TinySecp256k1Interface } from "ecpair"; 21 | import { toXOnly } from "./utils"; 22 | import { assert } from "console"; 23 | const tinysecp: TinySecp256k1Interface = require("tiny-secp256k1"); 24 | initEccLib(tinysecp as any); 25 | const network = networks.testnet; 26 | 27 | export async function savm_bit_commitment_tx(keypair: Signer) { 28 | let premiages0 = getRandomNumbers(8); 29 | let premiages1 = getRandomNumbers(8); 30 | assert(premiages0.length == 8); 31 | assert(premiages1.length == 8); 32 | let preimage_hashs0: Buffer[] = []; 33 | let preimage_hashs1: Buffer[] = []; 34 | premiages0.forEach((value, index, array) => { 35 | preimage_hashs0.push(crypto.hash160(value)); 36 | console.log(`premiages0:${premiages0[index].toString("hex")}`); 37 | console.log(`preimage_hashs0:${preimage_hashs0[index].toString("hex")}`); 38 | }); 39 | premiages1.forEach((value, index, array) => { 40 | preimage_hashs1.push(crypto.hash160(value)); 41 | console.log(`premiages1:${premiages1[index].toString("hex")}`); 42 | console.log(`preimage_hashs1:${preimage_hashs1[index].toString("hex")}`); 43 | }); 44 | 45 | // construct all bit value commitment 46 | let bit_commitment_script = savm_generate_all_gates_bit_commitment_scripts( 47 | preimage_hashs0, 48 | preimage_hashs1, 49 | ); 50 | // confirm stack size equal to 1 51 | bit_commitment_script = `${bit_commitment_script} OP_1`; 52 | // verifeir can refunds after waiting 10 blocks 53 | let verifier_refund_time_lock_script = generate_relate_time_lock_script(10); 54 | console.log(`time_lock_script:${verifier_refund_time_lock_script}`); 55 | console.log(`bit_commitment_script:${bit_commitment_script}`); 56 | let bit_commitment_taptree = construct_scripts_taptree([ 57 | bit_commitment_script, 58 | verifier_refund_time_lock_script, 59 | ]); 60 | 61 | const bit_commitment_script_redeem = { 62 | output: script.fromASM(bit_commitment_script), 63 | redeemVersion: 192, 64 | }; 65 | 66 | const bit_commitment_script_p2tr = payments.p2tr({ 67 | internalPubkey: toXOnly(keypair.publicKey), 68 | scriptTree: bit_commitment_taptree, 69 | redeem: bit_commitment_script_redeem, 70 | network, 71 | }); 72 | // A = 1, B=0, C=1, D=1, E=1, F=0, G=1, H=1 73 | let prover_inputs = savm_generate_all_bit_commitment_scripts_inputs( 74 | premiages0, 75 | premiages1, 76 | [1, 0, 1, 1, 1, 0, 1, 1], 77 | ); 78 | 79 | // construct transaction 80 | await send_tx( 81 | "bit_commitment_script redeem", 82 | bit_commitment_script_p2tr, 83 | "mscxdTxVSoR8VyRkZEGJ4dxECJXcXfQqVz", 84 | prover_inputs, 85 | keypair, 86 | ); 87 | 88 | // A NAND B = E 89 | let gate1_script = savm_generate_specific_gate_fail_script( 90 | preimage_hashs0, 91 | preimage_hashs1, 92 | 0, 93 | 1, 94 | 4, 95 | ); 96 | // C NAND D = F 97 | let gate2_script = savm_generate_specific_gate_fail_script( 98 | preimage_hashs0, 99 | preimage_hashs1, 100 | 2, 101 | 3, 102 | 5, 103 | ); 104 | // E NAND F = G 105 | let gate3_script = savm_generate_specific_gate_fail_script( 106 | preimage_hashs0, 107 | preimage_hashs1, 108 | 4, 109 | 5, 110 | 6, 111 | ); 112 | // F NAND G = H 113 | let gate4_script = savm_generate_specific_gate_fail_script( 114 | preimage_hashs0, 115 | preimage_hashs1, 116 | 5, 117 | 6, 118 | 7, 119 | ); 120 | // time lock script 121 | // prover can refund after waiting 20 blocks 122 | let prover_refund_time_lock_script = generate_relate_time_lock_script(20); 123 | 124 | let gates_fail_scripts_tree = construct_scripts_taptree([ 125 | gate1_script, 126 | gate2_script, 127 | gate3_script, 128 | gate4_script, 129 | prover_refund_time_lock_script, 130 | ]); 131 | const gates_fail_scripts_redeem = { 132 | output: script.fromASM(gate1_script), 133 | redeemVersion: 192, 134 | }; 135 | 136 | const gates_fail_scripts_p2tr = payments.p2tr({ 137 | internalPubkey: toXOnly(keypair.publicKey), 138 | scriptTree: gates_fail_scripts_tree, 139 | redeem: gates_fail_scripts_redeem, 140 | network, 141 | }); 142 | // A NAND B = E ; 1 NAND 0 = 0 (the correct version is 1 NAND 0 = 1) 143 | let verifier_inputs: Buffer[] = [ 144 | premiages1[0], // A=1 145 | Buffer.from([0x01]), // OP_IF 146 | premiages0[1], // B=0 147 | Buffer.from([]), //OP_ELSE 148 | premiages0[4], // C=0 149 | Buffer.from([]), //OP_ELSE 150 | ]; 151 | 152 | await send_tx( 153 | "verifier_check", 154 | gates_fail_scripts_p2tr, 155 | "mscxdTxVSoR8VyRkZEGJ4dxECJXcXfQqVz", 156 | verifier_inputs, 157 | keypair, 158 | ); 159 | } 160 | -------------------------------------------------------------------------------- /src/script_help.ts: -------------------------------------------------------------------------------- 1 | import { 2 | initEccLib, 3 | networks, 4 | script, 5 | Signer, 6 | payments, 7 | Psbt, 8 | } from "bitcoinjs-lib"; 9 | import { broadcast, waitUntilUTXO } from "./blockstream_utils"; 10 | import { TinySecp256k1Interface } from "ecpair"; 11 | import { Taptree, Tapleaf } from "bitcoinjs-lib/src/types"; 12 | import { witnessStackToScriptWitness } from "./witness_stack_to_script_witness"; 13 | import { toXOnly } from "./utils"; 14 | import { assert } from "console"; 15 | const tinysecp: TinySecp256k1Interface = require("tiny-secp256k1"); 16 | initEccLib(tinysecp as any); 17 | const network = networks.testnet; 18 | 19 | // Notice & Todo: If a leaf of a tree has been revealed in a previous round, it cannot be used as a leaf of the challenge taproot tree to 20 | // prevent Prover from deliberately reusing the leaf to unlock the Response Taproot Tree. 21 | export function construct_challenge_taptree( 22 | gate_1_hash_lock_script: string, 23 | gate_2_hash_lock_script: string, 24 | gate_3_hash_lock_script: string, 25 | ) { 26 | const parity_tree: Taptree = [ 27 | { 28 | output: script.fromASM(gate_1_hash_lock_script), 29 | }, 30 | { 31 | output: script.fromASM(gate_2_hash_lock_script), 32 | }, 33 | ]; 34 | 35 | const challenge_taproot_tree: Taptree = [ 36 | parity_tree, 37 | { output: script.fromASM(gate_3_hash_lock_script) }, 38 | ]; 39 | return challenge_taproot_tree; 40 | } 41 | 42 | export function construct_response_taptree( 43 | script1: string, 44 | script2: string, 45 | script3: string, 46 | ) { 47 | const response_tree: Taptree = [ 48 | { 49 | output: script.fromASM(script1), 50 | }, 51 | { 52 | output: script.fromASM(script2), 53 | }, 54 | ]; 55 | 56 | const response_taproot_tree: Taptree = [ 57 | response_tree, 58 | { output: script.fromASM(script3) }, 59 | ]; 60 | return response_taproot_tree; 61 | } 62 | 63 | export function construct_scripts_taptree(script_list: string[]): Taptree { 64 | let leaves = convert_script_to_tapnode(script_list); 65 | let next_layer_nodes: Taptree[] = leaves; 66 | while (true) { 67 | next_layer_nodes = construct_taptree_one_layer(next_layer_nodes); 68 | if (next_layer_nodes.length == 1) { 69 | break; 70 | } 71 | } 72 | return next_layer_nodes[0]; 73 | } 74 | 75 | export function convert_script_to_tapnode(script_list: string[]): Taptree[] { 76 | const leaves = script_list.map((value, index, array) => { 77 | let leaf: Taptree = { output: script.fromASM(value) }; 78 | return leaf; 79 | }); 80 | return leaves; 81 | } 82 | 83 | export function construct_taptree_one_layer(script_list: Taptree[]): Taptree[] { 84 | const next_layer_nodes: Taptree[] = []; 85 | const leaves = script_list.forEach((value, index, array) => { 86 | // Notice: the program no consider the specific case that array length equals to 1, 87 | // which maybe leading to loop forever when constructing taptree 88 | if (index + 1 == array.length && index % 2 == 0) { 89 | next_layer_nodes.push(value); 90 | return value; 91 | } 92 | if (index % 2 != 0) { 93 | const next_layer_node: Taptree = [value, array[index - 1]]; 94 | next_layer_nodes.push(next_layer_node); 95 | } 96 | return value; 97 | }); 98 | return next_layer_nodes; 99 | } 100 | // https://github.com/tianmingyun/MasterBitcoin2CN/blob/master/ch07.md 101 | // 如果furture_block_number/nlocktime不为零,低于5亿,则将其解释为区块高度,这意味着交易在指定的区块高度之前无效,并且不被传播,也不被包含在区块链中。如果大于或等于5亿,它被解释为Unix纪元时间戳(自1-1-1970之后的秒数),并且交易在指定时间之前无效 102 | // 更确切地说,如果出现以下任一情况,CHECKLOCKTIMEVERIFY失败并停止执行,标记交易无效(来自:BIP-65): 103 | // 104 | // 105 | // 106 | // CLTV不替换nLocktime,而是限制特定的UTXO,使它们只能在大于或等于nLocktime设置的值的将来交易中使用。 107 | // CLTV操作码采用一个参数作为输入,为与nLocktime相同格式的数字(区块高度或Unix纪元时间) 108 | // 109 | // 堆栈是空的 110 | // 堆栈中的顶部项小于0 111 | // 顶层堆栈项和nLocktime字段的锁定时间类型(高度或者时间戳)不相同 112 | // 顶层堆栈项大于交易的nLocktime字段 113 | // 输入的nSequence字段为0xffffffff 114 | // 注释 CLTV和nLocktime描述时间锁必须使用相同的格式,无论是区块高度还是自Unix纪元以来经过的秒数。 最重要的是,在一起使用时,nLocktime的格式必须与输出中的CLTV格式相匹配,它们必须都是区块高度或都是秒数时间。 115 | export function generate_absoluate_time_lock_script( 116 | furture_block_number: number, 117 | ): string { 118 | const script = `${Buffer.from([furture_block_number]).toString( 119 | "hex", 120 | )} OP_NOP2 OP_DROP`; 121 | return script; 122 | } 123 | // nSequence相对时间锁 124 | // 从编程角度,如果没有设置最高位(1<<31位 )为1,意味着它是一个表示“相对锁定时间”的标志. 125 | // 126 | // 相对时间锁可以设置在每个交易输入中,方法是设置每个输入中的nSequence字段。 127 | // 交易的输入中的nSequence值小于231,就表示具有相对时间锁。这种交易中的输入只有相对锁定时间到期后才能有效。 128 | // 例如,一笔交易的输入的nSequence相对时间锁是30个区块,那么只有当输入引用的UTXO被挖出后再经过30个区块之后,该交易才有效。 129 | // 由于nSequence是每个输入中的字段,因此交易可能包含任何数量的时间锁定输入,这其中的每个输入都必须满足时间限制交易才能有效。交易中的输入可以是时间锁定输入(nSequence <231),也可以是没有相对时间锁定(nSequence> = 231)的输入。 130 | // 131 | // nSequence值以块或秒为单位,但与nLocktime中使用的格式略有不同。类型(type)标志用于区分计数块和计数时间(以秒为单位)。 132 | // 类型标志设置在第23个最低有效位(即值1 << 22)。如果设置了类型标志,则nSequence值将被解释为512秒的倍数。如果未设置类型标志,则nSequence值被解释为区块数。 133 | // 当将nSequence解释为相对时间锁时,只考虑16个最低有效位。一旦对标志(位32和23)求值,nSequence值通常用16位掩码(例如nSequence或者0x0000FFFF)进行“屏蔽”。 134 | export function generate_relate_time_lock_script( 135 | wait_block_num: number, 136 | ): string { 137 | const script = `${Buffer.from([wait_block_num]).toString( 138 | "hex", 139 | )} OP_NOP OP_DROP`; 140 | return script; 141 | } 142 | 143 | export function generate_hash_lock_script(hash: Buffer): string { 144 | const script = `OP_HASH160 ${hash.toString("hex")} OP_EQUALVERIFY`; 145 | return script; 146 | } 147 | 148 | export function generate_equivocation_script( 149 | hash0: Buffer, 150 | hash1: Buffer, 151 | ): string { 152 | // the verifier can unlock the utxo when he has the two preimage for hash1 and hash0 153 | // the input is [preimage0, preimage1] 154 | const bitvalue_script_asm = `OP_HASH160 ${hash1.toString( 155 | "hex", 156 | )} OP_EQUALVERIFY OP_HASH160 ${hash0.toString("hex")} OP_EQUALVERIFY OP_1`; 157 | return bitvalue_script_asm; 158 | } 159 | 160 | export function generate_bitcommitment_script( 161 | hash0: Buffer, 162 | hash1: Buffer, 163 | ): string { 164 | const bitvalue_script_asm = `OP_IF OP_HASH160 ${hash1.toString( 165 | "hex", 166 | )} OP_EQUALVERIFY OP_1 OP_ELSE OP_HASH160 ${hash0.toString( 167 | "hex", 168 | )} OP_EQUALVERIFY OP_0 OP_ENDIF`; 169 | return bitvalue_script_asm; 170 | } 171 | 172 | // export function savm_generate_bitcommitment_script( 173 | // hash0: Buffer, 174 | // hash1: Buffer, 175 | // ): string { 176 | // const bitvalue_script_asm = `OP_IF OP_HASH160 ${hash1.toString( 177 | // "hex", 178 | // )} OP_EQUALVERIFY OP_ELSE OP_HASH160 ${hash0.toString( 179 | // "hex", 180 | // )} OP_EQUALVERIFY OP_ENDIF`; 181 | // return bitvalue_script_asm; 182 | // } 183 | 184 | export function savm_generate_bitcommitment_script( 185 | hash0: Buffer, 186 | hash1: Buffer, 187 | ): string { 188 | const bitvalue_script_asm = `OP_DUP OP_HASH160 ${hash1.toString( 189 | "hex", 190 | )} OP_EQUAL OP_SWAP OP_HASH160 ${hash0.toString( 191 | "hex", 192 | )} OP_EQUAL OP_ADD OP_1 OP_EQUALVERIFY`; 193 | return bitvalue_script_asm; 194 | } 195 | 196 | export function savm_generate_all_bit_commitment_scripts_inputs( 197 | premiages0_list: Buffer[], 198 | preimages1_list: Buffer[], 199 | gates_input_value: number[], 200 | ) { 201 | assert(premiages0_list.length == preimages1_list.length); 202 | assert(premiages0_list.length == gates_input_value.length); 203 | let inputs: Buffer[] = []; 204 | premiages0_list.forEach((value, index, array) => { 205 | if (gates_input_value[index] == 1) { 206 | // inputs.push(Buffer.from([0x1])); 207 | inputs.push(preimages1_list[index]); 208 | } else if (gates_input_value[index] == 0) { 209 | // inputs.push(Buffer.from([])); 210 | inputs.push(premiages0_list[index]); 211 | } 212 | }); 213 | inputs = inputs.reverse(); 214 | return inputs; 215 | } 216 | 217 | export function savm_generate_all_gates_bit_commitment_scripts( 218 | hash0_list: Buffer[], 219 | hash1_list: Buffer[], 220 | ): string { 221 | assert(hash0_list.length == hash1_list.length); 222 | let complete_script = ""; 223 | hash0_list.forEach((value, index, array) => { 224 | let bs = savm_generate_bitcommitment_script( 225 | hash0_list[index], 226 | hash1_list[index], 227 | ); 228 | if (index != 0) { 229 | complete_script = `${complete_script} ${bs}`; 230 | } else { 231 | complete_script = bs; 232 | } 233 | }); 234 | 235 | return complete_script; 236 | // the inputs for this savm script 237 | // [gate1_hash0, gate1_hash1 ... gaten_hash0,gaten_hash1] 238 | } 239 | 240 | export function savm_generate_specific_gate_fail_script( 241 | preimage_hashs0: Buffer[], 242 | preimage_hashs1: Buffer[], 243 | left_operator_index: number, 244 | right_operator_index: number, 245 | result_index: number, 246 | ): string { 247 | assert(preimage_hashs0.length == preimage_hashs1.length); 248 | assert(left_operator_index < preimage_hashs0.length); 249 | assert(right_operator_index < preimage_hashs0.length); 250 | assert(result_index < preimage_hashs0.length); 251 | return generate_NAND_Fail_gate_script( 252 | preimage_hashs0[left_operator_index], 253 | preimage_hashs1[left_operator_index], 254 | preimage_hashs0[right_operator_index], 255 | preimage_hashs0[right_operator_index], 256 | preimage_hashs0[result_index], 257 | preimage_hashs1[result_index], 258 | ); 259 | } 260 | 261 | // NAND Fail case: 262 | // 1 NAND 0 = 0 263 | // 1 NAND 1 = 1 264 | // 0 NAND 1 = 0 265 | // 0 NAND 0 = 0 266 | // NAND Success case: 267 | // 1 NAND 1 = 0 268 | // 0 NAND 1 = 1 269 | // 0 NAND 0 = 1 270 | // 1 NAND 0 = 1 271 | export function generate_NAND_Fail_gate_script( 272 | left_operator_0_hash: Buffer, 273 | left_operator_1_hash: Buffer, 274 | right_operator_0_hash: Buffer, 275 | right_operator_1_hash: Buffer, 276 | result_0_hash: Buffer, 277 | result_1_hash: Buffer, 278 | ): string { 279 | const C_bitvalue_script = generate_bitcommitment_script( 280 | result_0_hash, 281 | result_1_hash, 282 | ); 283 | const B_bitvalue_script = generate_bitcommitment_script( 284 | right_operator_0_hash, 285 | right_operator_1_hash, 286 | ); 287 | const A_bitvalue_script = generate_bitcommitment_script( 288 | left_operator_0_hash, 289 | left_operator_1_hash, 290 | ); 291 | const complete_script_asm = `${C_bitvalue_script} OP_TOALTSTACK ${B_bitvalue_script} OP_TOALTSTACK ${A_bitvalue_script} OP_FROMALTSTACK OP_BOOLAND OP_NOT OP_FROMALTSTACK OP_EQUAL OP_0 OP_EQUALVERIFY OP_1`; 292 | return complete_script_asm; 293 | } 294 | 295 | export function generate_NAND_gate_script( 296 | left_operator_0_hash: Buffer, 297 | left_operator_1_hash: Buffer, 298 | right_operator_0_hash: Buffer, 299 | right_operator_1_hash: Buffer, 300 | result_0_hash: Buffer, 301 | result_1_hash: Buffer, 302 | ): string { 303 | const C_bitvalue_script = generate_bitcommitment_script( 304 | result_0_hash, 305 | result_1_hash, 306 | ); 307 | const B_bitvalue_script = generate_bitcommitment_script( 308 | right_operator_0_hash, 309 | right_operator_1_hash, 310 | ); 311 | const A_bitvalue_script = generate_bitcommitment_script( 312 | left_operator_0_hash, 313 | left_operator_1_hash, 314 | ); 315 | const complete_script_asm = `${C_bitvalue_script} OP_TOALTSTACK ${B_bitvalue_script} OP_TOALTSTACK ${A_bitvalue_script} OP_FROMALTSTACK OP_BOOLAND OP_NOT OP_FROMALTSTACK OP_EQUALVERIFY OP_1`; 316 | return complete_script_asm; 317 | } 318 | 319 | export function one_round_challenge_and_response( 320 | gate_hash_lock_script: string, 321 | challenge_tree: Taptree, 322 | gate_hashlock_and_gate_script: string, 323 | response_taptree: Taptree, 324 | keypair: Signer, 325 | ) { 326 | // send the challenge NAND2 transaction 327 | const challenge_script_redeem = { 328 | output: script.fromASM(gate_hash_lock_script), 329 | redeemVersion: 192, 330 | }; 331 | 332 | const challenge_p2tr = payments.p2tr({ 333 | internalPubkey: toXOnly(keypair.publicKey), 334 | scriptTree: challenge_tree, 335 | redeem: challenge_script_redeem, 336 | network, 337 | }); 338 | // prover can response the challenge by entering the preimage 339 | // which can be found from the and reveal the input and output for the NAND gate 340 | const response_script_redeem = { 341 | output: script.fromASM(gate_hashlock_and_gate_script), 342 | redeemVersion: 192, 343 | }; 344 | 345 | const response_p2tr = payments.p2tr({ 346 | internalPubkey: toXOnly(keypair.publicKey), 347 | scriptTree: response_taptree, 348 | redeem: response_script_redeem, 349 | network, 350 | }); 351 | 352 | return { challenge_p2tr, response_p2tr }; 353 | } 354 | 355 | export async function send_tx( 356 | txname: string, 357 | p2tr: payments.Payment, 358 | to_addr: string, 359 | inputs: any[], 360 | keypair: Signer, 361 | ) { 362 | const challenge_script_redeem: payments.Payment = p2tr.redeem!; 363 | 364 | // taproot address generated by taptree + keypair 365 | const challenge_addr = p2tr.address ?? ""; 366 | console.log( 367 | `[TxName:${txname}]_script:${challenge_script_redeem.output!.toString( 368 | "hex", 369 | )}`, 370 | ); 371 | console.log( 372 | `[TxName:${txname}] Waiting till UTXO is detected at addr: ${challenge_addr}`, 373 | ); 374 | let miner_fee: number = 250; 375 | let utxos = await waitUntilUTXO(challenge_addr, miner_fee); 376 | console.log( 377 | `Trying the Hash lock spend path with UTXO ${utxos[0].txid}:${utxos[0].vout}`, 378 | ); 379 | 380 | const tapLeafScript = { 381 | leafVersion: challenge_script_redeem.redeemVersion!, 382 | script: challenge_script_redeem.output!, 383 | controlBlock: p2tr.witness![p2tr.witness!.length - 1], 384 | }; 385 | 386 | const psbt = new Psbt({ network }); 387 | let utxo_with_maxvalue = utxos[0]; 388 | if (utxos.length > 1) { 389 | utxos.forEach((utxo, index, array) => { 390 | if (utxo.value > utxo_with_maxvalue.value) { 391 | utxo_with_maxvalue = utxo; 392 | } 393 | }); 394 | } 395 | psbt.addInput({ 396 | hash: utxo_with_maxvalue.txid, 397 | index: utxo_with_maxvalue.vout, 398 | witnessUtxo: { value: utxo_with_maxvalue.value, script: p2tr.output! }, 399 | tapLeafScript: [tapLeafScript], 400 | }); 401 | 402 | psbt.addOutput({ 403 | address: to_addr, // acount1 address 404 | value: utxo_with_maxvalue.value - miner_fee, // We need to provide enough fee to miner (1stoash/1byte) 405 | }); 406 | 407 | const customFinalizer = (_inputIndex: number, input: any) => { 408 | const scriptSolution = inputs; 409 | const witness = scriptSolution 410 | .concat(tapLeafScript.script) 411 | .concat(tapLeafScript.controlBlock); 412 | 413 | return { 414 | finalScriptWitness: witnessStackToScriptWitness(witness), 415 | }; 416 | }; 417 | 418 | psbt.finalizeInput(0, customFinalizer); 419 | let tx = psbt.extractTransaction(); 420 | let txHex = tx.toHex(); 421 | console.warn( 422 | `Transaction Length:${txHex.length / 2}; ${tx.byteLength( 423 | true, 424 | )}; ${tx.byteLength(false)}; ${tx.byteLength()}`, 425 | ); 426 | console.log(`Broadcasting Transaction Hex: ${txHex}`); 427 | 428 | // tx.outs[0].value = utxo_with_maxvalue.value - txHex.length/2; 429 | 430 | let txid = await broadcast(tx.toHex()); 431 | console.log(`Success! Txid is ${txid}`); 432 | } 433 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function toXOnly(pubkey: Buffer): Buffer { 2 | return pubkey.subarray(1, 33); 3 | } 4 | 5 | export function getRandomNumber(min: number, max: number): number { 6 | return Math.floor(Math.random() * (max - min + 1)) + min; 7 | } 8 | 9 | // export function getRandomNumbers( 10 | // count: number, 11 | // ): Buffer[] { 12 | // let result: Buffer[] = []; 13 | // for (let i = 0; i < count; i++) { 14 | // result.push(Buffer.from([Math.random()])); 15 | // } 16 | // return result; 17 | // } 18 | 19 | export function getRandomNumbers(count: number): Buffer[] { 20 | let result: Buffer[] = []; 21 | for (let i = 0; i < count; i++) { 22 | result.push( 23 | Buffer.from([ 24 | getRandomNumber(0, 255), 25 | getRandomNumber(0, 255), 26 | getRandomNumber(0, 255), 27 | getRandomNumber(0, 255), 28 | ]), 29 | ); 30 | } 31 | return result; 32 | } 33 | -------------------------------------------------------------------------------- /src/witness_stack_to_script_witness.ts: -------------------------------------------------------------------------------- 1 | import varuint from "varuint-bitcoin"; 2 | 3 | /** 4 | * Helper function that produces a serialized witness script 5 | * https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/csv.spec.ts#L477 6 | */ 7 | export function witnessStackToScriptWitness(witness: Buffer[]) { 8 | let buffer = Buffer.allocUnsafe(0); 9 | 10 | function writeSlice(slice: Buffer) { 11 | buffer = Buffer.concat([buffer, Buffer.from(slice)]); 12 | } 13 | 14 | function writeVarInt(i: number) { 15 | const currentLen = buffer.length; 16 | const varintLen = varuint.encodingLength(i); 17 | 18 | buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); 19 | varuint.encode(i, buffer, currentLen); 20 | } 21 | 22 | function writeVarSlice(slice: Buffer) { 23 | writeVarInt(slice.length); 24 | writeSlice(slice); 25 | } 26 | 27 | function writeVector(vector: Buffer[]) { 28 | writeVarInt(vector.length); 29 | vector.forEach(writeVarSlice); 30 | } 31 | 32 | writeVector(witness); 33 | 34 | return buffer; 35 | } 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "commonjs", /* Specify what module code is generated. */ 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/dotenv@^8.2.0": 6 | version "8.2.0" 7 | resolved "https://registry.npmjs.org/@types/dotenv/-/dotenv-8.2.0.tgz" 8 | integrity sha512-ylSC9GhfRH7m1EUXBXofhgx4lUWmFeQDINW5oLuS+gxWdfUeW4zJdeVTYVkexEW+e2VUvlZR2kGnGGipAWR7kw== 9 | dependencies: 10 | dotenv "*" 11 | 12 | "@types/node@^18.13.0": 13 | version "18.13.0" 14 | resolved "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz" 15 | integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg== 16 | 17 | asynckit@^0.4.0: 18 | version "0.4.0" 19 | resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" 20 | integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== 21 | 22 | axios@^1.3.2: 23 | version "1.3.2" 24 | resolved "https://registry.npmjs.org/axios/-/axios-1.3.2.tgz" 25 | integrity sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw== 26 | dependencies: 27 | follow-redirects "^1.15.0" 28 | form-data "^4.0.0" 29 | proxy-from-env "^1.1.0" 30 | 31 | base-x@^3.0.2: 32 | version "3.0.9" 33 | resolved "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz" 34 | integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== 35 | dependencies: 36 | safe-buffer "^5.0.1" 37 | 38 | bech32@^2.0.0: 39 | version "2.0.0" 40 | resolved "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz" 41 | integrity sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg== 42 | 43 | bip174@^2.1.0: 44 | version "2.1.0" 45 | resolved "https://registry.npmjs.org/bip174/-/bip174-2.1.0.tgz" 46 | integrity sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA== 47 | 48 | bitcoinjs-lib@^6.1.0: 49 | version "6.1.0" 50 | resolved "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.0.tgz" 51 | integrity sha512-eupi1FBTJmPuAZdChnzTXLv2HBqFW2AICpzXZQLniP0V9FWWeeUQSMKES6sP8isy/xO0ijDexbgkdEyFVrsuJw== 52 | dependencies: 53 | bech32 "^2.0.0" 54 | bip174 "^2.1.0" 55 | bs58check "^2.1.2" 56 | create-hash "^1.1.0" 57 | ripemd160 "^2.0.2" 58 | typeforce "^1.11.3" 59 | varuint-bitcoin "^1.1.2" 60 | wif "^2.0.1" 61 | 62 | bs58@^4.0.0: 63 | version "4.0.1" 64 | resolved "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz" 65 | integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== 66 | dependencies: 67 | base-x "^3.0.2" 68 | 69 | bs58check@^2.1.2, bs58check@<3.0.0: 70 | version "2.1.2" 71 | resolved "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz" 72 | integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== 73 | dependencies: 74 | bs58 "^4.0.0" 75 | create-hash "^1.1.0" 76 | safe-buffer "^5.1.2" 77 | 78 | cipher-base@^1.0.1: 79 | version "1.0.4" 80 | resolved "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz" 81 | integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== 82 | dependencies: 83 | inherits "^2.0.1" 84 | safe-buffer "^5.0.1" 85 | 86 | combined-stream@^1.0.8: 87 | version "1.0.8" 88 | resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" 89 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 90 | dependencies: 91 | delayed-stream "~1.0.0" 92 | 93 | create-hash@^1.1.0: 94 | version "1.2.0" 95 | resolved "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz" 96 | integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== 97 | dependencies: 98 | cipher-base "^1.0.1" 99 | inherits "^2.0.1" 100 | md5.js "^1.3.4" 101 | ripemd160 "^2.0.1" 102 | sha.js "^2.4.0" 103 | 104 | delayed-stream@~1.0.0: 105 | version "1.0.0" 106 | resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" 107 | integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== 108 | 109 | dotenv@*: 110 | version "16.4.4" 111 | resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.4.tgz" 112 | integrity sha512-XvPXc8XAQThSjAbY6cQ/9PcBXmFoWuw1sQ3b8HqUCR6ziGXjkTi//kB9SWa2UwqlgdAIuRqAa/9hVljzPehbYg== 113 | 114 | ecpair@^2.1.0: 115 | version "2.1.0" 116 | resolved "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz" 117 | integrity sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw== 118 | dependencies: 119 | randombytes "^2.1.0" 120 | typeforce "^1.18.0" 121 | wif "^2.0.6" 122 | 123 | follow-redirects@^1.15.0: 124 | version "1.15.2" 125 | resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" 126 | integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== 127 | 128 | form-data@^4.0.0: 129 | version "4.0.0" 130 | resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" 131 | integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== 132 | dependencies: 133 | asynckit "^0.4.0" 134 | combined-stream "^1.0.8" 135 | mime-types "^2.1.12" 136 | 137 | hash-base@^3.0.0: 138 | version "3.1.0" 139 | resolved "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz" 140 | integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== 141 | dependencies: 142 | inherits "^2.0.4" 143 | readable-stream "^3.6.0" 144 | safe-buffer "^5.2.0" 145 | 146 | inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4: 147 | version "2.0.4" 148 | resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" 149 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 150 | 151 | md5.js@^1.3.4: 152 | version "1.3.5" 153 | resolved "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz" 154 | integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== 155 | dependencies: 156 | hash-base "^3.0.0" 157 | inherits "^2.0.1" 158 | safe-buffer "^5.1.2" 159 | 160 | mime-db@1.52.0: 161 | version "1.52.0" 162 | resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" 163 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 164 | 165 | mime-types@^2.1.12: 166 | version "2.1.35" 167 | resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" 168 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 169 | dependencies: 170 | mime-db "1.52.0" 171 | 172 | prettier@^3.0.3: 173 | version "3.0.3" 174 | resolved "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz" 175 | integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== 176 | 177 | proxy-from-env@^1.1.0: 178 | version "1.1.0" 179 | resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" 180 | integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== 181 | 182 | randombytes@^2.1.0: 183 | version "2.1.0" 184 | resolved "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz" 185 | integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== 186 | dependencies: 187 | safe-buffer "^5.1.0" 188 | 189 | readable-stream@^3.6.0: 190 | version "3.6.0" 191 | resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" 192 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== 193 | dependencies: 194 | inherits "^2.0.3" 195 | string_decoder "^1.1.1" 196 | util-deprecate "^1.0.1" 197 | 198 | ripemd160@^2.0.1, ripemd160@^2.0.2: 199 | version "2.0.2" 200 | resolved "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz" 201 | integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== 202 | dependencies: 203 | hash-base "^3.0.0" 204 | inherits "^2.0.1" 205 | 206 | safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: 207 | version "5.2.1" 208 | resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" 209 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 210 | 211 | sha.js@^2.4.0: 212 | version "2.4.11" 213 | resolved "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz" 214 | integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== 215 | dependencies: 216 | inherits "^2.0.1" 217 | safe-buffer "^5.0.1" 218 | 219 | string_decoder@^1.1.1: 220 | version "1.3.0" 221 | resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" 222 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 223 | dependencies: 224 | safe-buffer "~5.2.0" 225 | 226 | tiny-secp256k1@^2.2.1: 227 | version "2.2.1" 228 | resolved "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.1.tgz" 229 | integrity sha512-/U4xfVqnVxJXN4YVsru0E6t5wVncu2uunB8+RVR40fYUxkKYUPS10f+ePQZgFBoE/Jbf9H1NBveupF2VmB58Ng== 230 | dependencies: 231 | uint8array-tools "0.0.7" 232 | 233 | typeforce@^1.11.3, typeforce@^1.18.0: 234 | version "1.18.0" 235 | resolved "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz" 236 | integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== 237 | 238 | typescript@^4.9.5: 239 | version "4.9.5" 240 | resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" 241 | integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== 242 | 243 | uint8array-tools@0.0.7: 244 | version "0.0.7" 245 | resolved "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz" 246 | integrity sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ== 247 | 248 | util-deprecate@^1.0.1: 249 | version "1.0.2" 250 | resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" 251 | integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== 252 | 253 | varuint-bitcoin@^1.1.2: 254 | version "1.1.2" 255 | resolved "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz" 256 | integrity sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw== 257 | dependencies: 258 | safe-buffer "^5.1.1" 259 | 260 | wif@^2.0.1, wif@^2.0.6: 261 | version "2.0.6" 262 | resolved "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz" 263 | integrity sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ== 264 | dependencies: 265 | bs58check "<3.0.0" 266 | --------------------------------------------------------------------------------