diff --git a/.gitignore b/.gitignore index e69de29..40b878d 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6a96bd5 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +build: + npm install -g pnpm + pnpm install +run: + pnpm tsx src/index.ts \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..bc6a7f1 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "code-challenge-2024-nishu0", + "version": "1.0.0", + "description": "Summer of Bitcoin 2024: Mine your first block", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "bitcoinjs-lib": "^6.1.5", + "ecpair": "^2.1.0", + "lodash.clonedeep": "^4.5.0", + "tiny-secp256k1": "^2.2.3", + "tsx": "^4.7.1", + "typescript": "^5.4.3" + }, + "devDependencies": { + "@types/lodash.clonedeep": "^4.5.9", + "@types/node": "^20.11.30" + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..6cb79cd --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,555 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + bitcoinjs-lib: + specifier: ^6.1.5 + version: 6.1.5 + ecpair: + specifier: ^2.1.0 + version: 2.1.0 + lodash.clonedeep: + specifier: ^4.5.0 + version: 4.5.0 + tiny-secp256k1: + specifier: ^2.2.3 + version: 2.2.3 + tsx: + specifier: ^4.7.1 + version: 4.9.3 + typescript: + specifier: ^5.4.3 + version: 5.4.5 + devDependencies: + '@types/lodash.clonedeep': + specifier: ^4.5.9 + version: 4.5.9 + '@types/node': + specifier: ^20.11.30 + version: 20.12.10 + +packages: + + '@esbuild/aix-ppc64@0.20.2': + resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.20.2': + resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.20.2': + resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.20.2': + resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.20.2': + resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.20.2': + resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.20.2': + resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.20.2': + resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.20.2': + resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.20.2': + resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.20.2': + resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.20.2': + resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.20.2': + resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.20.2': + resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.20.2': + resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.20.2': + resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.20.2': + resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.20.2': + resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.20.2': + resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.20.2': + resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.20.2': + resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.20.2': + resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.20.2': + resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@noble/hashes@1.4.0': + resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} + engines: {node: '>= 16'} + + '@types/lodash.clonedeep@4.5.9': + resolution: {integrity: sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==} + + '@types/lodash@4.17.1': + resolution: {integrity: sha512-X+2qazGS3jxLAIz5JDXDzglAF3KpijdhFxlf/V1+hEsOUc+HnWi81L/uv/EvGuV90WY+7mPGFCUDGfQC3Gj95Q==} + + '@types/node@20.12.10': + resolution: {integrity: sha512-Eem5pH9pmWBHoGAT8Dr5fdc5rYA+4NAovdM4EktRPVAAiJhmWWfQrA0cFhAbOsQdSfIHjAud6YdkbL69+zSKjw==} + + base-x@3.0.9: + resolution: {integrity: sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==} + + base-x@4.0.0: + resolution: {integrity: sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==} + + bech32@2.0.0: + resolution: {integrity: sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==} + + bip174@2.1.1: + resolution: {integrity: sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==} + engines: {node: '>=8.0.0'} + + bitcoinjs-lib@6.1.5: + resolution: {integrity: sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ==} + engines: {node: '>=8.0.0'} + + bs58@4.0.1: + resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} + + bs58@5.0.0: + resolution: {integrity: sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==} + + bs58check@2.1.2: + resolution: {integrity: sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==} + + bs58check@3.0.1: + resolution: {integrity: sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==} + + cipher-base@1.0.4: + resolution: {integrity: sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==} + + create-hash@1.2.0: + resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} + + ecpair@2.1.0: + resolution: {integrity: sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==} + engines: {node: '>=8.0.0'} + + esbuild@0.20.2: + resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} + engines: {node: '>=12'} + hasBin: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-tsconfig@4.7.4: + resolution: {integrity: sha512-ofbkKj+0pjXjhejr007J/fLf+sW+8H7K5GCm+msC8q3IpvgjobpyPqSRFemNyIMxklC0zeJpi7VDFna19FacvQ==} + + hash-base@3.1.0: + resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==} + engines: {node: '>=4'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + + md5.js@1.3.5: + resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + ripemd160@2.0.2: + resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + sha.js@2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + hasBin: true + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + tiny-secp256k1@2.2.3: + resolution: {integrity: sha512-SGcL07SxcPN2nGKHTCvRMkQLYPSoeFcvArUSCYtjVARiFAWU44cCIqYS0mYAU6nY7XfvwURuTIGo2Omt3ZQr0Q==} + engines: {node: '>=14.0.0'} + + tsx@4.9.3: + resolution: {integrity: sha512-czVbetlILiyJZI5zGlj2kw9vFiSeyra9liPD4nG+Thh4pKTi0AmMEQ8zdV/L2xbIVKrIqif4sUNrsMAOksx9Zg==} + engines: {node: '>=18.0.0'} + hasBin: true + + typeforce@1.18.0: + resolution: {integrity: sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==} + + typescript@5.4.5: + resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + engines: {node: '>=14.17'} + hasBin: true + + uint8array-tools@0.0.7: + resolution: {integrity: sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==} + engines: {node: '>=14.0.0'} + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + varuint-bitcoin@1.1.2: + resolution: {integrity: sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==} + + wif@2.0.6: + resolution: {integrity: sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==} + +snapshots: + + '@esbuild/aix-ppc64@0.20.2': + optional: true + + '@esbuild/android-arm64@0.20.2': + optional: true + + '@esbuild/android-arm@0.20.2': + optional: true + + '@esbuild/android-x64@0.20.2': + optional: true + + '@esbuild/darwin-arm64@0.20.2': + optional: true + + '@esbuild/darwin-x64@0.20.2': + optional: true + + '@esbuild/freebsd-arm64@0.20.2': + optional: true + + '@esbuild/freebsd-x64@0.20.2': + optional: true + + '@esbuild/linux-arm64@0.20.2': + optional: true + + '@esbuild/linux-arm@0.20.2': + optional: true + + '@esbuild/linux-ia32@0.20.2': + optional: true + + '@esbuild/linux-loong64@0.20.2': + optional: true + + '@esbuild/linux-mips64el@0.20.2': + optional: true + + '@esbuild/linux-ppc64@0.20.2': + optional: true + + '@esbuild/linux-riscv64@0.20.2': + optional: true + + '@esbuild/linux-s390x@0.20.2': + optional: true + + '@esbuild/linux-x64@0.20.2': + optional: true + + '@esbuild/netbsd-x64@0.20.2': + optional: true + + '@esbuild/openbsd-x64@0.20.2': + optional: true + + '@esbuild/sunos-x64@0.20.2': + optional: true + + '@esbuild/win32-arm64@0.20.2': + optional: true + + '@esbuild/win32-ia32@0.20.2': + optional: true + + '@esbuild/win32-x64@0.20.2': + optional: true + + '@noble/hashes@1.4.0': {} + + '@types/lodash.clonedeep@4.5.9': + dependencies: + '@types/lodash': 4.17.1 + + '@types/lodash@4.17.1': {} + + '@types/node@20.12.10': + dependencies: + undici-types: 5.26.5 + + base-x@3.0.9: + dependencies: + safe-buffer: 5.2.1 + + base-x@4.0.0: {} + + bech32@2.0.0: {} + + bip174@2.1.1: {} + + bitcoinjs-lib@6.1.5: + dependencies: + '@noble/hashes': 1.4.0 + bech32: 2.0.0 + bip174: 2.1.1 + bs58check: 3.0.1 + typeforce: 1.18.0 + varuint-bitcoin: 1.1.2 + + bs58@4.0.1: + dependencies: + base-x: 3.0.9 + + bs58@5.0.0: + dependencies: + base-x: 4.0.0 + + bs58check@2.1.2: + dependencies: + bs58: 4.0.1 + create-hash: 1.2.0 + safe-buffer: 5.2.1 + + bs58check@3.0.1: + dependencies: + '@noble/hashes': 1.4.0 + bs58: 5.0.0 + + cipher-base@1.0.4: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + + create-hash@1.2.0: + dependencies: + cipher-base: 1.0.4 + inherits: 2.0.4 + md5.js: 1.3.5 + ripemd160: 2.0.2 + sha.js: 2.4.11 + + ecpair@2.1.0: + dependencies: + randombytes: 2.1.0 + typeforce: 1.18.0 + wif: 2.0.6 + + esbuild@0.20.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.20.2 + '@esbuild/android-arm': 0.20.2 + '@esbuild/android-arm64': 0.20.2 + '@esbuild/android-x64': 0.20.2 + '@esbuild/darwin-arm64': 0.20.2 + '@esbuild/darwin-x64': 0.20.2 + '@esbuild/freebsd-arm64': 0.20.2 + '@esbuild/freebsd-x64': 0.20.2 + '@esbuild/linux-arm': 0.20.2 + '@esbuild/linux-arm64': 0.20.2 + '@esbuild/linux-ia32': 0.20.2 + '@esbuild/linux-loong64': 0.20.2 + '@esbuild/linux-mips64el': 0.20.2 + '@esbuild/linux-ppc64': 0.20.2 + '@esbuild/linux-riscv64': 0.20.2 + '@esbuild/linux-s390x': 0.20.2 + '@esbuild/linux-x64': 0.20.2 + '@esbuild/netbsd-x64': 0.20.2 + '@esbuild/openbsd-x64': 0.20.2 + '@esbuild/sunos-x64': 0.20.2 + '@esbuild/win32-arm64': 0.20.2 + '@esbuild/win32-ia32': 0.20.2 + '@esbuild/win32-x64': 0.20.2 + + fsevents@2.3.3: + optional: true + + get-tsconfig@4.7.4: + dependencies: + resolve-pkg-maps: 1.0.0 + + hash-base@3.1.0: + dependencies: + inherits: 2.0.4 + readable-stream: 3.6.2 + safe-buffer: 5.2.1 + + inherits@2.0.4: {} + + lodash.clonedeep@4.5.0: {} + + md5.js@1.3.5: + dependencies: + hash-base: 3.1.0 + inherits: 2.0.4 + safe-buffer: 5.2.1 + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + resolve-pkg-maps@1.0.0: {} + + ripemd160@2.0.2: + dependencies: + hash-base: 3.1.0 + inherits: 2.0.4 + + safe-buffer@5.2.1: {} + + sha.js@2.4.11: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + tiny-secp256k1@2.2.3: + dependencies: + uint8array-tools: 0.0.7 + + tsx@4.9.3: + dependencies: + esbuild: 0.20.2 + get-tsconfig: 4.7.4 + optionalDependencies: + fsevents: 2.3.3 + + typeforce@1.18.0: {} + + typescript@5.4.5: {} + + uint8array-tools@0.0.7: {} + + undici-types@5.26.5: {} + + util-deprecate@1.0.2: {} + + varuint-bitcoin@1.1.2: + dependencies: + safe-buffer: 5.2.1 + + wif@2.0.6: + dependencies: + bs58check: 2.1.2 diff --git a/run.sh b/run.sh index 721aeb2..9f2f4f4 100644 --- a/run.sh +++ b/run.sh @@ -1 +1,3 @@ -# Update this file to run your own code \ No newline at end of file +# Update this file to run your own code +make build +make run \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..d385ca5 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,2 @@ +const DIFFICULTY = + "0000ffff00000000000000000000000000000000000000000000000000000000"; diff --git a/src/features/block/coinbaseTransaction.ts b/src/features/block/coinbaseTransaction.ts new file mode 100644 index 0000000..f9366fc --- /dev/null +++ b/src/features/block/coinbaseTransaction.ts @@ -0,0 +1,47 @@ +import { Transaction } from "../../types"; +import { compactSize } from "../encoding/compactSize"; + +export const ZEROS = + "0000000000000000000000000000000000000000000000000000000000000000"; +const MAX_VALUE = 0xffffffff; +const height = 538403; +const blockReward = 1250000000; + +const coinbaseTemplate = { + version: 0, + locktime: 0, + vin: [ + { + txid: ZEROS, + vout: MAX_VALUE, + prevout: null, + scriptsig: "03233708", + scriptsig_asm: "OP_PUSHBYTES_3 233708", + }, + ], + vout: [ + { + scriptpubkey: "76a914edf10a7fac6b32e24daa5305c723f3de58db1bc888ac", + scriptpubkey_asm: + "OP_DUP OP_HASH160 OP_PUSHBYTES_20 edf10a7fac6b32e24daa5305c723f3de58db1bc8 OP_EQUALVERIFY OP_CHECKSIG", + scriptpubkey_type: "p2pkh", + value: blockReward, + }, + { + //op return push 36 bytes 4 btyes commitment + hash256 of witness merkle root + zeros + scriptpubkey: "6a24aa21a9ed", // add the witness commitment later + scriptpubkey_type: "op_return", + value: 0, + }, + ], +} as unknown as Transaction; + +export const generateCoinbaseTransaction = ( + totalFee: number, + commitmentHeader: string +) => { + coinbaseTemplate.vout[1].scriptpubkey = `6a24aa21a9ed${commitmentHeader}`; + coinbaseTemplate.vout[0].value = blockReward + totalFee; + + return coinbaseTemplate; +}; diff --git a/src/features/block/fee.ts b/src/features/block/fee.ts new file mode 100644 index 0000000..2856a32 --- /dev/null +++ b/src/features/block/fee.ts @@ -0,0 +1,31 @@ +import { Transaction } from "../../types"; +import { txWeight } from "../encoding/serializer"; + +export const totalFee = (txs: Transaction[]) => { + let inputValues = 0; + let outputValues = 0; + + for (const tx of txs) { + for (const input of tx.vin) { + inputValues += input.prevout.value; + } + for (const output of tx.vout) { + outputValues += output.value; + } + } + + return inputValues - outputValues; +}; + +export const feePerByte = (tx: Transaction) => { + let fee = 0; + for (const input of tx.vin) { + fee += input.prevout.value; + } + + for (const output of tx.vout) { + fee -= output.value; + } + + return fee / txWeight(tx); +}; diff --git a/src/features/block/merkleRoot.ts b/src/features/block/merkleRoot.ts new file mode 100644 index 0000000..e268f3a --- /dev/null +++ b/src/features/block/merkleRoot.ts @@ -0,0 +1,19 @@ +import { Transaction } from "../../types"; +import { reversify, sha256 } from "../../utils"; +import { txSerializer } from "../encoding/serializer"; + +export const merkleRoot = (txs: string[]) => { + let curr = txs; + + while (curr.length > 1) { + let next = []; + + if (curr.length % 2 === 1) curr = [...curr, curr[curr.length - 1]]; + for (let i = 0; i < curr.length; i += 2) { + next.push(sha256(sha256(curr[i] + curr[i + 1]))); + } + curr = next; + } + + return curr[0]; +}; diff --git a/src/features/block/mine.ts b/src/features/block/mine.ts new file mode 100644 index 0000000..75ab45c --- /dev/null +++ b/src/features/block/mine.ts @@ -0,0 +1,77 @@ +import { reversify, sha256 } from "../../utils"; +import { Transaction } from "../../types"; +import { merkleRoot } from "./merkleRoot"; +import { txSerializer } from "../encoding/serializer"; +import { ZEROS, generateCoinbaseTransaction } from "./coinbaseTransaction"; +import { totalFee } from "./fee"; +import { isClassStaticBlockDeclaration } from "typescript"; + +export const mine = ( + txs: Transaction[] +): { + serializedBlock: string; + blockHash: string; + coinbaseTransaction: Transaction; +} => { + const difficulty = + "0000ffff00000000000000000000000000000000000000000000000000000000"; + const version = Buffer.alloc(4); + version.writeInt32LE(4); + + const prevBlockHeader = + "0000000000000000000000000000000000000000000000000000000000000000"; //1 greater than the difficulty + + const witnessMerkleRootHash = reversify( + merkleRoot([ + ZEROS, //zeros are for the coinbase transaction + ...txs.map((tx) => + reversify(sha256(sha256(txSerializer(tx).serializedWTx))) + ), + ]) + ); + const commitmentHash = sha256(sha256(witnessMerkleRootHash + ZEROS)); + const fees = totalFee(txs); + const coinbaseTransaction = generateCoinbaseTransaction(fees, commitmentHash); + + const prevBlockHash = reversify(sha256(sha256(prevBlockHeader))); + const merkleRootHash = reversify( + merkleRoot( + [coinbaseTransaction, ...txs].map((tx) => + reversify(sha256(sha256(txSerializer(tx).serializedTx))) + ) + ) + ); + + const time = Buffer.alloc(4); + time.writeUint32LE(Math.floor(Date.now() / 1000)); + const nbits = "1f00ffff"; + + for (let nonce = 0; nonce < 0xffffffff; nonce++) { + const nonceBuf = Buffer.alloc(4); + nonceBuf.writeUInt32LE(nonce); + const serializedBlock = `${version.toString( + "hex" + )}${prevBlockHash}${merkleRootHash}${time.toString( + "hex" + )}${nbits}${nonceBuf.toString("hex")}`; + + // console.log(serializedBlock); + const blockHash = reversify(sha256(sha256(serializedBlock))); + if ( + Buffer.from(difficulty, "hex").compare(Buffer.from(blockHash, "hex")) < 0 + ) + continue; + return { serializedBlock, blockHash, coinbaseTransaction }; + } + + return { + serializedBlock: "", + blockHash: "", + coinbaseTransaction: { + version: 0, + locktime: 0, + vin: [], + vout: [], + } as unknown as Transaction, + }; +}; diff --git a/src/features/encoding/compactSize.ts b/src/features/encoding/compactSize.ts new file mode 100644 index 0000000..5f262f8 --- /dev/null +++ b/src/features/encoding/compactSize.ts @@ -0,0 +1,22 @@ +export const compactSize = (num: bigint) => { + if (+num.toString() < 0xfd) { + const buf = Buffer.alloc(1); + buf.writeUintLE(+num.toString(), 0, 1); + return buf; + } else if (+num.toString() <= 0xffff) { + const buf = Buffer.alloc(3); + buf.writeUint16LE(0xfd, 0); + buf.writeUint16LE(+num.toString(), 1); + return buf; + } else if (+num.toString() <= 0xffffffff) { + const buf = Buffer.alloc(5); + buf.writeUInt16LE(0xfe, 0); + buf.writeUint32LE(+num.toString(), 1); + return buf; + } else { + const buf = Buffer.alloc(9); + buf.writeUInt16LE(0xff, 0); + buf.writeBigUInt64LE(num, 1); + return buf; + } +}; diff --git a/src/features/encoding/serializer.ts b/src/features/encoding/serializer.ts new file mode 100644 index 0000000..0fa573b --- /dev/null +++ b/src/features/encoding/serializer.ts @@ -0,0 +1,389 @@ +import { Transaction, Input, Output } from "../../types"; +import { reversify, sha256 } from "../../utils"; +import { compactSize } from "./compactSize"; +import cloneDeep from "lodash.clonedeep"; +import { ECPairFactory } from "ecpair"; +import * as ecc from "tiny-secp256k1"; +import { bitcoin } from "ecpair/src/networks"; +import { getNextNBytes } from "../script/utils"; +import { ZEROS } from "../block/coinbaseTransaction"; + +const ECPair = ECPairFactory(ecc); + +export const outputSerializer = (outTx: Output) => { + const amount = Buffer.alloc(8); + amount.writeBigInt64LE(BigInt(outTx.value), 0); + return `${amount.toString("hex")}${compactSize( + BigInt(outTx.scriptpubkey.length / 2) + ).toString("hex")}${outTx.scriptpubkey}`; +}; + +export const inputSerializer = (inTx: Input) => { + let serializedInput = ""; + + const txHash = reversify(inTx.txid); + serializedInput += txHash; + + const outputIndex = Buffer.alloc(4); + outputIndex.writeUint32LE(inTx.vout, 0); + serializedInput += outputIndex.toString("hex"); + + const scriptSig = inTx.scriptsig; + const scriptSigSize = compactSize(BigInt(scriptSig.length / 2)); + const sequence = Buffer.alloc(4); + sequence.writeUint32LE(inTx.sequence, 0); + + serializedInput += scriptSigSize.toString("hex"); + serializedInput += scriptSig; + serializedInput += sequence.toString("hex"); + + return serializedInput; +}; + +export const txSerializer = (tx: Transaction) => { + let serializedTx = ""; + let serializedWTx = ""; + + const version = Buffer.alloc(4); + version.writeInt16LE(tx.version, 0); + serializedTx += version.toString("hex"); + serializedWTx += version.toString("hex"); + + serializedWTx += "0001"; + + const numInputs = compactSize(BigInt(tx.vin.length)); + serializedTx += numInputs.toString("hex"); + serializedWTx += numInputs.toString("hex"); + + for (let i = 0; i < tx.vin.length; i++) { + serializedTx += inputSerializer(tx.vin[i]); + serializedWTx += inputSerializer(tx.vin[i]); + } + + const numOutputs = compactSize(BigInt(tx.vout.length)); + serializedTx += numOutputs.toString("hex"); + serializedWTx += numOutputs.toString("hex"); + for (let i = 0; i < tx.vout.length; i++) { + serializedTx += outputSerializer(tx.vout[i]); + serializedWTx += outputSerializer(tx.vout[i]); + } + + let isWitness = false; + for (let i = 0; i < tx.vin.length; i++) { + if (!tx.vin[i].witness || tx.vin[i].witness.length === 0) { + serializedWTx += compactSize(BigInt(0)).toString("hex"); + } else { + isWitness = true; + serializedWTx += compactSize(BigInt(tx.vin[i].witness.length)).toString( + "hex" + ); + for (const witness of tx.vin[i].witness) { + serializedWTx += compactSize(BigInt(witness.length / 2)).toString( + "hex" + ); + serializedWTx += witness; + } + } + } + + const locktime = Buffer.alloc(4); + locktime.writeUint32LE(tx.locktime, 0); + serializedTx += locktime.toString("hex"); + serializedWTx += locktime.toString("hex"); + + return { + serializedTx, + serializedWTx: isWitness + ? tx.vin[0].txid === ZEROS + ? ZEROS + : serializedWTx + : serializedTx, + }; +}; + +export const txWeight = (tx: Transaction) => { + return txSerializer(tx).serializedTx.length / 2; // divide by two cuz 2 hex chars are 1 byte and 1e6 as you cconsider it in mb +}; + +const txForSigning = (tx: Transaction, input: number) => { + const txCopy = cloneDeep(tx); + for (let i = 0; i < txCopy.vin.length; i++) { + if (i === input) { + txCopy.vin[i].scriptsig = txCopy.vin[i].prevout.scriptpubkey; + } else { + txCopy.vin[i].scriptsig = ""; + } + } + + return txSerializer(txCopy).serializedTx + "01000000"; //force SIGHASH_ALL +}; + +const extractRSFromSignature = (derEncodedSignature: string) => { + let derEncodingScheme, + signatureLength, + r, + s, + rLength, + sLength, + rest, + prefix, + padding; + [derEncodingScheme, rest] = getNextNBytes(derEncodedSignature, 1); + if (derEncodingScheme !== "30") + throw new Error("Invalid DER encoding scheme"); + [signatureLength, rest] = getNextNBytes(rest, 1); + [prefix, rest] = getNextNBytes(rest, 1); + [rLength, rest] = getNextNBytes(rest, 1); + [r, rest] = getNextNBytes(rest, parseInt(rLength, 16)); + if (r.length === 66) [padding, r] = getNextNBytes(r, 1); //account for 00 padding + [prefix, rest] = getNextNBytes(rest, 1); + [sLength, rest] = getNextNBytes(rest, 1); + [s, rest] = getNextNBytes(rest, parseInt(sLength, 16)); + return r + s; +}; + +// const tx = { +// txid: "036da43312463ef1dff92d7c894a5362e07ff5b3111d1f166ba4cd91f3b142b7", +// version: 1, +// locktime: 0, +// vin: [ +// { +// txid: "29c1da61a7f859bfb198406cdaf333eb2dffb0878217e9a91b0451d225f3b985", +// vout: 46, +// prevout: { +// scriptpubkey: "76a914b93e0466997c5ffa7daec8a39746f34c8756ce7788ac", +// scriptpubkey_asm: +// "OP_DUP OP_HASH160 OP_PUSHBYTES_20 b93e0466997c5ffa7daec8a39746f34c8756ce77 OP_EQUALVERIFY OP_CHECKSIG", +// scriptpubkey_type: "p2pkh", +// scriptpubkey_address: "1HtUJX5U3kh6dqFNmkzBrqmEFvyoVwLrN7", +// value: 115216, +// }, +// scriptsig: +// "4830450221009ef1e4141890b2a16a788a0ea04bb0133c02ec507649e65e576d6e372cd123020220765c2cfef1ce59ea5e064797d9dbfdcb00f83371f21af06f5b3b91d23717df2e814104019fab1f0b85b95be4905a712e73415cbddb84a1dbb06653e7f9f68782ece6fc630ee111f10ba7cb0dabfe44b899018df73f541368d375f62fe82968eb404adc", +// scriptsig_asm: +// "OP_PUSHBYTES_72 30450221009ef1e4141890b2a16a788a0ea04bb0133c02ec507649e65e576d6e372cd123020220765c2cfef1ce59ea5e064797d9dbfdcb00f83371f21af06f5b3b91d23717df2e81 OP_PUSHBYTES_65 04019fab1f0b85b95be4905a712e73415cbddb84a1dbb06653e7f9f68782ece6fc630ee111f10ba7cb0dabfe44b899018df73f541368d375f62fe82968eb404adc", +// is_coinbase: false, +// sequence: 4294967295, +// }, +// { +// txid: "29c1da61a7f859bfb198406cdaf333eb2dffb0878217e9a91b0451d225f3b985", +// vout: 937, +// prevout: { +// scriptpubkey: "76a9147208148561e8ce36eb6961fde13c6e507c646b9588ac", +// scriptpubkey_asm: +// "OP_DUP OP_HASH160 OP_PUSHBYTES_20 7208148561e8ce36eb6961fde13c6e507c646b95 OP_EQUALVERIFY OP_CHECKSIG", +// scriptpubkey_type: "p2pkh", +// scriptpubkey_address: "1BPwiHhyyXMuFQJ4J2aU52anNM4j1GfVjY", +// value: 106018, +// }, +// scriptsig: +// "483045022100ffc0e60d71e03701a4b8b17e19fae07f490929611bad0a7b1b60f02af141efd202201d1b56818d0edbbaf652478134b62e09510d1c41ec4d97eeec52014f379826bc8141049fd33ff7c01fd6f17e10bd220f7f64abc179ae6386b239d91ab8080e335c83f92b7a0e7d2e7127f3fdaf466b5ddc9fa645f9579385dbfb4bbbe76ad028300adc", +// scriptsig_asm: +// "OP_PUSHBYTES_72 3045022100ffc0e60d71e03701a4b8b17e19fae07f490929611bad0a7b1b60f02af141efd202201d1b56818d0edbbaf652478134b62e09510d1c41ec4d97eeec52014f379826bc81 OP_PUSHBYTES_65 049fd33ff7c01fd6f17e10bd220f7f64abc179ae6386b239d91ab8080e335c83f92b7a0e7d2e7127f3fdaf466b5ddc9fa645f9579385dbfb4bbbe76ad028300adc", +// is_coinbase: false, +// sequence: 4294967295, +// }, +// { +// txid: "2cebf56a421673294603c5e1fbebd269fd74b30a6a3fdd48acbdbbca26b0225c", +// vout: 525, +// prevout: { +// scriptpubkey: "76a914571e43b6fe5098e761e86dfdde5e8e219eceb92988ac", +// scriptpubkey_asm: +// "OP_DUP OP_HASH160 OP_PUSHBYTES_20 571e43b6fe5098e761e86dfdde5e8e219eceb929 OP_EQUALVERIFY OP_CHECKSIG", +// scriptpubkey_type: "p2pkh", +// scriptpubkey_address: "18we3WFb3CpPBkkzCbBUcCSPKiVHrJYfPy", +// value: 111225, +// }, +// scriptsig: +// "483045022100839d571b36720b7d10a0155caca8653b7d62a996b620ed29657c4b68eb3b9345022004f9ffd2f917825137d5e98a19b5294bc0c1e5c5f9f33172f953e3a75a763703814104a5e3ae2399d6aa527ec7f5bffc40e3dd5d4b9dc2d4f8c91e001f2d179042e144ca509b45f592e4f7cad65063a7fdfae757e19c2a39dce682e56f3179ad8387e3", +// scriptsig_asm: +// "OP_PUSHBYTES_72 3045022100839d571b36720b7d10a0155caca8653b7d62a996b620ed29657c4b68eb3b9345022004f9ffd2f917825137d5e98a19b5294bc0c1e5c5f9f33172f953e3a75a76370381 OP_PUSHBYTES_65 04a5e3ae2399d6aa527ec7f5bffc40e3dd5d4b9dc2d4f8c91e001f2d179042e144ca509b45f592e4f7cad65063a7fdfae757e19c2a39dce682e56f3179ad8387e3", +// is_coinbase: false, +// sequence: 4294967295, +// }, +// { +// txid: "ced733236cd304eafcdbb10bfce1662d9f9ea6a8c3ca5f1fb9a61a67645c7ce6", +// vout: 0, +// prevout: { +// scriptpubkey: "76a914dfe6561dd4df2de04037168b7a84705d48fcec1988ac", +// scriptpubkey_asm: +// "OP_DUP OP_HASH160 OP_PUSHBYTES_20 dfe6561dd4df2de04037168b7a84705d48fcec19 OP_EQUALVERIFY OP_CHECKSIG", +// scriptpubkey_type: "p2pkh", +// scriptpubkey_address: "1MQscqDZ5S7uGsPXGBkGQS3GqDjd1rKQvE", +// value: 11761, +// }, +// scriptsig: +// "483045022100daa4d98a46efb36a668b6ceec2d076ef80ae16fc754aec0ed0ecef536e6d565d02204b130560491ef754730ad87aa15f6f98ed48fa354be84ea796639ca2287ccba18141044591aa390ae7e5329fe8b29ba367c7e4fa65ec7147d727139862ac53ec25981c18a2826a89bc2a6fd19dae27328855cb8ffc37307c8634930e7ed81ce05a94c8", +// scriptsig_asm: +// "OP_PUSHBYTES_72 3045022100daa4d98a46efb36a668b6ceec2d076ef80ae16fc754aec0ed0ecef536e6d565d02204b130560491ef754730ad87aa15f6f98ed48fa354be84ea796639ca2287ccba181 OP_PUSHBYTES_65 044591aa390ae7e5329fe8b29ba367c7e4fa65ec7147d727139862ac53ec25981c18a2826a89bc2a6fd19dae27328855cb8ffc37307c8634930e7ed81ce05a94c8", +// is_coinbase: false, +// sequence: 4294967295, +// }, +// { +// txid: "14de8d61ae9bc01491862372a909ba5ac8ec38941ccc8e2831f5f79a0951f529", +// vout: 275, +// prevout: { +// scriptpubkey: "76a914dd67ab7c4fa493e6e9eac33f3f72827d2109fde988ac", +// scriptpubkey_asm: +// "OP_DUP OP_HASH160 OP_PUSHBYTES_20 dd67ab7c4fa493e6e9eac33f3f72827d2109fde9 OP_EQUALVERIFY OP_CHECKSIG", +// scriptpubkey_type: "p2pkh", +// scriptpubkey_address: "1MBgXWBJZyPvCrTL56Jf533bHnET9RCd8L", +// value: 130541, +// }, +// scriptsig: +// "473044022042bb90214771653e6511a64d22136e0b4001f5077724d461f17134c8fb38609b02206a23c74ca8879984e1aa617b918289a630f1a47a1c525534ece0506b89ea3df2814104844ecaf938d1610c746155ff15571e63ad890e3bdb51c2adabd7d5399a8a403877e0f92d5abb8bee5bb753e9fb622dc7449de58b830ea208f0b63ab2a663b072", +// scriptsig_asm: +// "OP_PUSHBYTES_71 3044022042bb90214771653e6511a64d22136e0b4001f5077724d461f17134c8fb38609b02206a23c74ca8879984e1aa617b918289a630f1a47a1c525534ece0506b89ea3df281 OP_PUSHBYTES_65 04844ecaf938d1610c746155ff15571e63ad890e3bdb51c2adabd7d5399a8a403877e0f92d5abb8bee5bb753e9fb622dc7449de58b830ea208f0b63ab2a663b072", +// is_coinbase: false, +// sequence: 4294967295, +// }, +// { +// txid: "2cebf56a421673294603c5e1fbebd269fd74b30a6a3fdd48acbdbbca26b0225c", +// vout: 409, +// prevout: { +// scriptpubkey: "76a914dd67ab7c4fa493e6e9eac33f3f72827d2109fde988ac", +// scriptpubkey_asm: +// "OP_DUP OP_HASH160 OP_PUSHBYTES_20 dd67ab7c4fa493e6e9eac33f3f72827d2109fde9 OP_EQUALVERIFY OP_CHECKSIG", +// scriptpubkey_type: "p2pkh", +// scriptpubkey_address: "1MBgXWBJZyPvCrTL56Jf533bHnET9RCd8L", +// value: 108089, +// }, +// scriptsig: +// "483045022100a1d233643d1e39211b681f2cf407c21472beb4303da6f6111bdf2de96191803b022047754e545a4d97ac32b2c7b9c4e23d2a2121ab9a299414ad03d5f6b52a28a33b814104844ecaf938d1610c746155ff15571e63ad890e3bdb51c2adabd7d5399a8a403877e0f92d5abb8bee5bb753e9fb622dc7449de58b830ea208f0b63ab2a663b072", +// scriptsig_asm: +// "OP_PUSHBYTES_72 3045022100a1d233643d1e39211b681f2cf407c21472beb4303da6f6111bdf2de96191803b022047754e545a4d97ac32b2c7b9c4e23d2a2121ab9a299414ad03d5f6b52a28a33b81 OP_PUSHBYTES_65 04844ecaf938d1610c746155ff15571e63ad890e3bdb51c2adabd7d5399a8a403877e0f92d5abb8bee5bb753e9fb622dc7449de58b830ea208f0b63ab2a663b072", +// is_coinbase: false, +// sequence: 4294967295, +// }, +// ], +// vout: [ +// { +// scriptpubkey: "76a914dba129909f56d7c889872cc691a4f8ff5c59f6fe88ac", +// scriptpubkey_asm: +// "OP_DUP OP_HASH160 OP_PUSHBYTES_20 dba129909f56d7c889872cc691a4f8ff5c59f6fe OP_EQUALVERIFY OP_CHECKSIG", +// scriptpubkey_type: "p2pkh", +// scriptpubkey_address: "1M2J3mZ53hf7GPMPPy8EPzEymdGW8cm9u6", +// value: 554914, +// }, +// ], +// size: 1123, +// weight: 4492, +// fee: 27936, +// status: { +// confirmed: true, +// block_height: 834638, +// block_hash: +// "000000000000000000025f742c626208ac87e0b7d15054abb4a19ca2d735a54e", +// block_time: 1710405325, +// }, +// hex: "010000000685b9f325d251041ba9e9178287b0ff2deb33f3da6c4098b1bf59f8a761dac1292e0000008b4830450221009ef1e4141890b2a16a788a0ea04bb0133c02ec507649e65e576d6e372cd123020220765c2cfef1ce59ea5e064797d9dbfdcb00f83371f21af06f5b3b91d23717df2e814104019fab1f0b85b95be4905a712e73415cbddb84a1dbb06653e7f9f68782ece6fc630ee111f10ba7cb0dabfe44b899018df73f541368d375f62fe82968eb404adcffffffff85b9f325d251041ba9e9178287b0ff2deb33f3da6c4098b1bf59f8a761dac129a90300008b483045022100ffc0e60d71e03701a4b8b17e19fae07f490929611bad0a7b1b60f02af141efd202201d1b56818d0edbbaf652478134b62e09510d1c41ec4d97eeec52014f379826bc8141049fd33ff7c01fd6f17e10bd220f7f64abc179ae6386b239d91ab8080e335c83f92b7a0e7d2e7127f3fdaf466b5ddc9fa645f9579385dbfb4bbbe76ad028300adcffffffff5c22b026cabbbdac48dd3f6a0ab374fd69d2ebfbe1c50346297316426af5eb2c0d0200008b483045022100839d571b36720b7d10a0155caca8653b7d62a996b620ed29657c4b68eb3b9345022004f9ffd2f917825137d5e98a19b5294bc0c1e5c5f9f33172f953e3a75a763703814104a5e3ae2399d6aa527ec7f5bffc40e3dd5d4b9dc2d4f8c91e001f2d179042e144ca509b45f592e4f7cad65063a7fdfae757e19c2a39dce682e56f3179ad8387e3ffffffffe67c5c64671aa6b91f5fcac3a8a69e9f2d66e1fc0bb1dbfcea04d36c2333d7ce000000008b483045022100daa4d98a46efb36a668b6ceec2d076ef80ae16fc754aec0ed0ecef536e6d565d02204b130560491ef754730ad87aa15f6f98ed48fa354be84ea796639ca2287ccba18141044591aa390ae7e5329fe8b29ba367c7e4fa65ec7147d727139862ac53ec25981c18a2826a89bc2a6fd19dae27328855cb8ffc37307c8634930e7ed81ce05a94c8ffffffff29f551099af7f531288ecc1c9438ecc85aba09a97223869114c09bae618dde14130100008a473044022042bb90214771653e6511a64d22136e0b4001f5077724d461f17134c8fb38609b02206a23c74ca8879984e1aa617b918289a630f1a47a1c525534ece0506b89ea3df2814104844ecaf938d1610c746155ff15571e63ad890e3bdb51c2adabd7d5399a8a403877e0f92d5abb8bee5bb753e9fb622dc7449de58b830ea208f0b63ab2a663b072ffffffff5c22b026cabbbdac48dd3f6a0ab374fd69d2ebfbe1c50346297316426af5eb2c990100008b483045022100a1d233643d1e39211b681f2cf407c21472beb4303da6f6111bdf2de96191803b022047754e545a4d97ac32b2c7b9c4e23d2a2121ab9a299414ad03d5f6b52a28a33b814104844ecaf938d1610c746155ff15571e63ad890e3bdb51c2adabd7d5399a8a403877e0f92d5abb8bee5bb753e9fb622dc7449de58b830ea208f0b63ab2a663b072ffffffff01a2770800000000001976a914dba129909f56d7c889872cc691a4f8ff5c59f6fe88ac00000000", +// } as unknown as Transaction; + +const tx = { + version: 2, + locktime: 0, + vin: [ + { + txid: "fb7fe37919a55dfa45a062f88bd3c7412b54de759115cb58c3b9b46ac5f7c925", + vout: 1, + prevout: { + scriptpubkey: "76a914286eb663201959fb12eff504329080e4c56ae28788ac", + scriptpubkey_asm: + "OP_DUP OP_HASH160 OP_PUSHBYTES_20 286eb663201959fb12eff504329080e4c56ae287 OP_EQUALVERIFY OP_CHECKSIG", + scriptpubkey_type: "p2pkh", + scriptpubkey_address: "14gnf7L2DjBYKFuWb6iftBoWE9hmAoFbcF", + value: 433833, + }, + scriptsig: + "4830450221008f619822a97841ffd26eee942d41c1c4704022af2dd42600f006336ce686353a0220659476204210b21d605baab00bef7005ff30e878e911dc99413edb6c1e022acd012102c371793f2e19d1652408efef67704a2e9953a43a9dd54360d56fc93277a5667d", + scriptsig_asm: + "OP_PUSHBYTES_72 30450221008f619822a97841ffd26eee942d41c1c4704022af2dd42600f006336ce686353a0220659476204210b21d605baab00bef7005ff30e878e911dc99413edb6c1e022acd01 OP_PUSHBYTES_33 02c371793f2e19d1652408efef67704a2e9953a43a9dd54360d56fc93277a5667d", + is_coinbase: false, + sequence: 4294967295, + }, + ], + vout: [ + { + scriptpubkey: "76a9141ef7874d338d24ecf6577e6eadeeee6cd579c67188ac", + scriptpubkey_asm: + "OP_DUP OP_HASH160 OP_PUSHBYTES_20 1ef7874d338d24ecf6577e6eadeeee6cd579c671 OP_EQUALVERIFY OP_CHECKSIG", + scriptpubkey_type: "p2pkh", + scriptpubkey_address: "13pjoLcRKqhzPCbJgYW77LSFCcuwmHN2qA", + value: 387156, + }, + { + scriptpubkey: "76a9142e391b6c47778d35586b1f4154cbc6b06dc9840c88ac", + scriptpubkey_asm: + "OP_DUP OP_HASH160 OP_PUSHBYTES_20 2e391b6c47778d35586b1f4154cbc6b06dc9840c OP_EQUALVERIFY OP_CHECKSIG", + scriptpubkey_type: "p2pkh", + scriptpubkey_address: "15DQVhQ7PU6VPsTtvwLxfDsTP4P6A3Z5vP", + value: 37320, + }, + ], +} as unknown as Transaction; + +// const { serializedTx, serializedWTx } = txSerializer( +// tx as unknown as Transaction +// ); +// console.log("serializedWTx", serializedWTx); +// console.log(reversify(sha256(sha256(serializedTx)))); +// console.log(reversify(sha256(sha256(serializedWTx)))); + +// console.log(txSerializer(tx)); +const txToBeSigned = txForSigning(tx, 0); +const hash = sha256(sha256(txToBeSigned)); + +const pubkey = ECPair.fromPublicKey( + Buffer.from( + "02c371793f2e19d1652408efef67704a2e9953a43a9dd54360d56fc93277a5667d", + "hex" + ), + { compressed: false, network: bitcoin } +); + +console.log( + extractRSFromSignature( + "30450221008f619822a97841ffd26eee942d41c1c4704022af2dd42600f006336ce686353a0220659476204210b21d605baab00bef7005ff30e878e911dc99413edb6c1e022acd01" + ) +); + +const res = pubkey.verify( + Buffer.from(hash, "hex"), + Buffer.from( + extractRSFromSignature( + //extract r, s from DER encoded ECDSA signature + "30450221008f619822a97841ffd26eee942d41c1c4704022af2dd42600f006336ce686353a0220659476204210b21d605baab00bef7005ff30e878e911dc99413edb6c1e022acd01" + ), + "hex" + ) +); + +console.log(res); + +// console.log( +// reversify("b4948747cc3ddbc03e016c43d82087bf3fff63b856e887561005ec1acd2eb290") +// ); + +// console.log(res); + +// p2pkh +//02000000|01|25c9f7c56ab4b9c358cb159175de542b41c7d38bf862a045fa5da51979e37ffb|01000000|6b|4830450221008f619822a97841ffd26eee942d41c1c4704022af2dd42600f006336ce686353a0220659476204210b21d605baab00bef7005ff30e878e911dc99413edb6c1e022acd012102c371793f2e19d1652408efef67704a2e9953a43a9dd54360d56fc93277a5667d|ffffffff|02|54e8050000000000|19|76a9141ef7874d338d24ecf6577e6eadeeee6cd579c67188ac|c891000000000000|19|76a9142e391b6c47778d35586b1f4154cbc6b06dc9840c88ac|00000000| +//p2wpkh +//version|witness|numInputs| input tx hash 1 | input tx index 1 | scriptSig size | sequence|inputTxHash2 |outputIndex2|scriptSig2|sequence2|num outputs| output value | output scriptpubkey size | output scriptpubkey +//01000000|0001|02|de4879b9137defa55479f365334d67e59ef146c0146a71e70161ef7e7cc65fb4|01000000|00|ffffffff|e7b2f24ec6fde8d97cab15e885e68b45899a06ba4385c753793ed6ceb3771d56|00000000|00|ffffffff|02|a0b67600|0000000022|0020e84d3f6e5cbdc0edf746db92890753c7cbb0a5d56e046be6015819f3b4bd8abc|552a|00000000000016|0014a8f98809869fce19104d18efad1273e891973687|02|47|304402203e6a1971bbf6c42136fc8dfcf11efa115472ae063d0345f446b6383c2f17380b02204b126ed182a51bb32f90276fea4c7c0b9a9ba22df5d2fde7d3c45c0b5542265601|21|02e7d637bf72bdd26390a93535e7f70d0dc3091fc0768ac973d82d94fa09c48da5|02|47|3044022072f357d0ab64f4c828579eb1e8626d9b938b3d2948766f94add00859338bcd1a02200a52417f8b556154a5112ab4445e42b0e28a64e09fe6bdea2712cb59e447056f01|21|02e7d637bf72bdd26390a93535e7f70d0dc3091fc0768ac973d82d94fa09c48da5|00000000 +//p2wsh +//02000000|0001|01|30d1c40780728b4e30c53333fa7e38bd8ebed021c05ec135592ad17a078513a5|00000000|00|fdffffff|03|43721600|0000000022|0020d333523199087a0d8faad8667ba5540d93b7041ce0af2f10248565edb86a55fc|52e86d02|0000000022|0020af9746c91370a2132c85fad6641650e5c160f3522b748b15120d03695dcfd4c3|6e8cf000|0000000016|0014bb9daf27204b53a37612eace73520b0774c119f8|0548|3045022100f9beb9585cc5b8487264898c532c10c862d24b98a53688ddf2b6800bb049d6b2022001d3ce09fa560efafe553214356151dde3046463214939b34cf27500969c83fd01|21|030681010e3431c0a31da520cf46a2b2c1d645a0d486918139f7e3f8c7fda677ab|20|1769d10a9a7e8506e14edc4801e40cc24699ec4d4086ddcc186e33fe458470c6|01|01|5b|63a820406af310092ba4cc3d12b65d573c0acd25d63a97905c8cf38a77b524dee657e88876a914bb9daf27204b53a37612eace73520b0774c119f867022001b27576a9142c3debdb231b31b42a8877fc3b16de014b13f10b6888ac|00000000 +//01000000000102 +// input txhash1: de4879b9137defa55479f365334d67e59ef146c0146a71e70161ef7e7cc65fb4 + +// const j = +// "01000000000101bb3b4760944489ec9bed5d5fb002f33b3dda278784d7faef371067e518c97d3b1000000000ffffffff02a0860100000000001976a9146085312a9c500ff9cc35b571b0a1e5efb7fb9f1688ac163d340200000000160014ad4cc1cc859c57477bf90d0f944360d90a3998bf024730440220780ad409b4d13eb1882aaf2e7a53a206734aa302279d6859e254a7f0a7633556022011fd0cbdf5d4374513ef60f850b7059c6a093ab9e46beb002505b7cba0623cf30121022bf8c45da789f695d59f93983c813ec205203056e19ec5d3fbefa809af67e2ec00000000"; +// const a = +// "01000000000101bb3b4760944489ec9bed5d5fb002f33b3dda278784d7faef371067e518c97d3b1000000000ffffffff02a0860100000000001976a9146085312a9c500ff9cc35b571b0a1e5efb7fb9f1688ac163d340200000000160014ad4cc1cc859c57477bf90d0f944360d90a3998bf028e30440220780ad409b4d13eb1882aaf2e7a53a206734aa302279d6859e254a7f0a7633556022011fd0cbdf5d4374513ef60f850b7059c6a093ab9e46beb002505b7cba0623cf30142022bf8c45da789f695d59f93983c813ec205203056e19ec5d3fbefa809af67e2ec00000000"; + +// for (let i = 0; i < j.length; i++) { +// if (j[i] !== a[i]) { +// console.log(a.slice(i)); +// console.log(i); +// } +// } diff --git a/src/features/encodingSandbox/p2sh.ts b/src/features/encodingSandbox/p2sh.ts new file mode 100644 index 0000000..77aa4f6 --- /dev/null +++ b/src/features/encodingSandbox/p2sh.ts @@ -0,0 +1,38 @@ +import * as bitcoin from "bitcoinjs-lib"; + +import { alice } from "./wallets.json"; +import { text } from "stream/consumers"; +import { reversify } from "../../utils"; + +const network = bitcoin.networks.regtest; + +const redeemScript = bitcoin.script.compile([ + bitcoin.opcodes.OP_ADD, + bitcoin.opcodes.OP_5, + bitcoin.opcodes.OP_EQUAL, +]); +const p2sh = bitcoin.payments.p2sh({ + redeem: { output: redeemScript, network }, + network, +}); + +const transaction = new bitcoin.Transaction(); +transaction.version = 2; +transaction.addInput( + Buffer.from( + reversify( + "82f39b0d951d2a604568eabe47d6013a4d83dcf9c476c30c4d3ffafb72fbb21f" + ), + "hex" + ), + 0 +); + +transaction.addOutput( + bitcoin.address.toOutputScript(alice[1].p2wpkh!, network), + 5000 +); + +const sighash = bitcoin.Transaction.SIGHASH_ALL; + +// transaction.hashForSignature(); diff --git a/src/features/encodingSandbox/wallets.json b/src/features/encodingSandbox/wallets.json new file mode 100644 index 0000000..02fdf6d --- /dev/null +++ b/src/features/encodingSandbox/wallets.json @@ -0,0 +1,314 @@ +{ + "alice": [ + { + "entropy": "182301471f6892728ae56bb95b54396e", + "mnemonic": "blouse blossom fade disagree matrix deer clog pulp rich survey atom tackle", + "seed": "91a60250b6a45c07cdb3e0394cc2ebd1bb91bd2628c586a3131ead9c8c0d407ba06c8b4bea6a6a475e06ea312241de050fc9c88b651ee3ddd5e1379de97b1f32", + "xprivMaster": "tprv8ZgxMBicQKsPemXDEPPcJwAkrgszB75x5Lv99sqqifnLgcqrHBoXrTKbeQAMncrUwV6xwqZmhJXzKzQZuqcETEE2sdvNKYLQnZAKAGp7CRs", + "privKeyMaster": "3db7641394f017746d9f38125eb1e84b6ee8db0e1939949e97802904fc72bdb0", + "wifMaster": "cPefpopKGSrh6jh7scQt8jfq9CEWCNGhruNZ61R5BGqaqVAtgVp6", + "xpubMaster": "tpubD6NzVbkrYhZ4YEZ1834CiLpsRiPvLSGreeWvSPt98wajX76cuad82wwTpZWRn74XpBWWG8s1F2xwjy4d8fZbRE8NrTK4n26a5uChTHhgpf2", + "pubKeyMaster": "0379b9bbb9fff8a286dddcda0ec3fc1172b3320f0a8233032d3071ea70bfab95c2", + "pubKeyFingerprintMaster": "96a1f656" + }, + { + "xpriv": "tprv8fdMUVqCQ5pAQLj3ReLEQevoNeni1eAqCTrbgLKE1cJt7iN3baed3DmGWpR7NRssvdBE8qqQVLVGaWaZ37A4GCvCzrtQzuVjMqT7dydZf7C", + "privKey": "4dce80aad60aee8ea17c307ae00baa6cff261b780af68b86eedae36ba657dd81", + "wif": "cQBwuzEBYQrbWKFZZFpgitRpdDDxUrT1nzvhDWhxMmFtWdRnrCSm", + "xpub": "tpubDCKPcusSYTVqHokqKHzpp4auwgJeAyMjmmTNxrMXRt7GxCcpDyUDDiP8gybjM2kdkZAqh4z9n7QRDeK8mpsmKymY59KLrg9JPbyjTMfTJQY", + "pubKey": "03745c9aceb84dcdeddf2c3cdc1edb0b0b5af2f9bf85612d73fa6394758eaee35d", + "pubKeyHash": "fb8820f35effa054399540b8ca86040d8ddaa4d5", + "pubKeyFingerprint": "fb8820f3", + "p2pkh": "n4SvybJicv79X1Uc4o3fYXWGwXadA53FSq", + "p2sh-p2wpkh": "2MzFvFvnhFskGnVQpUr1ZPr4wYWLwf211s6", + "p2wpkh": "bcrt1qlwyzpu67l7s9gwv4gzuv4psypkxa4fx4ggs05g", + "path": "m/0'/0'/0'" + }, + { + "xpriv": "tprv8fdMUVqCQ5pAUFBkXM8X4JqeSDhTi1mrmKQcxZW1XeGCfqqhREeWZXP2NoMrjYcfqtZWGTdrLTAxNBhRH7kHYuMuvn2W7kR8sm5b5fyXU9B", + "privKey": "19ce51bca110b5790a1d1abc93783a3694c00e5c5148620cd6f27fac611c3f09", + "wif": "cNSs88aArgZeHrq7wpq8B56k1LGi4BVbSHqzYDic9ztju6TruHM5", + "xpub": "tpubDCKPcusSYTVqMiDYQzo7TiVm1FDPsLxmLd1QF5YJwv4bWL6U3dU6k1ztYwTsNzCkqaxkHmLsnGY25JoLgzsyzcUN77RBMLgjXGnGMmtV3ye", + "pubKey": "02b233055e060a554201d227a0b9d019cf24468ab1a7d9446c423a198cfbf956d5", + "pubKeyHash": "0b85d9b75f55ad9d42ea2ae9a245568ca34932f6", + "pubKeyFingerprint": "0b85d9b7", + "p2pkh": "mgZt5Fqzszdwf8hDgZt3mUf7js611aKRPc", + "p2sh-p2wpkh": "2N95xbaytPcRBwi2HmUTumE7YSfLBk8po99", + "p2wpkh": "bcrt1qpwzand6l2kke6sh29t56y32k3j35jvhkrn9s5l", + "path": "m/0'/0'/1'" + }, + { + "xpriv": "tprv8fdMUVqCQ5pAVpmNgQycAZx5WdrxPqVbNaji2Sos97Yg2WE1WWGMGFtBQisYYVxfLN4jGFpFrFLCcoA9bECvkrL8Zb2Ymg2sNA6tTup1mp5", + "privKey": "b9ede4ac299cb7473f2516ed2dbc1a3ec6e8cf9cdadeac00c938fb174d6662d5", + "wif": "cTp88sXB3ZDrWNnD1BtShjREFrw8eCs87bZncrZitryhFbiT2mmQ", + "xpub": "tpubDCKPcusSYTVqPHoAa4eCZycC5fNtZAgVwtLVJxrAZPM4rzUn8u5wSkW3apf4pKfn7ABiEc7e3hwKEiJMpDHq9v8qHRXL1xzHi6bS1KoGeJg", + "pubKey": "0220f2d0315bf811009bf82069dc6be7225cab598d0f3504695539d631711b6252", + "pubKeyHash": "f1c63bbd3d0a20865c43406fcfc09ad953b23aa0", + "pubKeyFingerprint": "f1c63bbd", + "p2pkh": "n3ZLcnCtfRucM4WLnXqukm9bTdb1PWeETk", + "p2sh-p2wpkh": "2NC9jSfJyLLAKQgzxSJ7QGrq4X3Wgh34kCb", + "p2wpkh": "bcrt1q78rrh0fapgsgvhzrgphulsy6m9fmyw4q2ehuh6", + "path": "m/0'/0'/2'" + } + ], + "bob": [ + { + "entropy": "28c8b37e1462a460fafa440d3ec66d29", + "mnemonic": "churn easily test churn clean corn typical embrace artwork wage opera fame", + "seed": "756cc32ffb263732758726f6c1e5a0b3a2a7aa76556567946f0277634f6f9184f33805a1bacc067089c831ee9c6742d8d391a1c08129775756afc36c3b8767c2", + "xprivMaster": "tprv8ZgxMBicQKsPe58FjHQLEzn1FBrxyHPUhQtpayjMWssUfraCxP5GbAcZdwnPfKkQcSA3jWSCGC1WH5An9piEJho6cpRXS9fAWsXHUMYaQzA", + "privKeyMaster": "e5ae5079523415a4290b0c02615732a50fdf84f8d1100bf2f5c8f02e1de6cce0", + "wifMaster": "cVHAtbY8yTpRaFsPwdvRtNMNeTmVQP7LLW7FD53Mbjan4c55oS62", + "xpubMaster": "tpubD6NzVbkrYhZ4XYA3cw4veQS7pDNu8caPGiVbsVmew9fsWLpyamtrmfERp5nTQkGidxAEF5aqGX7QVr7RtQUuUMMNznzdmY7zGmtpaCaKtxP", + "pubKeyMaster": "037092709cc49343652384627fee5c9db3c3680d699d989dfb46a6fd28a1ff8fb2", + "pubKeyFingerprintMaster": "42d18d1f" + }, + { + "xpriv": "tprv8h4mmmehwB5GKzoVGQxqnDNGqbqgsXga2hh4iQ7MmiHA5bd6qDWzqAzReUe3mcH4eHRLcuF7YmHz8ctGpc69wcZL7QoXwjAEc9GRLfWeFZt", + "privKey": "1d9944b5497b6b9ac6f52ac3bc077bc01610b8401d799eaa35e83b44ed858139", + "wif": "cNaEjitvA19JZxWAFyCFMsm16TvGEmVAW3AkPnVr8E9vgwdZWMGV", + "xpub": "tpubDDkovBgx5YkwDTqHA4dSBd2PQdMd2rsUc1Hqzv9fBz5Yv5ssTcLb1fcHpcKqHUpG9cHMZDSpeWcq3XJLvHDJeW91aFuuR3y96dHKZkdw1X2", + "pubKey": "027efbabf425077cdbceb73f6681c7ebe2ade74a65ea57ebcf0c42364d3822c590", + "pubKeyHash": "03f338e0f898ef40f1eb6734dc1f5aa72a4161f8", + "pubKeyFingerprint": "03f338e0", + "p2pkh": "mfsqh18UT3XjpJ8yVeiM7YX1mLxq5REG9d", + "p2sh-p2wpkh": "2N8QRLVT9VTa5ryoqSbGhGqq1x3FKp9SfoJ", + "p2wpkh": "bcrt1qq0en3c8cnrh5pu0tvu6dc8665u4yzc0cw9nweq", + "path": "m/0'/0'/0'" + }, + { + "xpriv": "tprv8h4mmmehwB5GNHiAy8Q4xn7rTT67amTTHdsm3myMcPhXvxcnapcHNhKAWVPmxvAVLs2TFQUQSjw3vzHiVMp1gmb9GPohqAgPJZa9vp97HV5", + "privKey": "03993d9ca5b282eea78ac41afa713be51dad940b9976c073ab190ee80d0a239d", + "wif": "cMhhNRoKiJP37jwwDjoXBhMRk8aEiTTe7fLbFNUc3HwD4jLciNfd", + "xpub": "tpubDDkovBgx5YkwFkjxrn4fNBmy2Uc3k6eMrwUYLJ1f2fVvmSsZDDRsZBw2gdxPy54XpMUPCKruAaUsGa29X16mcYX6n7aK1uvK37EHUpd7cCb", + "pubKey": "02d8698e38dc413e175c60290b223c0cf175d15f22c889bc98849e81548e1a3168", + "pubKeyHash": "1bf41b0d5729e00b36bf0237a84d0b9746fcc1cd", + "pubKeyFingerprint": "1bf41b0d", + "p2pkh": "mi4kxzYMgjqdqjvfLkHGPi7R1ZrLMx2BH7", + "p2sh-p2wpkh": "2N6j5eEZzeouYMkHLXU5vU4BorXPhAR43NZ", + "p2wpkh": "bcrt1qr06pkr2h98sqkd4lqgm6sngtjar0eswdrpttw7", + "path": "m/0'/0'/1'" + }, + { + "xpriv": "tprv8h4mmmehwB5GS36HRJyanaa9Z7DLVry1Z8sp9vDett8df4JiNVUR3oa6uu4S1LSjXmMWHcEbCGYmtddYFyZzTuAa2ofRSm1cWcdZqhNca7w", + "privKey": "9d1dc4bd64a510f1ec30ce1712bc50b2824eabfcae0fcd79976b2110df7ddfbd", + "wif": "cSr7dfYATGQzMaS66caTGX3n2owEr5YcMUdmHSc3prj8zfusFz4s", + "xpub": "tpubDDkovBgx5YkwKW85JxeBBzEG88jGfC9v8SUbSSFxK9w2VYZUztJ1EJBy62ptW9urcCfT74q2kKyBMvoejdks1EzRvZHdYzxbrC8ATcXimvk", + "pubKey": "03091bf7a5f90630c7c6c10d68d5555917668d307565ed7ba458e69d137b33f86b", + "pubKeyHash": "8236b4b6e79b48df5ca2617c6bb73fa281a83e07", + "pubKeyFingerprint": "8236b4b6", + "p2pkh": "msPTg2SY8YasMCcCxgA4LocqdxP9SDz8uu", + "p2sh-p2wpkh": "2NEBdHDRNphUosaHENxyxs3Qp498NHgVtUz", + "p2wpkh": "bcrt1qsgmtfdh8ndyd7h9zv97xhdel52q6s0s8597nzc", + "path": "m/0'/0'/2'" + } + ], + "carol": [ + { + "entropy": "61628dbe355f4675d895d399b984aaf4", + "mnemonic": "gesture behave hurdle height virtual depend girl risk often slow click trial", + "seed": "2c335b239ca3f9f1b4ca0a3026832214ca6209877009e18b90ab5b9f54706161e3109e98b46fc2a78984503467c1899ae0690cfd89112ed0cee5f9b9c7ab84c5", + "xprivMaster": "tprv8ZgxMBicQKsPeDhXotfwyNcbMKXGkqErJZ5eDgsfSpJ31fgry34VBqjnyCSVgRkGHms78MN3EHqp91s2Crzr4aFHm2taEiKWRQk6jAde4Zm", + "privKeyMaster": "2d7d443d24cfe1c5c86669dd0ae28c63812acfc1d9ff08943ae5ecde66f96135", + "wifMaster": "cP78KGzMDDqXcapTjFjakbBAqgyW6q1T8GgTMCbcd22mDS6WNrS3", + "xpubMaster": "tpubD6NzVbkrYhZ4XgjKhYLYNnGhvM3CvARksrgRWCuxs66Rr9wdbRt5NLMf9NcQBbrjbbVYUKNrQwtikYwDMsNGxerTcSrJEcS5sehkxzZJd9m", + "pubKeyMaster": "03d61decee15d4cdae07e0c665cf26e54846272d0791a953dfd426f607280bcfe4", + "pubKeyFingerprintMaster": "8d3c2e13" + }, + { + "xpriv": "tprv8gwH3MfJRRBk2RD4WUeMz5aYttG3NQpCL5W4a97pdyQW5DTCukxA9Bi21vvaB1ys1t89ywak4CdKzzRRbxPhwmkQZHiVkJFmNb5L9bDdG7Y", + "privKey": "5dd33245e949acf42a2d066a498a4060faf87a154d8889a3fbedcf6df2f10ef8", + "wif": "cQj5thuudcupEQo5WTYqVzs6ZtPGkdfCepNUpgJe6VAFeKFfhMtW", + "xpub": "tpubDDdKBmhYZnsQutErQ8JxPVEfTumyXk16uP6qrfA84FCtuhhyY9mkKgKtC3biozuUVov2GXNszmKPVMTbg7ftPY3RBhDw897S7eHrDjzX4vJ", + "pubKey": "023a11cfcedb993ff2e7523f92e359c4454072a66d42e8b74b4b27a8a1258abddd", + "pubKeyHash": "1053863792b523fff2252ea687da9ebcd402ead1", + "pubKeyFingerprint": "10538637", + "p2pkh": "mh1HAVWhKkzcvF41MNRKfakVvPV2sfaf3R", + "p2sh-p2wpkh": "2MyfuZkCtYeSEA4LjJAkkxW6zxdyhKnVyBX", + "p2wpkh": "bcrt1qzpfcvdujk53llu3996ng0k57hn2q96k3dwmytw", + "path": "m/0'/0'/0'" + }, + { + "xpriv": "tprv8gwH3MfJRRBk3m5Ym1CCMg8ixcr2CvBQsFPwPgK4rbKHLPAR5g32WFoeAxuMiAWfjff7e7651QB4ZHSaSVLYGQ7P1g3nVtqemqmvnsnNDQ6", + "privKey": "2b8fba1aa3e324adda8f828aeafb4e5197090deb8b7fdf6f528688a832d80158", + "wif": "cP3NxUDcpQGmSEftJ9VVznpMXh8AHkyj9qczSUz2HHjweK2PjcsJ", + "xpub": "tpubDDdKBmhYZnsQwE7Leernm5nqXeMxNFNKSYzigCMNGs7gAsRBi4rcgkRWM9C5zPjEv4JMwNwV5dCCkK56vUo7a7tBcEJ6pBjYDC4EHLYp5Dm", + "pubKey": "03e3aee22cc50638083afa8bd60da82cc351588619610566ca28cc50620e3f8cd9", + "pubKeyHash": "ed67278516aa8e224479150d8648ca2e0a2c0e9a", + "pubKeyFingerprint": "ed672785", + "p2pkh": "n3AE1ou9qaJvM8yyhEKg8ktDKYY2aJbGcK", + "p2sh-p2wpkh": "2MttMce5bYsZu22bFvXCKcosef9aJ4Bg36s", + "p2wpkh": "bcrt1qa4nj0pgk428zy3rez5xcvjx29c9zcr5654qqzm", + "path": "m/0'/0'/1'" + }, + { + "xpriv": "tprv8gwH3MfJRRBk7Lo7UufdydnNpj1HTQtEAEoG8DWhnakNEkwGLzEmp8Hcev2nFbWgNGj4iDE676VgCHnbi6rzmgfJky8aqm6uXJXnvFbGRsm", + "privKey": "5ec8abddc483200a96ac9f909d30f19fb7a258548888fdc45b85f1840cd8e762", + "wif": "cQkx12ymQJW4yiqRUefqFrYZR6oxHrNBoD7SBcuLTRTRAneFKgTH", + "xpub": "tpubDDdKBmhYZnsQzopuNZLEP3SVPkXDck58jYQ3QjZ1CrYm5FC2yP4MzcuUq5sGGaui58tvE7D7mFyYuKPdgUsNTiVWdoFT9Tg2nMVqUEN8Kdf", + "pubKey": "03db4f9fadddee79aa79591a55548bbb74018ec82460718bba4e46746d5b5e4d32", + "pubKeyHash": "9c936f4d8104d3fe2749497d53e01eee9e074bd3", + "pubKeyFingerprint": "9c936f4d", + "p2pkh": "munrKvk5qGsG63be82rBk34hJqqSnSHWEk", + "p2sh-p2wpkh": "2Mt8ZpgtsnRnY3Lxed4KTExCvJxgePYwuPy", + "p2wpkh": "bcrt1qnjfk7nvpqnfluf6ff9748cq7a60qwj7n99m3l6", + "path": "m/0'/0'/2'" + } + ], + "dave": [ + { + "entropy": "6dc790b775c765abbbd981c0cdbbce9e", + "mnemonic": "horse develop column twist iron still urge coral school horse victory differ", + "seed": "68dff1e20671ca63775bbbb4a939394044ba598c844df10e8e2ed1ecd8f3816f1aee229ab4c53abb59606670c17e1fe920e95dae002b1c6204679ca6842776bf", + "xprivMaster": "tprv8ZgxMBicQKsPdqaJLU6VZJ5jDnsQYjY1yzgWovRFziENRjq4YmwNuTTgGrxLivQmTRbVTpNdgyNMwaE2C8DM4D87RGiPkwQy1jkEWN25GzG", + "privKeyMaster": "d981358d619effdf2f11580f8a4f9b543b8d6b17d4a32dc866d576b2d4dc0a52", + "wifMaster": "cUsW6L1KQiZMLdvVEf9P7etAzTmPrAoQAwfNWCzmQoCRYEPoxjur", + "xpubMaster": "tpubD6NzVbkrYhZ4XJc6E7m5xhjqnpPLi4ivZJHJ6STZQz2mGE5qBAky5x5YT1wm7MXDUjUm4bRWdsWnV35gikapLbQaczSbh9MGYEXft8VLrhq", + "pubKeyMaster": "03e6a1179354dfb1e938e7fdadcb20fa4cd36fe726fe31e723db6197f250699a52", + "pubKeyFingerprintMaster": "56079739" + }, + { + "xpriv": "tprv8g4usdMN5GeR2oru9nZjYXAqNCCG42jt4aYpNLfY2qYFQBUQFnr83KLVjPKvy7Tjfao1Fytb61eJvMbrhF9Z3g14bZNzR84NmB2xLhs7ynE", + "privKey": "22958827ca160b69dc183eb77c340cc9ce3e72045954fd16ad2ffe54979940e9", + "wif": "cNjvpAG2cEqnD8WUb9mpGyqYAczBKHZR5Gj7hWPd9CJGxVoWU1K1", + "xpub": "tpubDCkx23PcDeL5vGth3SEKwvpwwDiCDMvndt9berhqT7LeEfjAtBfiDoxMuXnaZQyTyEtVWPJFmhqgEZzg1Me5Fbrbpz7WyBCibmZUxRJWjXc", + "pubKey": "02e9d617f38f8c3ab9a6bde36ce991bafb295d7adba457699f8620c8160ec9e87a", + "pubKeyHash": "9838d506490d8039a446256c1a20a719d873e9c8", + "pubKeyFingerprint": "9838d506", + "p2pkh": "muPq5yf84ot8JE7FWvaXfqG3YZGpZx9Zg7", + "p2sh-p2wpkh": "2NASxJxnyuV5NgurasHoa6bfAfE8aZwz41T", + "p2wpkh": "bcrt1qnqud2pjfpkqrnfzxy4kp5g98r8v886wgvs9e7r", + "path": "m/0'/0'/0'" + }, + { + "xpriv": "tprv8g4usdMN5GeR5EXkQ2MyjEyQo5DUayML679ctea3regfe8qdUAom2kuHsTwFwaKcNT3n6rvSXrLsuNgtrmSBKEHqBdjS3pFaFfPnevVTi1N", + "privKey": "7e1d9149e1d3e00659f2977a91d9b16fd17e5af51c3b00327ffb2ee8462cd80b", + "wif": "cRorU4b9uwAYuBMdHtLYrfX73hbYGPdedgMQHUJ8Vffz53hDmxn1", + "xpub": "tpubDCkx23PcDeL5xhZYHg2a8edXN6jQkJYEfQkQBAcMGvV4Ud6Q6ZdMDFXA3d8NLQvRdypwYJT2NS7LZFtU8gzrSQqxu4p6bkkrhf2ubHyLBHg", + "pubKey": "03a5c879a6456564f3a09374ab8027e3014517a1a54bceeddc3e29916602b2932b", + "pubKeyHash": "6fccd5594854cca3368a2ebc171a0cdd5a79c7b8", + "pubKeyFingerprint": "6fccd559", + "p2pkh": "mqi6gRN8Faaaf6C9YGgKXXNDEuFiRNTCUM", + "p2sh-p2wpkh": "2MtjZ41f2ELewgXu1FvY4cxEV4ezEU2Y6E7", + "p2wpkh": "bcrt1qdlxd2k2g2nx2xd52967pwxsvm4d8n3acvwg04s", + "path": "m/0'/0'/1'" + }, + { + "xpriv": "tprv8g4usdMN5GeR6bJZq18bgk8BgNqmDQvSwdzQP3wcjwvuq1ktWQxip3RjAsoKty8SWCCgGFk8nadJrGd5nfHWicpCQKRTJMorRXrPWHREz1Z", + "privKey": "543af9d237d3b71d7dd20390d372914c6416709b094bf93849942631814edbfa", + "wif": "cQQS9rEkW5Gi3TWCysdGGaKSZdQKR7qbBStxQtcA8mb7xipGGq45", + "xpub": "tpubDCkx23PcDeL5z4LMieoC69nJFQMhNk7MWwbBfZyvADjJfW1f8onJzY3bLzgVByZQFtQS1dCvALAKqpybK51thXYyKWTxgHT1T9sAKmiFBXx", + "pubKey": "024bbf83821c4102c4bc41846ef02434b78e37a448e8c55a3e6ef0ad701258a755", + "pubKeyHash": "413bf36c4f490f9b86b59a79616b291617db57cd", + "pubKeyFingerprint": "413bf36c", + "p2pkh": "mmTt2ywcvRo91RvutobovP9Q8YZsmpoMTi", + "p2sh-p2wpkh": "2Mw9fS8Zghno4StubxM3X7oQBFkGAg76KrR", + "p2wpkh": "bcrt1qgyalxmz0fy8ehp44nfukz6efzctak47dyxlcv5", + "path": "m/0'/0'/2'" + } + ], + "eve": [ + { + "entropy": "d8ec0331d6228a59b17cb412700761f0", + "mnemonic": "suggest gas small proof chunk coast shine notable bar lens such theme", + "seed": "ae4a2eda97c884469cb593568e2627e47de956bc67e0ba01362cb751c9cfca43477c303063127092f8dae8224a3decc48fa7649a2dae20068103d0aaa456cb09", + "xprivMaster": "tprv8ZgxMBicQKsPf97Q1kY8KhHSLtot3X8mUfoPy4EGjKnRCdGrCk9njeiaSFDXi2Y1JhsyoqReUCM77x4aVNCestM2zEhtGDKyk8CDJyBJ9D8", + "privKeyMaster": "aee3418891a442e70aa994cc1a53cbbcb4871f69ea859539d3ecfd0b079a2da5", + "wifMaster": "cTSfGDXQ27akHj1uUh7omWeWLamhZtiDSNNYg3p8oxwrJi9X4QjA", + "xpubMaster": "tpubD6NzVbkrYhZ4Yc9BuQCij6wYuvKpCrKg3yQBFaGa9bap37Xcq8yNv9LScPDknnEECW7uNDbDxnx1ddnXJqrCd3Rdw6u2Thm5weY4ffZXbaM", + "pubKeyMaster": "033a24eb95576c816e0c7dc1cef571bd9dd2fac8bc11ab3cdba0da49c2cf9070e2", + "pubKeyFingerprintMaster": "8be2c68c" + }, + { + "xpriv": "tprv8gi1x9A98qduZExYc2cwtUtxDAuuRjF5CmR2sWUP73UzcnPLpAmWaFZD85bYvPuhwCwHucydzniA1U6Cmr6XQyVyFEnBhFnYQCS3ignKnPv", + "privKey": "be1cb1b26446b12d82eb0dfa5c3479d75f3b60922baf200d4ca9f2f8ea9349fb", + "wif": "cTxFj3T9nfEjCM21WdYz67yU3FwyptYkXVuznvUS54admvLuNQ6C", + "xpub": "tpubDDQ46ZCPHDKaShzLVgHYHtZ4nCRqb4Ryn51pA2WgXKHPTGe7SZb6kkB5JDh5roQrcGoB43qLnQNhomvzA5u1qtzWLCjH3ebRbEG3Nio4iQn", + "pubKey": "03556bb5a184b64757b70e54c6f067420709971ab4178aebffa338bf6a1db2e3ab", + "pubKeyHash": "6e8f348d0aa3a3ee16440b9496f3d3c0ccf66f6b", + "pubKeyFingerprint": "6e8f348d", + "p2pkh": "mqbYBESF4bib4VTmsqe6twxMDKtVpeeJpt", + "p2sh-p2wpkh": "2N5AemJdXmQbLwF1ndhBtLRocE1UhP2Ja9M", + "p2wpkh": "bcrt1qd68nfrg25w37u9jypw2fdu7ncrx0vmmtwgeht9", + "path": "m/0'/0'/0'" + }, + { + "xpriv": "tprv8gi1x9A98qdubPYqD8DBMZvj5eTUDb6hT7CCWkWFeDfovcWitrN1Fo9vg5HZGNpYeTKyRV2aWk9XJJLipQD4rgi2XAXJbuXAUBDRvRxyfg2", + "privKey": "db1bcd042e7bbb6d05e6babe0c21d0220f0478838a45e1521575b34829653920", + "wif": "cUvcvLkMU6RsTkJNr7JE1rrVPaprPDQCGSELSmmUEWkxUnXUBcHZ", + "xpub": "tpubDDQ46ZCPHDKaUrad6msmkyaqefyQNvHc2QnyoGYZ4VUCm6mVXFBbSHmnrBGbMWcix5hRjYQLg3BEtHTqTZGzFCzez5wbjtGWD6KpP9r9n3R", + "pubKey": "025c459bb32a91d73cad6923de83b7a67887516ce37d7b00947cdeb0c1218d6953", + "pubKeyHash": "40f4168cf1d7aa67906875f089f1b3ada928fc03", + "pubKeyFingerprint": "40f4168c", + "p2pkh": "mmSPwsM5Hb7fp8qoXosGJSZJu1rjwh7CzR", + "p2sh-p2wpkh": "2Mv9PBWD2aeN69TrgTg8ebTKEoAXRtaTdzV", + "p2wpkh": "bcrt1qgr6pdr83674x0yrgwhcgnudn4k5j3lqrseu8hw", + "path": "m/0'/0'/1'" + }, + { + "xpriv": "tprv8gi1x9A98qdud5xGGCSCg1gwaaXi7zyPBPj4xTNGG1xcAeVAbWBxRqEoaDGRJSjZ9ZQ6i7jc8Wx8qR3LEUpiy4krtDNCue1idSkXC36vXvi", + "privKey": "4ba20aa8ebc34a2ac221b910d14e333f0cdd727d9583ad6e83be14b61d49111f", + "wif": "cQ7iqwQsmNYL4SefBytLZ7pG54PwZrtnXVqXcpaarK6XRf1mspRb", + "xpub": "tpubDDQ46ZCPHDKaWYz49r6o5RM49c3eHLAHkhKrEyQZgHm118jwDu1YcKrfkLfPpvFKTx1jsKWEX837DmFUgAGggwyrPRL9aUaBzgzk84oGtko", + "pubKey": "0286d90b1ac602ebf284124fa0176805a082a96142174e873b78ed1b45de12aff1", + "pubKeyHash": "9d9d7e65ba7d3570427bcb76afcad36441d8f1d8", + "pubKeyFingerprint": "9d9d7e65", + "p2pkh": "mutM42bNg6771m5snDwtydYc8Qu8f5s1W4", + "p2sh-p2wpkh": "2Msw3vdHpm76YsqzCFbgbRka2NEecnSFHZe", + "p2wpkh": "bcrt1qnkwhued6056hqsnmedm2ljknv3qa3uwcdzza8r", + "path": "m/0'/0'/2'" + } + ], + "mallory": [ + { + "entropy": "6af8462dd020ff7e2239a0a346d27448", + "mnemonic": "helmet season merge park avocado sample material cross person custom other mountain", + "seed": "9920708cb2a882ac9311183ee34b0d8ec502a05204ab3027cfea6495ff08184fbaa353a8268e3ba06643eab9bf81f023819d9ed5209f412eb2f17adb5a0bfaaa", + "xprivMaster": "tprv8ZgxMBicQKsPfMQxJYYS6L3m1mXgAPQuUfhZVuseZgHKgWcSAgLqUJsuXMR9CxNCnajd7JvtfK9yFoV3cbr5uS4XMVKd2BGxccyN5kRctQe", + "privKeyMaster": "c113fb4b0f3ae465a5981759821dbcca81cb7bf654e2483741d2d820bd5be421", + "wifMaster": "cU4282H3GUTtCjcXG6gByehpxZQ1RdUeB9pgPedeZakYEH77AvAX", + "xpubMaster": "tpubD6NzVbkrYhZ4YpSkCCD2Vjhsao3cKibp3yJLnRuwyx5iWzsCo5AReoVmhVUqMc19owzFx453Cu2MkHZ4PRCznwvCqu6nUyr1S7KV4pxUYzb", + "pubKeyMaster": "035434c0a3de4b7ff76c8f43dbb027ff962eeb8631c9ab520edae163970bf930f5", + "pubKeyFingerprintMaster": "6d851146" + }, + { + "xpriv": "tprv8gxXKvjGCb1ufMx55dAGtijwKHVw262P22dREZ8zDjhVCCf9gNFD63ZFmM9qhVTKZza6yWxCmtErPZFgnL7K5JiQTJFEijCvCUF46MdVFVL", + "privKey": "751bd339c907e21d638e9bad793c81139a9da23ba634be94fc8b64a64f553db2", + "wif": "cRWLzy2pD7cLnERzUk1vy4ouDdjXjjVbWgUg3VZ2PatAZzgtJFPc", + "xpub": "tpubDDeZULmWLxhaYpyryGpsJ8Q3tK1sBRDHbLECX5BHe1Vt2guvJm4oGYB7wTDJorVRG3CEs5sTLMRFG8vGorSEoxkb6vhy6bccwuicePB7f5X", + "pubKey": "020055836de77cc0836ea3d713879929dd4ed4e619b92b607111c6de23c3553a73", + "pubKeyHash": "b2289f5c18e6af46b6786d9e53ed3124b36ff070", + "pubKeyFingerprint": "b2289f5c", + "p2pkh": "mwkyEhauZdkziHJijx9rwZgShr9gYi9Hkh", + "p2sh-p2wpkh": "2N7HGKwJF8R2qVCGm3NnZrnFLZzHfudQirj", + "p2wpkh": "bcrt1qkg5f7hqcu6h5ddncdk098mf3yjeklurs02ny62", + "path": "m/0'/0'/0'" + }, + { + "xpriv": "tprv8gxXKvjGCb1ufuxS6fvYRx76wiMnEufrBt186qtYqx3cdfJR5qC2E3qASHwoCUR7KvWmwMyD4GUsBdTfdYr7fQ6HsDoRkQjZepWxiTPH2fH", + "privKey": "ffdcbe4f5e291fd510e538b6bb3a6baffd773568135a3e7d58d88c3f9802d25e", + "wif": "cWA4h9h9h1rBKEkGFWD31TAWC4z5JVybBenGNZsJQBTC5wL6wDxM", + "xpub": "tpubDDeZULmWLxhaZNzDzKb8qMmDWjsiQErkmBbuPMvrGDr1U9ZBiE1cQYT2cPFH2RMDeNFwF1DTQ5FS7H8EUyhhptKUM5yW4nQ1hfoiJcHPk9R", + "pubKey": "02273553c876ea756448a71d85b52f8d10e3413202cbf60865ee078987e82a9594", + "pubKeyHash": "b7e4a67d7519e27fe055e8f510757e4579274938", + "pubKeyFingerprint": "b7e4a67d", + "p2pkh": "mxHHs4fNt1mBh4gz5MWegw4KYbGwPLXSUk", + "p2sh-p2wpkh": "2Mx5KXS8zWPdSZ85JDYeBWi2HZZ5oTpy8bT", + "p2wpkh": "bcrt1qklj2vlt4r838lcz4ar63qat7g4ujwjfcq44j54", + "path": "m/0'/0'/1'" + }, + { + "xpriv": "tprv8gxXKvjGCb1uidca4SXuUJJdK23mBBxtZUT91vGMLgJEEuxF35FHHCnCwqoFjTgL7Gb1AK8cgwCgy3TDq8oef5oDAFzssDiiVm3bDLcgLau", + "privKey": "658ef2f5f718d9a9d4218ad36e139189d682dcb680e75aedd3fd0a54d3011579", + "wif": "cQz7nqBJBK2ciUBDnwyu5Abe6RhV5o64DjfGyGeTzW1YJaS1bEUD", + "xpub": "tpubDDeZULmWLxhac6eMx6CVshxjt3ZhLX9o8n3vJSJekx6d5QD1fU4sThQ57x3YdXWyS9YVnYs4u6JajhBNQpUJRGTR8dnGaAg8F58hT6GHAs9", + "pubKey": "02095c5d50d7baf0ba1de83e2991a17462b42ea9aa996dcea97e428f23fef56d3f", + "pubKeyHash": "8a8e8595c370c147f5692bc88c61d40bae4c2df5", + "pubKeyFingerprint": "8a8e8595", + "p2pkh": "mt9aH7gacU77TpobHcbMtkBZZ5PmjGryvn", + "p2sh-p2wpkh": "2N8AXBf2RBTA1vBJnHphihrDF9pgAiyim3y", + "p2wpkh": "bcrt1q328gt9wrwrq50atf90ygccw5pwhyct04vdwxnw", + "path": "m/0'/0'/2'" + } + ] +} diff --git a/src/features/script/constants.ts b/src/features/script/constants.ts new file mode 100644 index 0000000..0967e76 --- /dev/null +++ b/src/features/script/constants.ts @@ -0,0 +1 @@ +export const SIGNED_INT_MAX = 0x7fffffff; diff --git a/src/features/script/error.ts b/src/features/script/error.ts new file mode 100644 index 0000000..5c6b530 --- /dev/null +++ b/src/features/script/error.ts @@ -0,0 +1,7 @@ +export const ERRORS = { + MISMATCHED_IF_ELSE: "MISMATCHED IF_ELSE", + OP_ENDIF_WITHOUT_OP_IF: "OP_ENDIF_WITHOUT_OP_IF", + OP_ELSE_WITHOUT_OP_IF: "OP_ELSE_WITHOUT_OP_IF", + VERIFY: "TOP VALUE IS FALSE", + STACK_EMPTY: "STACK IS EMPTY", +}; diff --git a/src/features/script/executor.ts b/src/features/script/executor.ts new file mode 100644 index 0000000..4e19c06 --- /dev/null +++ b/src/features/script/executor.ts @@ -0,0 +1,144 @@ +import { OP_CODES } from "./op_codes"; +import { ERRORS } from "./error"; +import { ScriptStack } from "./stack"; +import { encodeNumber, getNextNBytes, parseHex, parseNumber } from "./utils"; +import * as crypto from "crypto"; + +//executes the op_code and modifies the stack +//todo: op_return - what does it mean by invalid +//todo: arithmetic - check for varint in parsenumber as well +export const executor = ( + stack: ScriptStack, + initialScript: string +): { script: string } => { + let [opcode, script] = getNextNBytes(initialScript, 1); + if (opcode == OP_CODES.OP_0) { + stack.push("0"); + return { script }; + } else if ( + opcode >= OP_CODES.OP_PUSHBYTES_1 && + opcode <= OP_CODES.OP_PUSHBYTES_75 + ) { + let [bytes, newScript] = getNextNBytes( + script, + parseHex(opcode) - parseHex(OP_CODES.OP_PUSHBYTES_1) + 1 + ); + stack.push(bytes); + return { script: newScript }; + } else if (opcode === OP_CODES.OP_PUSHDATA1) { + let [bytesLength, newScript] = getNextNBytes(script, 1); + let [bytes, newScript2] = getNextNBytes(newScript, parseHex(bytesLength)); + if (stack.shouldExecute()) stack.push(bytes); + return { script: newScript2 }; + } else if (opcode === OP_CODES.OP_PUSHDATA2) { + let [bytesLength, newScript] = getNextNBytes(script, 2); + let [bytes, newScript2] = getNextNBytes(newScript, parseHex(bytesLength)); + if (stack.shouldExecute()) stack.push(bytes); + return { script: newScript2 }; + } else if (opcode === OP_CODES.OP_PUSHDATA4) { + let [bytesLength, newScript] = getNextNBytes(script, 4); + let [bytes, newScript2] = getNextNBytes(newScript, parseHex(bytesLength)); + if (stack.shouldExecute()) stack.push(bytes); + return { script: newScript2 }; + } else if (opcode >= OP_CODES.OP_1 && opcode <= OP_CODES.OP_16) { + if (stack.shouldExecute()) + stack.push(String(parseHex(opcode) - parseHex(OP_CODES.OP_1) + 1)); + return { script }; + } else if (opcode === OP_CODES.OP_IF) { + const condition = stack.pop() !== "0"; + stack.onIf(condition); + return { script }; + } else if (opcode === OP_CODES.OP_NOTIF) { + const condition = stack.pop() === "0"; + stack.onIf(condition); + return { script }; + } else if (opcode === OP_CODES.OP_ELSE) { + stack.onElse(); + return { script }; + } else if (opcode === OP_CODES.OP_ENDIF) { + stack.onEndIf(); + return { script }; + } else if (opcode === OP_CODES.OP_NOP) { + return { script }; + } else if (opcode === OP_CODES.OP_VERIFY) { + if (stack.shouldExecute()) { + const top = stack.pop(); + if (top === "0") throw new Error(ERRORS.VERIFY); + } + return { script }; + } else if (opcode === OP_CODES.OP_1ADD) { + if (!stack.shouldExecute()) return { script }; + const top = stack.pop(); + if (!top) throw new Error(ERRORS.STACK_EMPTY); + stack.push(encodeNumber(parseNumber(top!) + 1)); + return { script }; + } else if (opcode === OP_CODES.OP_RIPEMD160) { + if (!stack.shouldExecute()) return { script }; + const top = stack.pop(); + if (!top) throw new Error(ERRORS.STACK_EMPTY); + stack.push(crypto.createHash("ripemd160").update(top).digest("hex")); + return { script }; + } else if (opcode === OP_CODES.OP_SHA1) { + if (!stack.shouldExecute()) return { script }; + const top = stack.pop(); + if (!top) throw new Error(ERRORS.STACK_EMPTY); + stack.push(crypto.createHash("sha1").update(top).digest("hex")); + return { script }; + } else if (opcode === OP_CODES.OP_SHA256) { + if (!stack.shouldExecute()) return { script }; + const top = stack.pop(); + if (!top) throw new Error(ERRORS.STACK_EMPTY); + stack.push(crypto.createHash("sha256").update(top).digest("hex")); + return { script }; + } else if (opcode === OP_CODES.OP_HASH160) { + if (!stack.shouldExecute()) return { script }; + const top = stack.pop(); + if (!top) throw new Error(ERRORS.STACK_EMPTY); + const sha256 = crypto.createHash("sha256").update(top).digest("hex"); + stack.push(crypto.createHash("ripemd160").update(sha256).digest("hex")); + return { script }; + } else if (opcode === OP_CODES.OP_HASH256) { + if (!stack.shouldExecute()) return { script }; + const top = stack.pop(); + if (!top) throw new Error(ERRORS.STACK_EMPTY); + const sha256 = crypto.createHash("sha256").update(top).digest("hex"); + stack.push(crypto.createHash("sha256").update(sha256).digest("hex")); + return { script }; + } else { + throw new Error("not yet implemented"); + } +}; + +const test = ` + ${OP_CODES.OP_0} + ${OP_CODES.OP_NOTIF} + ${OP_CODES.OP_0} + ${OP_CODES.OP_NOTIF} + ${OP_CODES.OP_4} + ${OP_CODES.OP_ELSE} + ${OP_CODES.OP_5} + ${OP_CODES.OP_ENDIF} + ${OP_CODES.OP_ELSE} + ${OP_CODES.OP_3} + ${OP_CODES.OP_ENDIF}`.replace(/\s/g, ""); + +const testExecutor = () => { + // const stack = new ScriptStack("", ""); + // let script = test; + // console.log(script); + // while (script.length != 0) { + // const { script: newScript } = executor(stack, script); + // script = newScript; + // } + // console.log(stack.top()); + + const script = + "52210395f33d5a959556ba6b57298066baf468c2e5ab3cc58ddaf7166f057fae1655a7210246aae217f1102dde12a7e77203f7114de07f0068cfb1a3d825fff4ca2266737621028ace79c534a3b5c482b6cc446ea20d757e88c516ec054ae31c7a47863864904853ae"; + const sha256 = crypto + .createHash("sha256") + .update(Buffer.from(script, "hex")) + .digest("hex"); + console.log(sha256); +}; + +testExecutor(); diff --git a/src/features/script/op_codes.ts b/src/features/script/op_codes.ts new file mode 100644 index 0000000..bff6051 --- /dev/null +++ b/src/features/script/op_codes.ts @@ -0,0 +1,216 @@ +export enum OP_CODES { + //constants + OP_0 = "00", + OP_PUSHBYTES_1 = "01", + OP_PUSHBYTES_2 = "02", + OP_PUSHBYTES_3 = "03", + OP_PUSHBYTES_4 = "04", + OP_PUSHBYTES_5 = "05", + OP_PUSHBYTES_6 = "06", + OP_PUSHBYTES_7 = "07", + OP_PUSHBYTES_8 = "08", + OP_PUSHBYTES_9 = "09", + OP_PUSHBYTES_10 = "0a", + OP_PUSHBYTES_11 = "0b", + OP_PUSHBYTES_12 = "0c", + OP_PUSHBYTES_13 = "0d", + OP_PUSHBYTES_14 = "0e", + OP_PUSHBYTES_15 = "0f", + OP_PUSHBYTES_16 = "10", + OP_PUSHBYTES_17 = "11", + OP_PUSHBYTES_18 = "12", + OP_PUSHBYTES_19 = "13", + OP_PUSHBYTES_20 = "14", + OP_PUSHBYTES_21 = "15", + OP_PUSHBYTES_22 = "16", + OP_PUSHBYTES_23 = "17", + OP_PUSHBYTES_24 = "18", + OP_PUSHBYTES_25 = "19", + OP_PUSHBYTES_26 = "1a", + OP_PUSHBYTES_27 = "1b", + OP_PUSHBYTES_28 = "1c", + OP_PUSHBYTES_29 = "1d", + OP_PUSHBYTES_30 = "1e", + OP_PUSHBYTES_31 = "1f", + OP_PUSHBYTES_32 = "20", + OP_PUSHBYTES_33 = "21", + OP_PUSHBYTES_34 = "22", + OP_PUSHBYTES_35 = "23", + OP_PUSHBYTES_36 = "24", + OP_PUSHBYTES_37 = "25", + OP_PUSHBYTES_38 = "26", + OP_PUSHBYTES_39 = "27", + OP_PUSHBYTES_40 = "28", + OP_PUSHBYTES_41 = "29", + OP_PUSHBYTES_42 = "2a", + OP_PUSHBYTES_43 = "2b", + OP_PUSHBYTES_44 = "2c", + OP_PUSHBYTES_45 = "2d", + OP_PUSHBYTES_46 = "2e", + OP_PUSHBYTES_47 = "2f", + OP_PUSHBYTES_48 = "30", + OP_PUSHBYTES_49 = "31", + OP_PUSHBYTES_50 = "32", + OP_PUSHBYTES_51 = "33", + OP_PUSHBYTES_52 = "34", + OP_PUSHBYTES_53 = "35", + OP_PUSHBYTES_54 = "36", + OP_PUSHBYTES_55 = "37", + OP_PUSHBYTES_56 = "38", + OP_PUSHBYTES_57 = "39", + OP_PUSHBYTES_58 = "3a", + OP_PUSHBYTES_59 = "3b", + OP_PUSHBYTES_60 = "3c", + OP_PUSHBYTES_61 = "3d", + OP_PUSHBYTES_62 = "3e", + OP_PUSHBYTES_63 = "3f", + OP_PUSHBYTES_64 = "40", + OP_PUSHBYTES_65 = "41", + OP_PUSHBYTES_66 = "42", + OP_PUSHBYTES_67 = "43", + OP_PUSHBYTES_68 = "44", + OP_PUSHBYTES_69 = "45", + OP_PUSHBYTES_70 = "46", + OP_PUSHBYTES_71 = "47", + OP_PUSHBYTES_72 = "48", + OP_PUSHBYTES_73 = "49", + OP_PUSHBYTES_74 = "4a", + OP_PUSHBYTES_75 = "4b", + OP_PUSHDATA1 = "4c", + OP_PUSHDATA2 = "4d", + OP_PUSHDATA4 = "4e", + OP_1NEGATE = "4f", + OP_RESERVED = "50", //reserved + OP_1 = "51", + OP_TRUE = OP_1, + OP_2 = "52", + OP_3 = "53", + OP_4 = "54", + OP_5 = "55", + OP_6 = "56", + OP_7 = "57", + OP_8 = "58", + OP_9 = "59", + OP_10 = "5a", + OP_11 = "5b", + OP_12 = "5c", + OP_13 = "5d", + OP_14 = "5e", + OP_15 = "5f", + OP_16 = "60", + OP_PUSHNUM_1 = OP_1, + OP_PUSHNUM_2 = OP_2, + OP_PUSHNUM_3 = OP_3, + OP_PUSHNUM_4 = OP_4, + OP_PUSHNUM_5 = OP_5, + OP_PUSHNUM_6 = OP_6, + OP_PUSHNUM_7 = OP_7, + OP_PUSHNUM_8 = OP_8, + OP_PUSHNUM_9 = OP_9, + OP_PUSHNUM_10 = OP_10, + OP_PUSHNUM_11 = OP_11, + OP_PUSHNUM_12 = OP_12, + OP_PUSHNUM_13 = OP_13, + OP_PUSHNUM_14 = OP_14, + OP_PUSHNUM_15 = OP_15, + OP_PUSHNUM_16 = OP_16, + //flow control + OP_NOP = "61", + OP_VER = "62", //reserved + OP_IF = "63", + OP_NOTIF = "64", + OP_VERIF = "65", //reserved + OP_VERNOTIF = "66", //reserved + OP_ELSE = "67", + OP_ENDIF = "68", + OP_VERIFY = "69", + OP_RETURN = "6a", + // stack + OP_TOALTSTACK = "6b", + OP_FROMALTSTACK = "6c", + OP_2DROP = "6d", + OP_2DUP = "6e", + OP_3DUP = "6f", + OP_2OVER = "70", + OP_2ROT = "71", + OP_2SWAP = "72", + OP_IFDUP = "73", + OP_DEPTH = "74", + OP_DROP = "75", + OP_DUP = "76", + OP_NIP = "77", + OP_OVER = "78", + OP_PICK = "79", + OP_ROLL = "7a", + OP_ROT = "7b", + OP_SWAP = "7c", + OP_TUCK = "7d", + //splice + OP_CAT = "7e", //disabled + OP_SUBSTR = "7f", //disabled + OP_LEFT = "80", //disabled + OP_RIGHT = "81", //disabled + OP_SIZE = "82", + //bitwise + OP_INVERT = "83", + OP_AND = "84", + OP_OR = "85", + OP_XOR = "86", + OP_EQUAL = "87", + OP_EQUALVERIFY = "88", + OP_RESERVED1 = "89", //reserved + OP_RESERVED2 = "8a", //reserved + //arithmetic + OP_1ADD = "8b", + OP_1SUB = "8c", + OP_2MUL = "8d", //disabled + OP_2DIV = "8e", //disabled + OP_NEGATE = "8f", + OP_ABS = "90", + OP_NOT = "91", + OP_0NOTEQUAL = "92", + OP_ADD = "93", + OP_SUB = "94", + OP_MUL = "95", //disabled + OP_DIV = "96", //disabled + OP_MOD = "97", //disabled + OP_LSHIFT = "98", //disabled + OP_RSHIFT = "99", //disabled + OP_BOOLAND = "9a", + OP_BOOLOR = "9b", + OP_NUMEQUAL = "9c", + OP_NUMEQUALVERIFY = "9d", + OP_NUMNOTEQUAL = "9e", + OP_LESSTHAN = "9f", + OP_GREATERTHAN = "a0", + OP_LESSTHANOREQUAL = "a1", + OP_GREATERTHANOREQUAL = "a2", + OP_MIN = "a3", + OP_MAX = "a4", + OP_WITHIN = "a5", + // crypto + OP_RIPEMD160 = "a6", + OP_SHA1 = "a7", + OP_SHA256 = "a8", + OP_HASH160 = "a9", + OP_HASH256 = "aa", + OP_CODESEPARATOR = "ab", + OP_CHECKSIG = "ac", + OP_CHECKSIGVERIFY = "ad", + OP_CHECKMULTISIG = "ae", + OP_CHECKMULTISIGVERIFY = "af", + OP_NOP1 = "b0", //reserved + //locktime + OP_CHECKLOCKTIMEVERIFY = "b1", + OP_CHECKSEQUENCEVERIFY = "b2", + OP_CSV = OP_CHECKSEQUENCEVERIFY, + OP_NOP4 = "b3", //reserved + OP_NOP5 = "b4", //reserved + OP_NOP6 = "b5", //reserved + OP_NOP7 = "b6", //reserved + OP_NOP8 = "b7", //reserved + OP_NOP9 = "b8", //reserved + OP_NOP10 = "b9", //reserved + OP_CHECKSIGADD = "ba", + OP_CHECKSIGADDVERIFY = "bb", // is this even there? +} diff --git a/src/features/script/stack.ts b/src/features/script/stack.ts new file mode 100644 index 0000000..329d1d2 --- /dev/null +++ b/src/features/script/stack.ts @@ -0,0 +1,53 @@ +import { IStack } from "../../interfaces/store"; + +export class Stack implements IStack { + private stack: T[] = []; + + push(key: T): void { + this.stack.push(key); + } + + pop(): T | undefined { + return this.stack.pop(); + } + + top(): T | undefined { + return this.stack[this.stack.length - 1]; + } +} + +export class ScriptStack extends Stack { + private scriptSigAsm: string; + private scriptPubKeyAsm: string; + private executionStates: boolean[] = [true]; + + constructor(scriptSigAsm: string, scriptPubKeyAsm: string) { + super(); + this.scriptSigAsm = scriptSigAsm; + this.scriptPubKeyAsm = scriptPubKeyAsm; + } + + //load the scriptSigAsm into the stack as the input + initializeStack(): void { + if (this.scriptSigAsm) { + const scriptTokens = this.scriptSigAsm.split(" "); + } + } + + shouldExecute(): boolean { + return this.executionStates[this.executionStates.length - 1]; + } + + onIf(condition: boolean) { + this.executionStates.push(condition); + } + + onElse() { + this.executionStates[this.executionStates.length - 1] = + !this.executionStates[this.executionStates.length - 1]; + } + + onEndIf() { + this.executionStates.pop(); + } +} diff --git a/src/features/script/utils.ts b/src/features/script/utils.ts new file mode 100644 index 0000000..742a926 --- /dev/null +++ b/src/features/script/utils.ts @@ -0,0 +1,29 @@ +import { StringMappingType } from "typescript"; + +export const getNextNBytes = (str: string, n: number): [string, string] => { + return [str.slice(0, n * 2), str.slice(n * 2)]; //2 hex chars per byte +}; + +export const parseHex = (str: string) => { + return parseInt(str, 10); +}; + +export const parseNumber = (number: string) => { + //reverse the number as it is in little endian format + return parseInt( + number + .match(/.{1,2}/g)! + .reverse() + .join(""), + 16 + ); +}; + +export const encodeNumber = (num: number) => { + //reverse the number as it should be in little endian format + return num + .toString(16) + .match(/.{1,2}/g)! + .reverse() + .join(""); +}; diff --git a/src/features/transactionHex.ts b/src/features/transactionHex.ts new file mode 100644 index 0000000..394a68a --- /dev/null +++ b/src/features/transactionHex.ts @@ -0,0 +1,3 @@ +import { Transaction } from "../types"; + +export const toRawTransactionHex = (tx: Transaction) => {}; diff --git a/src/features/validator/hash.ts b/src/features/validator/hash.ts new file mode 100644 index 0000000..d709b2a --- /dev/null +++ b/src/features/validator/hash.ts @@ -0,0 +1,61 @@ +import { Transaction, TransactionType } from "../../types"; +import * as crypto from "crypto"; +import { asmToHex, hash256, sha256 } from "../../utils"; +import { OP_CODES } from "../script/op_codes"; +import { collapseTextChangeRangesAcrossMultipleVersions } from "typescript"; + +export const HashValidator = (tx: Transaction) => { + let lockingScript = ""; + let script = ""; + let hash = ""; + let publicKey = ""; + + for (const input of tx.vin) { + switch (input.prevout.scriptpubkey_type) { + case TransactionType.P2PKH: + const scriptAsmTokens = input.scriptsig_asm.split(" "); + publicKey = scriptAsmTokens[scriptAsmTokens.length - 1]; + + hash = hash256(publicKey); + + lockingScript = input.prevout.scriptpubkey; + script = `${OP_CODES.OP_DUP}${OP_CODES.OP_HASH160}${OP_CODES.OP_PUSHBYTES_20}${hash}${OP_CODES.OP_EQUALVERIFY}${OP_CODES.OP_CHECKSIG}`; + + if (script !== lockingScript) return false; + break; + case TransactionType.P2SH: + const inputScript = input.scriptsig_asm.split(" "); + const hex = asmToHex(inputScript[inputScript.length - 1]); + lockingScript = input.prevout.scriptpubkey; + + const scriptHash = hash256(hex); + + script = `${OP_CODES.OP_HASH160}${OP_CODES.OP_PUSHBYTES_20}${scriptHash}${OP_CODES.OP_EQUAL}`; + if (script !== lockingScript) return false; + break; + case TransactionType.P2WPKH: + publicKey = input.witness[1]; + if (!publicKey) return false; + + hash = hash256(publicKey); + lockingScript = input.prevout.scriptpubkey; + script = `${OP_CODES.OP_0}${OP_CODES.OP_PUSHBYTES_20}${hash}`; + + if (script !== lockingScript) return false; + break; + case TransactionType.P2WSH: + const witnessScript = input.witness[input.witness.length - 1]; + if (!witnessScript) return false; + + lockingScript = input.prevout.scriptpubkey; + //OP_0 OP_PUSHBYTES_32 0b685cc06add0b2e23bcd67f0bef8d364cdc1abcf6fb126958826a7cfe351bf3 + script = `${OP_CODES.OP_0}${OP_CODES.OP_PUSHBYTES_32}${sha256( + witnessScript + )}`; + if (script !== lockingScript) return false; + break; + } + } + + return true; +}; diff --git a/src/features/validator/length.ts b/src/features/validator/length.ts new file mode 100644 index 0000000..b8e6fdb --- /dev/null +++ b/src/features/validator/length.ts @@ -0,0 +1,33 @@ +//validates the length of the scriptpubkeyaddress + +import { Transaction, TransactionType } from "../../types"; + +//validate the length of the scriptpubkey +export const LengthValidator = (tx: Transaction) => { + for (const input of tx.vin) { + if (input.prevout.scriptpubkey.length <= 0) return false; + switch (input.prevout.scriptpubkey_type) { + case TransactionType.P2PKH: + if (input.prevout.scriptpubkey.length !== 50) { + return false; + } + break; + case TransactionType.P2SH: + break; + case TransactionType.P2WPKH: + if (input.prevout.scriptpubkey.length !== 44) { + return false; + } + if (input.witness.length !== 2) return false; + break; + case TransactionType.P2WSH: + break; + case TransactionType.P2TR: + if (input.prevout.scriptpubkey.length !== 68) { + return false; + } + break; + } + } + return true; +}; diff --git a/src/features/validator/signature.ts b/src/features/validator/signature.ts new file mode 100644 index 0000000..35e7388 --- /dev/null +++ b/src/features/validator/signature.ts @@ -0,0 +1 @@ +// export const signature; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..ac95ffe --- /dev/null +++ b/src/index.ts @@ -0,0 +1,51 @@ +import * as fs from "fs"; +import { Transaction } from "./types"; +// import { mempool } from "./store/mempool"; +import { utxos } from "./store/utxos"; +import { LengthValidator } from "./features/validator/length"; +import { HashValidator } from "./features/validator/hash"; +import { reversify, sha256 } from "./utils"; +import { txSerializer, txWeight } from "./features/encoding/serializer"; +import { feePerByte } from "./features/block/fee"; +import { mine } from "./features/block/mine"; +import * as path from "path"; + +(async () => { + const files = fs.readdirSync("./mempool"); + const outputFile = path.join(__dirname, "..", "output.txt"); + let mempool: Transaction[] = []; + + const blockSize = 2 * 1e6; + + for (const file of files) { + const tx = JSON.parse(fs.readFileSync(`./mempool/${file}`, "utf8")); + // mempool.set(`${file}`.split(".")[0], { + // ...tx, + // txid: `${file}`.split(".")[0], + // }); + mempool.push(tx); + } + + let txs = []; + mempool.sort((txA, txB) => feePerByte(txB) - feePerByte(txA)); + let blockWeight = 0; + for (const tx of mempool) { + if (txWeight(tx) + blockWeight > blockSize) break; + + txs.push(tx); + blockWeight += txWeight(tx); + } + + const { serializedBlock, blockHash, coinbaseTransaction } = mine(txs); + fs.writeFileSync(outputFile, serializedBlock); + fs.appendFileSync(outputFile, "\n"); + fs.appendFileSync(outputFile, txSerializer(coinbaseTransaction).serializedTx); + fs.appendFileSync(outputFile, "\n"); + for (const tx of txs) { + fs.appendFileSync( + outputFile, + reversify(sha256(sha256(txSerializer(tx).serializedTx))) + ); + fs.appendFileSync(outputFile, "\n"); + } +})(); diff --git a/src/interfaces/store.ts b/src/interfaces/store.ts new file mode 100644 index 0000000..d955be3 --- /dev/null +++ b/src/interfaces/store.ts @@ -0,0 +1,9 @@ +export interface IStore { + get(key: K): V | undefined; + set(key: K, value: V): void; +} + +export interface IStack { + push(key: K): void; + pop(): K | undefined; +} diff --git a/src/store/mempool.ts b/src/store/mempool.ts new file mode 100644 index 0000000..223870c --- /dev/null +++ b/src/store/mempool.ts @@ -0,0 +1,17 @@ +import { IStore } from "../interfaces/store"; +import { Transaction } from "../types"; + +class Mempool { + map: Map; + constructor() { + this.map = new Map(); + } + get(key: string): Transaction | undefined { + return this.map.get(key); + } + set(key: string, value: Transaction): void { + this.map.set(key, value); + } +} + +export const mempool = new Mempool(); diff --git a/src/store/utxos.ts b/src/store/utxos.ts new file mode 100644 index 0000000..b06c093 --- /dev/null +++ b/src/store/utxos.ts @@ -0,0 +1,50 @@ +import { Transaction } from "../types"; + +class UTXOs { + map: Map>; + constructor() { + this.map = new Map>(); + } + private get(key: string): Set | undefined { + return this.map.get(key); + } + private set(key: string, value?: string): void { + if (!this.map.has(key)) { + this.map.set(key, new Set()); + } + if (value) this.map.set(key, this.map.get(key)!.add(value)); + } + + getUTXOs() { + //get all the keys and check if it's size is 1 and that element is empty + const utxos: Set = new Set(); + for (const key of this.map.keys()) { + if (this.map.get(key)!.size === 0) { + utxos.add(key); + } + } + return utxos; + } + + isInputSpent(txid: string, vout: number): boolean { + const spentBy = this.get(txid + vout); + if (!spentBy) return false; + return spentBy.size > 0; + } + + doesUTXOExist(txid: string, vout: number): boolean { + return this.get(txid + vout) !== undefined; + } + + addUTXOs(tx: Transaction): void { + for (const input of tx.vin) { + this.set(input.txid + input.vout, tx.txid); + } + + for (let i = 0; i < tx.vout.length; i++) { + this.set(tx.txid + i); + } + } +} + +export const utxos = new UTXOs(); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..86ca1ef --- /dev/null +++ b/src/types.ts @@ -0,0 +1,35 @@ +export type Transaction = { + txid: string; + version: number; + locktime: number; + vin: Input[]; + vout: Output[]; +}; + +export type Input = { + txid: string; + vout: number; + prevout: Output; + scriptsig: string; + scriptsig_asm: string; + witness: string[]; + is_coinbase: boolean; + sequence: number; + inner_redeemscript_asm: string; +}; + +export type Output = { + scriptpubkey: string; + scriptpubkey_asm: string; + scriptpubkey_type: string; + scriptpubkey_address: string; + value: number; +}; + +export enum TransactionType { + P2PKH = "p2pkh", + P2SH = "p2sh", + P2WPKH = "v0_p2wpkh", + P2WSH = "v0_p2wsh", + P2TR = "v1_p2tr", +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..f8693b1 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,41 @@ +import * as crypto from "crypto"; +import { OP_CODES } from "./features/script/op_codes"; + +export const hash256 = (str: string) => { + return crypto + .createHash("ripemd160") + .update(Buffer.from(sha256(str), "hex")) + .digest("hex"); +}; + +export const sha256 = (str: string) => { + return crypto + .createHash("sha256") + .update(Buffer.from(str, "hex")) + .digest("hex"); +}; + +export const asmToHex = (asm: string) => { + const tokens = asm.split(" ") as OP_CODES[]; + return [...new Array(tokens.length)] + .map((_, index) => OP_CODES[tokens[index]]) + .map((token, index) => (!token ? tokens[index] : token)) + .join(""); +}; + +//reverses every byte of the string - every 2 hex chars +export const reversify = (str: string) => { + return str + .match(/.{1,2}/g)! + .reverse() + .join(""); +}; + +// const asm = "OP_0 OP_PUSHBYTES_20 15ff0337937ecadd10ce56ffdfd4674817613223"; +// const hex = asmToHex(asm); +// console.log(hex); +// console.log( +// sha256( +// "201dda24da0b91e0eed9770878c504aeb07628ca4ccae9a7bd5347b96ee85dac52ac0063036f726401010a746578742f706c61696e00357b2270223a226272632d3230222c226f70223a226d696e74222c227469636b223a22646f6765222c22616d74223a2234323030227d68" +// ) +// ); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..e075f97 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "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. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}