diff --git a/package-lock.json b/package-lock.json index a1ba1ec..f932d24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,12 +5,12 @@ "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "version": "7.15.8", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.15.8.tgz", + "integrity": "sha512-2IAnmn8zbvC/jKYhq5Ki9I+DwjlrtMPUCH/CpHvqI4dNnlwHwsxoIhlc8WcYY5LSYknXQtAlFYuHfqAFCvQ4Wg==", "dev": true, "requires": { - "@babel/highlight": "^7.8.3" + "@babel/highlight": "^7.14.5" } }, "@babel/core": { @@ -196,14 +196,22 @@ } }, "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", + "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", "dev": true, "requires": { + "@babel/helper-validator-identifier": "^7.14.5", "chalk": "^2.0.0", - "esutils": "^2.0.2", "js-tokens": "^4.0.0" + }, + "dependencies": { + "@babel/helper-validator-identifier": { + "version": "7.15.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", + "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "dev": true + } } }, "@babel/parser": { @@ -383,6 +391,24 @@ "@types/base-x": "*" } }, + "@types/bs58check": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/bs58check/-/bs58check-2.1.0.tgz", + "integrity": "sha512-OxsysnJQh82vy9DRbOcw9m2j/WiyqZLn0YBhKxdQ+aCwoHj+tWzyCgpwAkr79IfDXZKxc6h7k89T9pwS78CqTQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/create-hash": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/create-hash/-/create-hash-1.2.2.tgz", + "integrity": "sha512-Fg8/kfMJObbETFU/Tn+Y0jieYewryLrbKwLCEIwPyklZZVY2qB+64KFjhplGSw+cseZosfFXctXO+PyIYD8iZQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/mocha": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", @@ -390,9 +416,9 @@ "dev": true }, "@types/node": { - "version": "12.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz", - "integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==", + "version": "16.11.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz", + "integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==", "dev": true }, "@types/proxyquire": { @@ -401,6 +427,24 @@ "integrity": "sha512-SQaNzWQ2YZSr7FqAyPPiA3FYpux2Lqh3HWMZQk47x3xbMCqgC/w0dY3dw9rGqlweDDkrySQBcaScXWeR+Yb11Q==", "dev": true }, + "@types/randombytes": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/randombytes/-/randombytes-2.0.0.tgz", + "integrity": "sha512-bz8PhAVlwN72vqefzxa14DKNT8jK/mV66CSjwdVQM/k3Th3EPKfUtdMniwZgMedQTFuywAsfjnZsg+pEnltaMA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/wif": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/wif/-/wif-2.0.2.tgz", + "integrity": "sha512-IiIuBeJzlh4LWJ7kVTrX0nwB60OG0vvGTaWC/SgSbVFw7uYUTF6gEuvDZ1goWkeirekJDD58Y8g7NljQh2fNkA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -557,14 +601,6 @@ "integrity": "sha512-RQ1nc7xtnLa5XltnCqkoR2zmhuz498RjMJwrLKQzOE049D1HUqnYfon7cVSbwS5UGm0/EQlC2CH+NY3MyITA4Q==", "dev": true }, - "bip66": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", - "integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, "bip68": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/bip68/-/bip68-1.0.4.tgz", @@ -574,7 +610,8 @@ "bitcoin-ops": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz", - "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==" + "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==", + "dev": true }, "bn.js": { "version": "4.11.8", @@ -888,18 +925,42 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, - "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "ecpair": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-1.0.0.tgz", + "integrity": "sha512-1L+P/ivLC3eKHgqcX1M9tFYQWXDoqwJ3zQnN7zDaTtLpiCQKpFTaAZvnsPC5PkWB4q3EPFAHffCLvjfCqRjuwQ==", + "dev": true, "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "randombytes": "^2.0.1", + "tiny-secp256k1": "^1.1.6", + "typeforce": "^1.11.3", + "wif": "^2.0.1" + } + }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + } } }, "emoji-regex": { @@ -956,12 +1017,6 @@ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -1549,11 +1604,6 @@ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", "dev": true }, - "merkle-lib": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/merkle-lib/-/merkle-lib-2.0.10.tgz", - "integrity": "sha1-grjbrnXieneFOItz+ddyXQ9vMyY=" - }, "minimaldata": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/minimaldata/-/minimaldata-1.0.2.tgz", @@ -2091,6 +2141,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", "integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=", + "dev": true, "requires": { "bitcoin-ops": "^1.3.0" } @@ -2446,15 +2497,15 @@ } }, "tslib": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, "tslint": { - "version": "5.20.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", - "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -2465,10 +2516,10 @@ "glob": "^7.1.1", "js-yaml": "^3.13.1", "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", + "mkdirp": "^0.5.3", "resolve": "^1.3.2", "semver": "^5.3.0", - "tslib": "^1.8.0", + "tslib": "^1.13.0", "tsutils": "^2.29.0" }, "dependencies": { @@ -2510,9 +2561,9 @@ "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" }, "typescript": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.2.2.tgz", - "integrity": "sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz", + "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==", "dev": true }, "uuid": { @@ -2522,9 +2573,9 @@ "dev": true }, "varuint-bitcoin": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.0.tgz", - "integrity": "sha512-jCEPG+COU/1Rp84neKTyDJQr478/hAfVp5xxYn09QEH0yBjbmPeMfuuQIrp+BUD83hybtYZKhr5elV3bvdV1bA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", + "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", "requires": { "safe-buffer": "^5.1.1" } diff --git a/package.json b/package.json index 887dff8..03df9d5 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "5.2.0", "description": "Client-side Bitcoin JavaScript library", "main": "./src/index.js", - "types": "./types/index.d.ts", + "types": "./src/index.d.ts", "engines": { "node": ">=8.0.0" }, @@ -18,7 +18,7 @@ "audit": "NPM_AUDIT_IGNORE_DEV=1 NPM_AUDIT_IGNORE_LEVEL=low npm-audit-whitelister .npm-audit-whitelister.json", "build": "npm run clean && tsc -p ./tsconfig.json && npm run formatjs", "build:tests": "npm run clean:jstests && tsc -p ./test/tsconfig.json", - "clean": "rimraf src types", + "clean": "rimraf src", "clean:jstests": "rimraf 'test/**/!(ts-node-register)*.js'", "coverage-report": "npm run build && npm run nobuild:coverage-report", "coverage-html": "npm run build && npm run nobuild:coverage-html", @@ -46,37 +46,36 @@ "url": "https://github.com/bitcoinjs/bitcoinjs-lib.git" }, "files": [ - "src", - "types" + "src" ], "dependencies": { "bech32": "^2.0.0", "bip174": "^2.0.1", "bip32": "^2.0.4", - "bip66": "^1.1.0", - "bitcoin-ops": "^1.4.0", - "bs58check": "^2.0.0", + "bs58check": "^2.1.2", "create-hash": "^1.1.0", "create-hmac": "^1.1.3", - "merkle-lib": "^2.0.10", - "pushdata-bitcoin": "^1.0.1", "randombytes": "^2.0.1", - "tiny-secp256k1": "^1.1.6", "typeforce": "^1.11.3", - "varuint-bitcoin": "^1.0.4", + "varuint-bitcoin": "^1.1.2", "wif": "^2.0.1" }, "devDependencies": { "@types/bs58": "^4.0.0", + "@types/bs58check": "^2.1.0", + "@types/create-hash": "^1.2.2", "@types/mocha": "^5.2.7", - "@types/node": "12.7.5", + "@types/node": "^16.11.1", "@types/proxyquire": "^1.3.28", + "@types/randombytes": "^2.0.0", + "@types/wif": "^2.0.2", "bip39": "^3.0.2", "bip65": "^1.0.1", "bip68": "^1.0.3", "bn.js": "^4.11.8", "bs58": "^4.0.0", "dhttp": "^3.0.0", + "ecpair": "^1.0.0", "hoodwink": "^2.0.0", "minimaldata": "^1.0.2", "mocha": "^7.1.1", @@ -87,8 +86,8 @@ "regtest-client": "0.2.0", "rimraf": "^2.6.3", "ts-node": "^8.3.0", - "tslint": "^5.20.1", - "typescript": "3.2.2" + "tslint": "^6.1.3", + "typescript": "^4.4.4" }, "license": "MIT" } diff --git a/types/address.d.ts b/src/address.d.ts similarity index 95% rename from types/address.d.ts rename to src/address.d.ts index 5c7ed5a..be0e00a 100644 --- a/types/address.d.ts +++ b/src/address.d.ts @@ -1,3 +1,4 @@ +/// import { Network } from './networks'; export interface Base58CheckResult { hash: Buffer; diff --git a/src/address.js b/src/address.js index 1709c37..12938fc 100644 --- a/src/address.js +++ b/src/address.js @@ -1,12 +1,13 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.toOutputScript = exports.fromOutputScript = exports.toBech32 = exports.toBase58Check = exports.fromBech32 = exports.fromBase58Check = void 0; const networks = require('./networks'); const payments = require('./payments'); const bscript = require('./script'); const types = require('./types'); -const { bech32, bech32m } = require('bech32'); +const bech32_1 = require('bech32'); const bs58check = require('bs58check'); -const typeforce = require('typeforce'); +const { typeforce } = types; const FUTURE_SEGWIT_MAX_SIZE = 40; const FUTURE_SEGWIT_MIN_SIZE = 2; const FUTURE_SEGWIT_MAX_VERSION = 16; @@ -43,17 +44,17 @@ function fromBech32(address) { let result; let version; try { - result = bech32.decode(address); + result = bech32_1.bech32.decode(address); } catch (e) {} if (result) { version = result.words[0]; if (version !== 0) throw new TypeError(address + ' uses wrong encoding'); } else { - result = bech32m.decode(address); + result = bech32_1.bech32m.decode(address); version = result.words[0]; if (version === 0) throw new TypeError(address + ' uses wrong encoding'); } - const data = bech32.fromWords(result.words.slice(1)); + const data = bech32_1.bech32.fromWords(result.words.slice(1)); return { version, prefix: result.prefix, @@ -70,11 +71,11 @@ function toBase58Check(hash, version) { } exports.toBase58Check = toBase58Check; function toBech32(data, version, prefix) { - const words = bech32.toWords(data); + const words = bech32_1.bech32.toWords(data); words.unshift(version); return version === 0 - ? bech32.encode(prefix, words) - : bech32m.encode(prefix, words); + ? bech32_1.bech32.encode(prefix, words) + : bech32_1.bech32m.encode(prefix, words); } exports.toBech32 = toBech32; function fromOutputScript(output, network) { diff --git a/src/bip66.d.ts b/src/bip66.d.ts new file mode 100644 index 0000000..547c57f --- /dev/null +++ b/src/bip66.d.ts @@ -0,0 +1,7 @@ +/// +export declare function check(buffer: Buffer): boolean; +export declare function decode(buffer: Buffer): { + r: Buffer; + s: Buffer; +}; +export declare function encode(r: Buffer, s: Buffer): Buffer; diff --git a/src/bip66.js b/src/bip66.js new file mode 100644 index 0000000..0070f99 --- /dev/null +++ b/src/bip66.js @@ -0,0 +1,102 @@ +'use strict'; +// Reference https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki +// Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] +// NOTE: SIGHASH byte ignored AND restricted, truncate before use +Object.defineProperty(exports, '__esModule', { value: true }); +exports.encode = exports.decode = exports.check = void 0; +function check(buffer) { + if (buffer.length < 8) return false; + if (buffer.length > 72) return false; + if (buffer[0] !== 0x30) return false; + if (buffer[1] !== buffer.length - 2) return false; + if (buffer[2] !== 0x02) return false; + const lenR = buffer[3]; + if (lenR === 0) return false; + if (5 + lenR >= buffer.length) return false; + if (buffer[4 + lenR] !== 0x02) return false; + const lenS = buffer[5 + lenR]; + if (lenS === 0) return false; + if (6 + lenR + lenS !== buffer.length) return false; + if (buffer[4] & 0x80) return false; + if (lenR > 1 && buffer[4] === 0x00 && !(buffer[5] & 0x80)) return false; + if (buffer[lenR + 6] & 0x80) return false; + if (lenS > 1 && buffer[lenR + 6] === 0x00 && !(buffer[lenR + 7] & 0x80)) + return false; + return true; +} +exports.check = check; +function decode(buffer) { + if (buffer.length < 8) throw new Error('DER sequence length is too short'); + if (buffer.length > 72) throw new Error('DER sequence length is too long'); + if (buffer[0] !== 0x30) throw new Error('Expected DER sequence'); + if (buffer[1] !== buffer.length - 2) + throw new Error('DER sequence length is invalid'); + if (buffer[2] !== 0x02) throw new Error('Expected DER integer'); + const lenR = buffer[3]; + if (lenR === 0) throw new Error('R length is zero'); + if (5 + lenR >= buffer.length) throw new Error('R length is too long'); + if (buffer[4 + lenR] !== 0x02) throw new Error('Expected DER integer (2)'); + const lenS = buffer[5 + lenR]; + if (lenS === 0) throw new Error('S length is zero'); + if (6 + lenR + lenS !== buffer.length) throw new Error('S length is invalid'); + if (buffer[4] & 0x80) throw new Error('R value is negative'); + if (lenR > 1 && buffer[4] === 0x00 && !(buffer[5] & 0x80)) + throw new Error('R value excessively padded'); + if (buffer[lenR + 6] & 0x80) throw new Error('S value is negative'); + if (lenS > 1 && buffer[lenR + 6] === 0x00 && !(buffer[lenR + 7] & 0x80)) + throw new Error('S value excessively padded'); + // non-BIP66 - extract R, S values + return { + r: buffer.slice(4, 4 + lenR), + s: buffer.slice(6 + lenR), + }; +} +exports.decode = decode; +/* + * Expects r and s to be positive DER integers. + * + * The DER format uses the most significant bit as a sign bit (& 0x80). + * If the significant bit is set AND the integer is positive, a 0x00 is prepended. + * + * Examples: + * + * 0 => 0x00 + * 1 => 0x01 + * -1 => 0xff + * 127 => 0x7f + * -127 => 0x81 + * 128 => 0x0080 + * -128 => 0x80 + * 255 => 0x00ff + * -255 => 0xff01 + * 16300 => 0x3fac + * -16300 => 0xc054 + * 62300 => 0x00f35c + * -62300 => 0xff0ca4 + */ +function encode(r, s) { + const lenR = r.length; + const lenS = s.length; + if (lenR === 0) throw new Error('R length is zero'); + if (lenS === 0) throw new Error('S length is zero'); + if (lenR > 33) throw new Error('R length is too long'); + if (lenS > 33) throw new Error('S length is too long'); + if (r[0] & 0x80) throw new Error('R value is negative'); + if (s[0] & 0x80) throw new Error('S value is negative'); + if (lenR > 1 && r[0] === 0x00 && !(r[1] & 0x80)) + throw new Error('R value excessively padded'); + if (lenS > 1 && s[0] === 0x00 && !(s[1] & 0x80)) + throw new Error('S value excessively padded'); + const signature = Buffer.allocUnsafe(6 + lenR + lenS); + // 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] + signature[0] = 0x30; + signature[1] = signature.length - 2; + signature[2] = 0x02; + signature[3] = r.length; + r.copy(signature, 4); + signature[4 + lenR] = 0x02; + signature[5 + lenR] = s.length; + s.copy(signature, 6 + lenR); + return signature; +} +exports.encode = encode; diff --git a/types/block.d.ts b/src/block.d.ts similarity index 96% rename from types/block.d.ts rename to src/block.d.ts index 7d8309c..1d90c13 100644 --- a/types/block.d.ts +++ b/src/block.d.ts @@ -1,3 +1,4 @@ +/// import { Transaction } from './transaction'; export declare class Block { static fromBuffer(buffer: Buffer): Block; diff --git a/src/block.js b/src/block.js index cb3ee4b..4e6ca84 100644 --- a/src/block.js +++ b/src/block.js @@ -1,12 +1,12 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.Block = void 0; const bufferutils_1 = require('./bufferutils'); const bcrypto = require('./crypto'); +const merkle_1 = require('./merkle'); const transaction_1 = require('./transaction'); const types = require('./types'); -const fastMerkleRoot = require('merkle-lib/fastRoot'); -const typeforce = require('typeforce'); -const varuint = require('varuint-bitcoin'); +const { typeforce } = types; const errorMerkleNoTxes = new TypeError( 'Cannot compute merkle root for zero transactions', ); @@ -14,16 +14,6 @@ const errorWitnessNotSegwit = new TypeError( 'Cannot compute witness commit for non-segwit block', ); class Block { - constructor() { - this.version = 1; - this.prevHash = undefined; - this.merkleRoot = undefined; - this.timestamp = 0; - this.witnessCommit = undefined; - this.bits = 0; - this.nonce = 0; - this.transactions = undefined; - } static fromBuffer(buffer) { if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)'); const bufferReader = new bufferutils_1.BufferReader(buffer); @@ -72,13 +62,21 @@ class Block { const hashes = transactions.map(transaction => transaction.getHash(forWitness), ); - const rootHash = fastMerkleRoot(hashes, bcrypto.hash256); + const rootHash = (0, merkle_1.fastMerkleRoot)(hashes, bcrypto.hash256); return forWitness ? bcrypto.hash256( Buffer.concat([rootHash, transactions[0].ins[0].witness[0]]), ) : rootHash; } + version = 1; + prevHash = undefined; + merkleRoot = undefined; + timestamp = 0; + witnessCommit = undefined; + bits = 0; + nonce = 0; + transactions = undefined; getWitnessCommit() { if (!txesHaveWitnessCommit(this.transactions)) return null; // The merkle root for the witness data is in an OP_RETURN output. @@ -117,7 +115,7 @@ class Block { if (headersOnly || !this.transactions) return 80; return ( 80 + - varuint.encodingLength(this.transactions.length) + + bufferutils_1.varuint.encodingLength(this.transactions.length) + this.transactions.reduce((a, x) => a + x.byteLength(allowWitness), 0) ); } @@ -125,7 +123,7 @@ class Block { return bcrypto.hash256(this.toBuffer(true)); } getId() { - return bufferutils_1.reverseBuffer(this.getHash()).toString('hex'); + return (0, bufferutils_1.reverseBuffer)(this.getHash()).toString('hex'); } getUTCDate() { const date = new Date(0); // epoch @@ -143,8 +141,12 @@ class Block { bufferWriter.writeUInt32(this.bits); bufferWriter.writeUInt32(this.nonce); if (headersOnly || !this.transactions) return buffer; - varuint.encode(this.transactions.length, buffer, bufferWriter.offset); - bufferWriter.offset += varuint.encode.bytes; + bufferutils_1.varuint.encode( + this.transactions.length, + buffer, + bufferWriter.offset, + ); + bufferWriter.offset += bufferutils_1.varuint.encode.bytes; this.transactions.forEach(tx => { const txSize = tx.byteLength(); // TODO: extract from toBuffer? tx.toBuffer(buffer, bufferWriter.offset); @@ -166,7 +168,7 @@ class Block { ); } checkProofOfWork() { - const hash = bufferutils_1.reverseBuffer(this.getHash()); + const hash = (0, bufferutils_1.reverseBuffer)(this.getHash()); const target = Block.calculateTarget(this.bits); return hash.compare(target) <= 0; } diff --git a/types/bufferutils.d.ts b/src/bufferutils.d.ts similarity index 92% rename from types/bufferutils.d.ts rename to src/bufferutils.d.ts index 95a48ba..40f89b3 100644 --- a/types/bufferutils.d.ts +++ b/src/bufferutils.d.ts @@ -1,3 +1,6 @@ +/// +import * as varuint from 'varuint-bitcoin'; +export { varuint }; export declare function readUInt64LE(buffer: Buffer, offset: number): number; export declare function writeUInt64LE(buffer: Buffer, value: number, offset: number): number; export declare function reverseBuffer(buffer: Buffer): Buffer; diff --git a/src/bufferutils.js b/src/bufferutils.js index a68fd31..933bebc 100644 --- a/src/bufferutils.js +++ b/src/bufferutils.js @@ -1,8 +1,10 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.BufferReader = exports.BufferWriter = exports.cloneBuffer = exports.reverseBuffer = exports.writeUInt64LE = exports.readUInt64LE = exports.varuint = void 0; const types = require('./types'); -const typeforce = require('typeforce'); +const { typeforce } = types; const varuint = require('varuint-bitcoin'); +exports.varuint = varuint; // https://github.com/feross/buffer/blob/master/index.js#L1127 function verifuint(value, max) { if (typeof value !== 'number') @@ -51,6 +53,8 @@ exports.cloneBuffer = cloneBuffer; * Helper class for serialization of bitcoin data types into a pre-allocated buffer. */ class BufferWriter { + buffer; + offset; constructor(buffer, offset = 0) { this.buffer = buffer; this.offset = offset; @@ -92,6 +96,8 @@ exports.BufferWriter = BufferWriter; * Helper class for reading of bitcoin data types from a buffer. */ class BufferReader { + buffer; + offset; constructor(buffer, offset = 0) { this.buffer = buffer; this.offset = offset; diff --git a/types/crypto.d.ts b/src/crypto.d.ts similarity index 90% rename from types/crypto.d.ts rename to src/crypto.d.ts index 5d93acd..1743681 100644 --- a/types/crypto.d.ts +++ b/src/crypto.d.ts @@ -1,3 +1,4 @@ +/// export declare function ripemd160(buffer: Buffer): Buffer; export declare function sha1(buffer: Buffer): Buffer; export declare function sha256(buffer: Buffer): Buffer; diff --git a/src/crypto.js b/src/crypto.js index e7dd596..f53b041 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -1,5 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.hash256 = exports.hash160 = exports.sha256 = exports.sha1 = exports.ripemd160 = void 0; const createHash = require('create-hash'); function ripemd160(buffer) { try { diff --git a/src/ecpair.js b/src/ecpair.js deleted file mode 100644 index 91fe3a9..0000000 --- a/src/ecpair.js +++ /dev/null @@ -1,107 +0,0 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); -const NETWORKS = require('./networks'); -const types = require('./types'); -const ecc = require('tiny-secp256k1'); -const randomBytes = require('randombytes'); -const typeforce = require('typeforce'); -const wif = require('wif'); -const isOptions = typeforce.maybe( - typeforce.compile({ - compressed: types.maybe(types.Boolean), - network: types.maybe(types.Network), - }), -); -class ECPair { - constructor(__D, __Q, options) { - this.__D = __D; - this.__Q = __Q; - this.lowR = false; - if (options === undefined) options = {}; - this.compressed = - options.compressed === undefined ? true : options.compressed; - this.network = options.network || NETWORKS.bitcoin; - if (__Q !== undefined) this.__Q = ecc.pointCompress(__Q, this.compressed); - } - get privateKey() { - return this.__D; - } - get publicKey() { - if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__D, this.compressed); - return this.__Q; - } - toWIF() { - if (!this.__D) throw new Error('Missing private key'); - return wif.encode(this.network.wif, this.__D, this.compressed); - } - sign(hash, lowR) { - if (!this.__D) throw new Error('Missing private key'); - if (lowR === undefined) lowR = this.lowR; - if (lowR === false) { - return ecc.sign(hash, this.__D); - } else { - let sig = ecc.sign(hash, this.__D); - const extraData = Buffer.alloc(32, 0); - let counter = 0; - // if first try is lowR, skip the loop - // for second try and on, add extra entropy counting up - while (sig[0] > 0x7f) { - counter++; - extraData.writeUIntLE(counter, 0, 6); - sig = ecc.signWithEntropy(hash, this.__D, extraData); - } - return sig; - } - } - verify(hash, signature) { - return ecc.verify(hash, this.publicKey, signature); - } -} -function fromPrivateKey(buffer, options) { - typeforce(types.Buffer256bit, buffer); - if (!ecc.isPrivate(buffer)) - throw new TypeError('Private key not in range [1, n)'); - typeforce(isOptions, options); - return new ECPair(buffer, undefined, options); -} -exports.fromPrivateKey = fromPrivateKey; -function fromPublicKey(buffer, options) { - typeforce(ecc.isPoint, buffer); - typeforce(isOptions, options); - return new ECPair(undefined, buffer, options); -} -exports.fromPublicKey = fromPublicKey; -function fromWIF(wifString, network) { - const decoded = wif.decode(wifString); - const version = decoded.version; - // list of networks? - if (types.Array(network)) { - network = network - .filter(x => { - return version === x.wif; - }) - .pop(); - if (!network) throw new Error('Unknown network version'); - // otherwise, assume a network object (or default to bitcoin) - } else { - network = network || NETWORKS.bitcoin; - if (version !== network.wif) throw new Error('Invalid network version'); - } - return fromPrivateKey(decoded.privateKey, { - compressed: decoded.compressed, - network: network, - }); -} -exports.fromWIF = fromWIF; -function makeRandom(options) { - typeforce(isOptions, options); - if (options === undefined) options = {}; - const rng = options.rng || randomBytes; - let d; - do { - d = rng(32); - typeforce(types.Buffer256bit, d); - } while (!ecc.isPrivate(d)); - return fromPrivateKey(d, options); -} -exports.makeRandom = makeRandom; diff --git a/types/index.d.ts b/src/index.d.ts similarity index 63% rename from types/index.d.ts rename to src/index.d.ts index f63a986..1086e4b 100644 --- a/types/index.d.ts +++ b/src/index.d.ts @@ -1,18 +1,15 @@ import * as bip32 from 'bip32'; import * as address from './address'; import * as crypto from './crypto'; -import * as ECPair from './ecpair'; import * as networks from './networks'; import * as payments from './payments'; import * as script from './script'; -export { ECPair, address, bip32, crypto, networks, payments, script }; +export { address, bip32, crypto, networks, payments, script }; export { Block } from './block'; -export { Psbt, PsbtTxInput, PsbtTxOutput } from './psbt'; -export { OPS as opcodes } from './script'; +export { Psbt, PsbtTxInput, PsbtTxOutput, Signer, SignerAsync, HDSigner, HDSignerAsync, } from './psbt'; +export { OPS as opcodes } from './ops'; export { Transaction } from './transaction'; export { BIP32Interface } from 'bip32'; -export { ECPairInterface, Signer, SignerAsync } from './ecpair'; export { Network } from './networks'; export { Payment, PaymentCreator, PaymentOpts, Stack, StackElement, } from './payments'; -export { OpCode } from './script'; export { Input as TxInput, Output as TxOutput } from './transaction'; diff --git a/src/index.js b/src/index.js index e27b98a..a643df8 100644 --- a/src/index.js +++ b/src/index.js @@ -1,13 +1,12 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.Transaction = exports.opcodes = exports.Psbt = exports.Block = exports.script = exports.payments = exports.networks = exports.crypto = exports.bip32 = exports.address = void 0; const bip32 = require('bip32'); exports.bip32 = bip32; const address = require('./address'); exports.address = address; const crypto = require('./crypto'); exports.crypto = crypto; -const ECPair = require('./ecpair'); -exports.ECPair = ECPair; const networks = require('./networks'); exports.networks = networks; const payments = require('./payments'); @@ -15,10 +14,30 @@ exports.payments = payments; const script = require('./script'); exports.script = script; var block_1 = require('./block'); -exports.Block = block_1.Block; +Object.defineProperty(exports, 'Block', { + enumerable: true, + get: function() { + return block_1.Block; + }, +}); var psbt_1 = require('./psbt'); -exports.Psbt = psbt_1.Psbt; -var script_1 = require('./script'); -exports.opcodes = script_1.OPS; +Object.defineProperty(exports, 'Psbt', { + enumerable: true, + get: function() { + return psbt_1.Psbt; + }, +}); +var ops_1 = require('./ops'); +Object.defineProperty(exports, 'opcodes', { + enumerable: true, + get: function() { + return ops_1.OPS; + }, +}); var transaction_1 = require('./transaction'); -exports.Transaction = transaction_1.Transaction; +Object.defineProperty(exports, 'Transaction', { + enumerable: true, + get: function() { + return transaction_1.Transaction; + }, +}); diff --git a/src/merkle.d.ts b/src/merkle.d.ts new file mode 100644 index 0000000..d602201 --- /dev/null +++ b/src/merkle.d.ts @@ -0,0 +1,2 @@ +/// +export declare function fastMerkleRoot(values: Buffer[], digestFn: (b: Buffer) => Buffer): Buffer; diff --git a/src/merkle.js b/src/merkle.js new file mode 100644 index 0000000..e93f9ca --- /dev/null +++ b/src/merkle.js @@ -0,0 +1,22 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.fastMerkleRoot = void 0; +function fastMerkleRoot(values, digestFn) { + if (!Array.isArray(values)) throw TypeError('Expected values Array'); + if (typeof digestFn !== 'function') + throw TypeError('Expected digest Function'); + let length = values.length; + const results = values.concat(); + while (length > 1) { + let j = 0; + for (let i = 0; i < length; i += 2, ++j) { + const left = results[i]; + const right = i + 1 === length ? left : results[i + 1]; + const data = Buffer.concat([left, right]); + results[j] = digestFn(data); + } + length = j; + } + return results[0]; +} +exports.fastMerkleRoot = fastMerkleRoot; diff --git a/types/networks.d.ts b/src/networks.d.ts similarity index 100% rename from types/networks.d.ts rename to src/networks.d.ts diff --git a/src/networks.js b/src/networks.js index 0c31fe1..ea710f8 100644 --- a/src/networks.js +++ b/src/networks.js @@ -1,5 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.testnet = exports.regtest = exports.bitcoin = void 0; exports.bitcoin = { messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'bc', diff --git a/src/ops.d.ts b/src/ops.d.ts new file mode 100644 index 0000000..cda7a84 --- /dev/null +++ b/src/ops.d.ts @@ -0,0 +1,7 @@ +declare const OPS: { + [key: string]: number; +}; +declare const REVERSE_OPS: { + [key: number]: string; +}; +export { OPS, REVERSE_OPS }; diff --git a/src/ops.js b/src/ops.js new file mode 100644 index 0000000..9d629cd --- /dev/null +++ b/src/ops.js @@ -0,0 +1,130 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.REVERSE_OPS = exports.OPS = void 0; +const OPS = { + OP_FALSE: 0, + OP_0: 0, + OP_PUSHDATA1: 76, + OP_PUSHDATA2: 77, + OP_PUSHDATA4: 78, + OP_1NEGATE: 79, + OP_RESERVED: 80, + OP_TRUE: 81, + OP_1: 81, + OP_2: 82, + OP_3: 83, + OP_4: 84, + OP_5: 85, + OP_6: 86, + OP_7: 87, + OP_8: 88, + OP_9: 89, + OP_10: 90, + OP_11: 91, + OP_12: 92, + OP_13: 93, + OP_14: 94, + OP_15: 95, + OP_16: 96, + OP_NOP: 97, + OP_VER: 98, + OP_IF: 99, + OP_NOTIF: 100, + OP_VERIF: 101, + OP_VERNOTIF: 102, + OP_ELSE: 103, + OP_ENDIF: 104, + OP_VERIFY: 105, + OP_RETURN: 106, + OP_TOALTSTACK: 107, + OP_FROMALTSTACK: 108, + OP_2DROP: 109, + OP_2DUP: 110, + OP_3DUP: 111, + OP_2OVER: 112, + OP_2ROT: 113, + OP_2SWAP: 114, + OP_IFDUP: 115, + OP_DEPTH: 116, + OP_DROP: 117, + OP_DUP: 118, + OP_NIP: 119, + OP_OVER: 120, + OP_PICK: 121, + OP_ROLL: 122, + OP_ROT: 123, + OP_SWAP: 124, + OP_TUCK: 125, + OP_CAT: 126, + OP_SUBSTR: 127, + OP_LEFT: 128, + OP_RIGHT: 129, + OP_SIZE: 130, + OP_INVERT: 131, + OP_AND: 132, + OP_OR: 133, + OP_XOR: 134, + OP_EQUAL: 135, + OP_EQUALVERIFY: 136, + OP_RESERVED1: 137, + OP_RESERVED2: 138, + OP_1ADD: 139, + OP_1SUB: 140, + OP_2MUL: 141, + OP_2DIV: 142, + OP_NEGATE: 143, + OP_ABS: 144, + OP_NOT: 145, + OP_0NOTEQUAL: 146, + OP_ADD: 147, + OP_SUB: 148, + OP_MUL: 149, + OP_DIV: 150, + OP_MOD: 151, + OP_LSHIFT: 152, + OP_RSHIFT: 153, + OP_BOOLAND: 154, + OP_BOOLOR: 155, + OP_NUMEQUAL: 156, + OP_NUMEQUALVERIFY: 157, + OP_NUMNOTEQUAL: 158, + OP_LESSTHAN: 159, + OP_GREATERTHAN: 160, + OP_LESSTHANOREQUAL: 161, + OP_GREATERTHANOREQUAL: 162, + OP_MIN: 163, + OP_MAX: 164, + OP_WITHIN: 165, + OP_RIPEMD160: 166, + OP_SHA1: 167, + OP_SHA256: 168, + OP_HASH160: 169, + OP_HASH256: 170, + OP_CODESEPARATOR: 171, + OP_CHECKSIG: 172, + OP_CHECKSIGVERIFY: 173, + OP_CHECKMULTISIG: 174, + OP_CHECKMULTISIGVERIFY: 175, + OP_NOP1: 176, + OP_NOP2: 177, + OP_CHECKLOCKTIMEVERIFY: 177, + OP_NOP3: 178, + OP_CHECKSEQUENCEVERIFY: 178, + OP_NOP4: 179, + OP_NOP5: 180, + OP_NOP6: 181, + OP_NOP7: 182, + OP_NOP8: 183, + OP_NOP9: 184, + OP_NOP10: 185, + OP_PUBKEYHASH: 253, + OP_PUBKEY: 254, + OP_INVALIDOPCODE: 255, +}; +exports.OPS = OPS; +const REVERSE_OPS = {}; +exports.REVERSE_OPS = REVERSE_OPS; +for (const op of Object.keys(OPS)) { + const code = OPS[op]; + REVERSE_OPS[code] = op; +} diff --git a/types/payments/embed.d.ts b/src/payments/embed.d.ts similarity index 100% rename from types/payments/embed.d.ts rename to src/payments/embed.d.ts diff --git a/src/payments/embed.js b/src/payments/embed.js index 19df35d..4b7218f 100644 --- a/src/payments/embed.js +++ b/src/payments/embed.js @@ -1,9 +1,10 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.p2data = void 0; const networks_1 = require('../networks'); const bscript = require('../script'); +const types_1 = require('../types'); const lazy = require('./lazy'); -const typef = require('typeforce'); const OPS = bscript.OPS; function stacksEqual(a, b) { if (a.length !== b.length) return false; @@ -15,11 +16,13 @@ function stacksEqual(a, b) { function p2data(a, opts) { if (!a.data && !a.output) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); - typef( + (0, types_1.typeforce)( { - network: typef.maybe(typef.Object), - output: typef.maybe(typef.Buffer), - data: typef.maybe(typef.arrayOf(typef.Buffer)), + network: types_1.typeforce.maybe(types_1.typeforce.Object), + output: types_1.typeforce.maybe(types_1.typeforce.Buffer), + data: types_1.typeforce.maybe( + types_1.typeforce.arrayOf(types_1.typeforce.Buffer), + ), }, a, ); @@ -38,7 +41,7 @@ function p2data(a, opts) { if (a.output) { const chunks = bscript.decompile(a.output); if (chunks[0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid'); - if (!chunks.slice(1).every(typef.Buffer)) + if (!chunks.slice(1).every(types_1.typeforce.Buffer)) throw new TypeError('Output is invalid'); if (a.data && !stacksEqual(a.data, o.data)) throw new TypeError('Data mismatch'); diff --git a/types/payments/index.d.ts b/src/payments/index.d.ts similarity index 97% rename from types/payments/index.d.ts rename to src/payments/index.d.ts index 922e0bf..1edf071 100644 --- a/types/payments/index.d.ts +++ b/src/payments/index.d.ts @@ -1,3 +1,4 @@ +/// import { Network } from '../networks'; import { p2data as embed } from './embed'; import { p2ms } from './p2ms'; diff --git a/src/payments/index.js b/src/payments/index.js index ddab977..c23c529 100644 --- a/src/payments/index.js +++ b/src/payments/index.js @@ -1,18 +1,54 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0; const embed_1 = require('./embed'); -exports.embed = embed_1.p2data; +Object.defineProperty(exports, 'embed', { + enumerable: true, + get: function() { + return embed_1.p2data; + }, +}); const p2ms_1 = require('./p2ms'); -exports.p2ms = p2ms_1.p2ms; +Object.defineProperty(exports, 'p2ms', { + enumerable: true, + get: function() { + return p2ms_1.p2ms; + }, +}); const p2pk_1 = require('./p2pk'); -exports.p2pk = p2pk_1.p2pk; +Object.defineProperty(exports, 'p2pk', { + enumerable: true, + get: function() { + return p2pk_1.p2pk; + }, +}); const p2pkh_1 = require('./p2pkh'); -exports.p2pkh = p2pkh_1.p2pkh; +Object.defineProperty(exports, 'p2pkh', { + enumerable: true, + get: function() { + return p2pkh_1.p2pkh; + }, +}); const p2sh_1 = require('./p2sh'); -exports.p2sh = p2sh_1.p2sh; +Object.defineProperty(exports, 'p2sh', { + enumerable: true, + get: function() { + return p2sh_1.p2sh; + }, +}); const p2wpkh_1 = require('./p2wpkh'); -exports.p2wpkh = p2wpkh_1.p2wpkh; +Object.defineProperty(exports, 'p2wpkh', { + enumerable: true, + get: function() { + return p2wpkh_1.p2wpkh; + }, +}); const p2wsh_1 = require('./p2wsh'); -exports.p2wsh = p2wsh_1.p2wsh; +Object.defineProperty(exports, 'p2wsh', { + enumerable: true, + get: function() { + return p2wsh_1.p2wsh; + }, +}); // TODO // witness commitment diff --git a/types/payments/lazy.d.ts b/src/payments/lazy.d.ts similarity index 100% rename from types/payments/lazy.d.ts rename to src/payments/lazy.d.ts diff --git a/src/payments/lazy.js b/src/payments/lazy.js index 1a71521..e620c72 100644 --- a/src/payments/lazy.js +++ b/src/payments/lazy.js @@ -1,5 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.value = exports.prop = void 0; function prop(object, name, f) { Object.defineProperty(object, name, { configurable: true, diff --git a/types/payments/p2ms.d.ts b/src/payments/p2ms.d.ts similarity index 100% rename from types/payments/p2ms.d.ts rename to src/payments/p2ms.d.ts diff --git a/src/payments/p2ms.js b/src/payments/p2ms.js index 9fed788..0b7e72d 100644 --- a/src/payments/p2ms.js +++ b/src/payments/p2ms.js @@ -1,11 +1,11 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.p2ms = void 0; const networks_1 = require('../networks'); const bscript = require('../script'); +const types_1 = require('../types'); const lazy = require('./lazy'); const OPS = bscript.OPS; -const typef = require('typeforce'); -const ecc = require('tiny-secp256k1'); const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 function stacksEqual(a, b) { if (a.length !== b.length) return false; @@ -30,15 +30,19 @@ function p2ms(a, opts) { (opts.allowIncomplete && x === OPS.OP_0) !== undefined ); } - typef( + (0, types_1.typeforce)( { - network: typef.maybe(typef.Object), - m: typef.maybe(typef.Number), - n: typef.maybe(typef.Number), - output: typef.maybe(typef.Buffer), - pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), - signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), - input: typef.maybe(typef.Buffer), + network: types_1.typeforce.maybe(types_1.typeforce.Object), + m: types_1.typeforce.maybe(types_1.typeforce.Number), + n: types_1.typeforce.maybe(types_1.typeforce.Number), + output: types_1.typeforce.maybe(types_1.typeforce.Buffer), + pubkeys: types_1.typeforce.maybe( + types_1.typeforce.arrayOf(types_1.isPoint), + ), + signatures: types_1.typeforce.maybe( + types_1.typeforce.arrayOf(isAcceptableSignature), + ), + input: types_1.typeforce.maybe(types_1.typeforce.Buffer), }, a, ); @@ -101,14 +105,15 @@ function p2ms(a, opts) { if (opts.validate) { if (a.output) { decode(a.output); - if (!typef.Number(chunks[0])) throw new TypeError('Output is invalid'); - if (!typef.Number(chunks[chunks.length - 2])) + if (!types_1.typeforce.Number(chunks[0])) + throw new TypeError('Output is invalid'); + if (!types_1.typeforce.Number(chunks[chunks.length - 2])) throw new TypeError('Output is invalid'); if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) throw new TypeError('Output is invalid'); if (o.m <= 0 || o.n > 16 || o.m > o.n || o.n !== chunks.length - 3) throw new TypeError('Output is invalid'); - if (!o.pubkeys.every(x => ecc.isPoint(x))) + if (!o.pubkeys.every(x => (0, types_1.isPoint)(x))) throw new TypeError('Output is invalid'); if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch'); if (a.n !== undefined && a.n !== o.n) throw new TypeError('n mismatch'); diff --git a/types/payments/p2pk.d.ts b/src/payments/p2pk.d.ts similarity index 100% rename from types/payments/p2pk.d.ts rename to src/payments/p2pk.d.ts diff --git a/src/payments/p2pk.js b/src/payments/p2pk.js index 702669e..2849530 100644 --- a/src/payments/p2pk.js +++ b/src/payments/p2pk.js @@ -1,24 +1,24 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.p2pk = void 0; const networks_1 = require('../networks'); const bscript = require('../script'); +const types_1 = require('../types'); const lazy = require('./lazy'); -const typef = require('typeforce'); const OPS = bscript.OPS; -const ecc = require('tiny-secp256k1'); // input: {signature} // output: {pubKey} OP_CHECKSIG function p2pk(a, opts) { if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); - typef( + (0, types_1.typeforce)( { - network: typef.maybe(typef.Object), - output: typef.maybe(typef.Buffer), - pubkey: typef.maybe(ecc.isPoint), - signature: typef.maybe(bscript.isCanonicalScriptSignature), - input: typef.maybe(typef.Buffer), + network: types_1.typeforce.maybe(types_1.typeforce.Object), + output: types_1.typeforce.maybe(types_1.typeforce.Buffer), + pubkey: types_1.typeforce.maybe(types_1.isPoint), + signature: types_1.typeforce.maybe(bscript.isCanonicalScriptSignature), + input: types_1.typeforce.maybe(types_1.typeforce.Buffer), }, a, ); @@ -52,7 +52,7 @@ function p2pk(a, opts) { if (a.output) { if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) throw new TypeError('Output is invalid'); - if (!ecc.isPoint(o.pubkey)) + if (!(0, types_1.isPoint)(o.pubkey)) throw new TypeError('Output pubkey is invalid'); if (a.pubkey && !a.pubkey.equals(o.pubkey)) throw new TypeError('Pubkey mismatch'); diff --git a/types/payments/p2pkh.d.ts b/src/payments/p2pkh.d.ts similarity index 100% rename from types/payments/p2pkh.d.ts rename to src/payments/p2pkh.d.ts diff --git a/src/payments/p2pkh.js b/src/payments/p2pkh.js index 94c801f..8edc8ba 100644 --- a/src/payments/p2pkh.js +++ b/src/payments/p2pkh.js @@ -1,28 +1,28 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.p2pkh = void 0; const bcrypto = require('../crypto'); const networks_1 = require('../networks'); const bscript = require('../script'); +const types_1 = require('../types'); const lazy = require('./lazy'); -const typef = require('typeforce'); -const OPS = bscript.OPS; -const ecc = require('tiny-secp256k1'); const bs58check = require('bs58check'); +const OPS = bscript.OPS; // input: {signature} {pubkey} // output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG function p2pkh(a, opts) { if (!a.address && !a.hash && !a.output && !a.pubkey && !a.input) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); - typef( + (0, types_1.typeforce)( { - network: typef.maybe(typef.Object), - address: typef.maybe(typef.String), - hash: typef.maybe(typef.BufferN(20)), - output: typef.maybe(typef.BufferN(25)), - pubkey: typef.maybe(ecc.isPoint), - signature: typef.maybe(bscript.isCanonicalScriptSignature), - input: typef.maybe(typef.Buffer), + network: types_1.typeforce.maybe(types_1.typeforce.Object), + address: types_1.typeforce.maybe(types_1.typeforce.String), + hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(20)), + output: types_1.typeforce.maybe(types_1.typeforce.BufferN(25)), + pubkey: types_1.typeforce.maybe(types_1.isPoint), + signature: types_1.typeforce.maybe(bscript.isCanonicalScriptSignature), + input: types_1.typeforce.maybe(types_1.typeforce.Buffer), }, a, ); @@ -116,7 +116,7 @@ function p2pkh(a, opts) { if (chunks.length !== 2) throw new TypeError('Input is invalid'); if (!bscript.isCanonicalScriptSignature(chunks[0])) throw new TypeError('Input has invalid signature'); - if (!ecc.isPoint(chunks[1])) + if (!(0, types_1.isPoint)(chunks[1])) throw new TypeError('Input has invalid pubkey'); if (a.signature && !a.signature.equals(chunks[0])) throw new TypeError('Signature mismatch'); diff --git a/types/payments/p2sh.d.ts b/src/payments/p2sh.d.ts similarity index 100% rename from types/payments/p2sh.d.ts rename to src/payments/p2sh.d.ts diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index 0f46403..8710bf1 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -1,12 +1,13 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.p2sh = void 0; const bcrypto = require('../crypto'); const networks_1 = require('../networks'); const bscript = require('../script'); +const types_1 = require('../types'); const lazy = require('./lazy'); -const typef = require('typeforce'); -const OPS = bscript.OPS; const bs58check = require('bs58check'); +const OPS = bscript.OPS; function stacksEqual(a, b) { if (a.length !== b.length) return false; return a.every((x, i) => { @@ -20,20 +21,24 @@ function p2sh(a, opts) { if (!a.address && !a.hash && !a.output && !a.redeem && !a.input) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); - typef( + (0, types_1.typeforce)( { - network: typef.maybe(typef.Object), - address: typef.maybe(typef.String), - hash: typef.maybe(typef.BufferN(20)), - output: typef.maybe(typef.BufferN(23)), - redeem: typef.maybe({ - network: typef.maybe(typef.Object), - output: typef.maybe(typef.Buffer), - input: typef.maybe(typef.Buffer), - witness: typef.maybe(typef.arrayOf(typef.Buffer)), + network: types_1.typeforce.maybe(types_1.typeforce.Object), + address: types_1.typeforce.maybe(types_1.typeforce.String), + hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(20)), + output: types_1.typeforce.maybe(types_1.typeforce.BufferN(23)), + redeem: types_1.typeforce.maybe({ + network: types_1.typeforce.maybe(types_1.typeforce.Object), + output: types_1.typeforce.maybe(types_1.typeforce.Buffer), + input: types_1.typeforce.maybe(types_1.typeforce.Buffer), + witness: types_1.typeforce.maybe( + types_1.typeforce.arrayOf(types_1.typeforce.Buffer), + ), }), - input: typef.maybe(typef.Buffer), - witness: typef.maybe(typef.arrayOf(typef.Buffer)), + input: types_1.typeforce.maybe(types_1.typeforce.Buffer), + witness: types_1.typeforce.maybe( + types_1.typeforce.arrayOf(types_1.typeforce.Buffer), + ), }, a, ); diff --git a/types/payments/p2wpkh.d.ts b/src/payments/p2wpkh.d.ts similarity index 100% rename from types/payments/p2wpkh.d.ts rename to src/payments/p2wpkh.d.ts diff --git a/src/payments/p2wpkh.js b/src/payments/p2wpkh.js index 9e9e685..168e08f 100644 --- a/src/payments/p2wpkh.js +++ b/src/payments/p2wpkh.js @@ -1,13 +1,13 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.p2wpkh = void 0; const bcrypto = require('../crypto'); const networks_1 = require('../networks'); const bscript = require('../script'); +const types_1 = require('../types'); const lazy = require('./lazy'); -const typef = require('typeforce'); +const bech32_1 = require('bech32'); const OPS = bscript.OPS; -const ecc = require('tiny-secp256k1'); -const { bech32 } = require('bech32'); const EMPTY_BUFFER = Buffer.alloc(0); // witness: {signature} {pubKey} // input: <> @@ -16,23 +16,25 @@ function p2wpkh(a, opts) { if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); - typef( + (0, types_1.typeforce)( { - address: typef.maybe(typef.String), - hash: typef.maybe(typef.BufferN(20)), - input: typef.maybe(typef.BufferN(0)), - network: typef.maybe(typef.Object), - output: typef.maybe(typef.BufferN(22)), - pubkey: typef.maybe(ecc.isPoint), - signature: typef.maybe(bscript.isCanonicalScriptSignature), - witness: typef.maybe(typef.arrayOf(typef.Buffer)), + address: types_1.typeforce.maybe(types_1.typeforce.String), + hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(20)), + input: types_1.typeforce.maybe(types_1.typeforce.BufferN(0)), + network: types_1.typeforce.maybe(types_1.typeforce.Object), + output: types_1.typeforce.maybe(types_1.typeforce.BufferN(22)), + pubkey: types_1.typeforce.maybe(types_1.isPoint), + signature: types_1.typeforce.maybe(bscript.isCanonicalScriptSignature), + witness: types_1.typeforce.maybe( + types_1.typeforce.arrayOf(types_1.typeforce.Buffer), + ), }, a, ); const _address = lazy.value(() => { - const result = bech32.decode(a.address); + const result = bech32_1.bech32.decode(a.address); const version = result.words.shift(); - const data = bech32.fromWords(result.words); + const data = bech32_1.bech32.fromWords(result.words); return { version, prefix: result.prefix, @@ -43,9 +45,9 @@ function p2wpkh(a, opts) { const o = { name: 'p2wpkh', network }; lazy.prop(o, 'address', () => { if (!o.hash) return; - const words = bech32.toWords(o.hash); + const words = bech32_1.bech32.toWords(o.hash); words.unshift(0x00); - return bech32.encode(network.bech32, words); + return bech32_1.bech32.encode(network.bech32, words); }); lazy.prop(o, 'hash', () => { if (a.output) return a.output.slice(2, 22); @@ -107,14 +109,14 @@ function p2wpkh(a, opts) { if (hash.length > 0 && !hash.equals(pkh)) throw new TypeError('Hash mismatch'); else hash = pkh; - if (!ecc.isPoint(a.pubkey) || a.pubkey.length !== 33) + if (!(0, types_1.isPoint)(a.pubkey) || a.pubkey.length !== 33) throw new TypeError('Invalid pubkey for p2wpkh'); } if (a.witness) { if (a.witness.length !== 2) throw new TypeError('Witness is invalid'); if (!bscript.isCanonicalScriptSignature(a.witness[0])) throw new TypeError('Witness has invalid signature'); - if (!ecc.isPoint(a.witness[1]) || a.witness[1].length !== 33) + if (!(0, types_1.isPoint)(a.witness[1]) || a.witness[1].length !== 33) throw new TypeError('Witness has invalid pubkey'); if (a.signature && !a.signature.equals(a.witness[0])) throw new TypeError('Signature mismatch'); diff --git a/types/payments/p2wsh.d.ts b/src/payments/p2wsh.d.ts similarity index 100% rename from types/payments/p2wsh.d.ts rename to src/payments/p2wsh.d.ts diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js index 20432a9..66ee1da 100644 --- a/src/payments/p2wsh.js +++ b/src/payments/p2wsh.js @@ -1,13 +1,13 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.p2wsh = void 0; const bcrypto = require('../crypto'); const networks_1 = require('../networks'); const bscript = require('../script'); +const types_1 = require('../types'); const lazy = require('./lazy'); -const typef = require('typeforce'); +const bech32_1 = require('bech32'); const OPS = bscript.OPS; -const ecc = require('tiny-secp256k1'); -const { bech32 } = require('bech32'); const EMPTY_BUFFER = Buffer.alloc(0); function stacksEqual(a, b) { if (a.length !== b.length) return false; @@ -20,7 +20,7 @@ function chunkHasUncompressedPubkey(chunk) { Buffer.isBuffer(chunk) && chunk.length === 65 && chunk[0] === 0x04 && - ecc.isPoint(chunk) + (0, types_1.isPoint)(chunk) ) { return true; } else { @@ -34,27 +34,31 @@ function p2wsh(a, opts) { if (!a.address && !a.hash && !a.output && !a.redeem && !a.witness) throw new TypeError('Not enough data'); opts = Object.assign({ validate: true }, opts || {}); - typef( + (0, types_1.typeforce)( { - network: typef.maybe(typef.Object), - address: typef.maybe(typef.String), - hash: typef.maybe(typef.BufferN(32)), - output: typef.maybe(typef.BufferN(34)), - redeem: typef.maybe({ - input: typef.maybe(typef.Buffer), - network: typef.maybe(typef.Object), - output: typef.maybe(typef.Buffer), - witness: typef.maybe(typef.arrayOf(typef.Buffer)), + network: types_1.typeforce.maybe(types_1.typeforce.Object), + address: types_1.typeforce.maybe(types_1.typeforce.String), + hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(32)), + output: types_1.typeforce.maybe(types_1.typeforce.BufferN(34)), + redeem: types_1.typeforce.maybe({ + input: types_1.typeforce.maybe(types_1.typeforce.Buffer), + network: types_1.typeforce.maybe(types_1.typeforce.Object), + output: types_1.typeforce.maybe(types_1.typeforce.Buffer), + witness: types_1.typeforce.maybe( + types_1.typeforce.arrayOf(types_1.typeforce.Buffer), + ), }), - input: typef.maybe(typef.BufferN(0)), - witness: typef.maybe(typef.arrayOf(typef.Buffer)), + input: types_1.typeforce.maybe(types_1.typeforce.BufferN(0)), + witness: types_1.typeforce.maybe( + types_1.typeforce.arrayOf(types_1.typeforce.Buffer), + ), }, a, ); const _address = lazy.value(() => { - const result = bech32.decode(a.address); + const result = bech32_1.bech32.decode(a.address); const version = result.words.shift(); - const data = bech32.fromWords(result.words); + const data = bech32_1.bech32.fromWords(result.words); return { version, prefix: result.prefix, @@ -71,9 +75,9 @@ function p2wsh(a, opts) { const o = { network }; lazy.prop(o, 'address', () => { if (!o.hash) return; - const words = bech32.toWords(o.hash); + const words = bech32_1.bech32.toWords(o.hash); words.unshift(0x00); - return bech32.encode(network.bech32, words); + return bech32_1.bech32.encode(network.bech32, words); }); lazy.prop(o, 'hash', () => { if (a.output) return a.output.slice(2); diff --git a/types/psbt.d.ts b/src/psbt.d.ts similarity index 88% rename from types/psbt.d.ts rename to src/psbt.d.ts index e7a79eb..8603a69 100644 --- a/types/psbt.d.ts +++ b/src/psbt.d.ts @@ -1,6 +1,6 @@ +/// import { Psbt as PsbtBase } from 'bip174'; import { KeyValue, PsbtGlobalUpdate, PsbtInput, PsbtInputUpdate, PsbtOutput, PsbtOutputUpdate } from 'bip174/src/lib/interfaces'; -import { Signer, SignerAsync } from './ecpair'; import { Network } from './networks'; import { Transaction } from './transaction'; export interface TransactionInput { @@ -18,6 +18,7 @@ export interface TransactionOutput { export interface PsbtTxOutput extends TransactionOutput { address: string | undefined; } +export declare type ValidateSigFunction = (pubkey: Buffer, msghash: Buffer, signature: Buffer) => boolean; /** * Psbt class can parse and generate a PSBT binary based off of the BIP174. * There are 6 roles that this class fulfills. (Explained in BIP174) @@ -58,11 +59,13 @@ export declare class Psbt { private __CACHE; private opts; constructor(opts?: PsbtOptsOptional, data?: PsbtBase); - readonly inputCount: number; - version: number; - locktime: number; - readonly txInputs: PsbtTxInput[]; - readonly txOutputs: PsbtTxOutput[]; + get inputCount(): number; + get version(): number; + set version(version: number); + get locktime(): number; + set locktime(locktime: number); + get txInputs(): PsbtTxInput[]; + get txOutputs(): PsbtTxOutput[]; combine(...those: Psbt[]): this; clone(): Psbt; setMaximumFeeRate(satoshiPerByte: number): void; @@ -83,8 +86,8 @@ export declare class Psbt { inputHasHDKey(inputIndex: number, root: HDSigner): boolean; outputHasPubkey(outputIndex: number, pubkey: Buffer): boolean; outputHasHDKey(outputIndex: number, root: HDSigner): boolean; - validateSignaturesOfAllInputs(): boolean; - validateSignaturesOfInput(inputIndex: number, pubkey?: Buffer): boolean; + validateSignaturesOfAllInputs(validator: ValidateSigFunction): boolean; + validateSignaturesOfInput(inputIndex: number, validator: ValidateSigFunction, pubkey?: Buffer): boolean; signAllInputsHD(hdKeyPair: HDSigner, sighashTypes?: number[]): this; signAllInputsHDAsync(hdKeyPair: HDSigner | HDSignerAsync, sighashTypes?: number[]): Promise; signInputHD(inputIndex: number, hdKeyPair: HDSigner, sighashTypes?: number[]): this; @@ -129,7 +132,7 @@ interface HDSignerBase { */ fingerprint: Buffer; } -interface HDSigner extends HDSignerBase { +export interface HDSigner extends HDSignerBase { /** * The path string must match /^m(\/\d+'?)+$/ * ex. m/44'/0'/0'/1/23 levels with ' must be hard derivations @@ -144,10 +147,22 @@ interface HDSigner extends HDSignerBase { /** * Same as above but with async sign method */ -interface HDSignerAsync extends HDSignerBase { +export interface HDSignerAsync extends HDSignerBase { derivePath(path: string): HDSignerAsync; sign(hash: Buffer): Promise; } +export interface Signer { + publicKey: Buffer; + network?: any; + sign(hash: Buffer, lowR?: boolean): Buffer; + getPublicKey?(): Buffer; +} +export interface SignerAsync { + publicKey: Buffer; + network?: any; + sign(hash: Buffer, lowR?: boolean): Promise; + getPublicKey?(): Buffer; +} /** * This function must do two things: * 1. Check if the `input` can be finalized. If it can not be finalized, throw. diff --git a/src/psbt.js b/src/psbt.js index 6381669..84aa5b8 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1,12 +1,12 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.Psbt = void 0; const bip174_1 = require('bip174'); const varuint = require('bip174/src/lib/converter/varint'); const utils_1 = require('bip174/src/lib/utils'); const address_1 = require('./address'); const bufferutils_1 = require('./bufferutils'); const crypto_1 = require('./crypto'); -const ecpair_1 = require('./ecpair'); const networks_1 = require('./networks'); const payments = require('./payments'); const bscript = require('./script'); @@ -25,7 +25,7 @@ const DEFAULT_OPTS = { * THIS IS NOT TO BE RELIED ON. * It is only here as a last ditch effort to prevent sending a 500 BTC fee etc. */ - maximumFeeRate: 5000, + maximumFeeRate: 5000, // satoshi per byte }; /** * Psbt class can parse and generate a PSBT binary based off of the BIP174. @@ -60,6 +60,23 @@ const DEFAULT_OPTS = { * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. */ class Psbt { + data; + static fromBase64(data, opts = {}) { + const buffer = Buffer.from(data, 'base64'); + return this.fromBuffer(buffer, opts); + } + static fromHex(data, opts = {}) { + const buffer = Buffer.from(data, 'hex'); + return this.fromBuffer(buffer, opts); + } + static fromBuffer(buffer, opts = {}) { + const psbtBase = bip174_1.Psbt.fromBuffer(buffer, transactionFromBuffer); + const psbt = new Psbt(opts, psbtBase); + checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE); + return psbt; + } + __CACHE; + opts; constructor(opts = {}, data = new bip174_1.Psbt(new PsbtTransaction())) { this.data = data; // set defaults @@ -69,6 +86,8 @@ class Psbt { __NON_WITNESS_UTXO_BUF_CACHE: [], __TX_IN_CACHE: {}, __TX: this.data.globalMap.unsignedTx.tx, + // Psbt's predecesor (TransactionBuilder - now removed) behavior + // was to not confirm input values before signing. // Even though we highly encourage people to get // the full parent transaction to verify values, the ability to // sign non-segwit inputs without the full transaction was often @@ -87,20 +106,6 @@ class Psbt { dpew(this, '__CACHE', false, true); dpew(this, 'opts', false, true); } - static fromBase64(data, opts = {}) { - const buffer = Buffer.from(data, 'base64'); - return this.fromBuffer(buffer, opts); - } - static fromHex(data, opts = {}) { - const buffer = Buffer.from(data, 'hex'); - return this.fromBuffer(buffer, opts); - } - static fromBuffer(buffer, opts = {}) { - const psbtBase = bip174_1.Psbt.fromBuffer(buffer, transactionFromBuffer); - const psbt = new Psbt(opts, psbtBase); - checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE); - return psbt; - } get inputCount() { return this.data.inputs.length; } @@ -118,7 +123,7 @@ class Psbt { } get txInputs() { return this.__CACHE.__TX.ins.map(input => ({ - hash: bufferutils_1.cloneBuffer(input.hash), + hash: (0, bufferutils_1.cloneBuffer)(input.hash), index: input.index, sequence: input.sequence, })); @@ -127,10 +132,13 @@ class Psbt { return this.__CACHE.__TX.outs.map(output => { let address; try { - address = address_1.fromOutputScript(output.script, this.opts.network); + address = (0, address_1.fromOutputScript)( + output.script, + this.opts.network, + ); } catch (_) {} return { - script: bufferutils_1.cloneBuffer(output.script), + script: (0, bufferutils_1.cloneBuffer)(output.script), value: output.value, address, }; @@ -229,7 +237,7 @@ class Psbt { const { address } = outputData; if (typeof address === 'string') { const { network } = this.opts; - const script = address_1.toOutputScript(address, network); + const script = (0, address_1.toOutputScript)(address, network); outputData = Object.assign(outputData, { script }); } const c = this.__CACHE; @@ -262,12 +270,12 @@ class Psbt { return getTxCacheValue('__FEE', 'fee', this.data.inputs, this.__CACHE); } finalizeAllInputs() { - utils_1.checkForInput(this.data.inputs, 0); // making sure we have at least one + (0, utils_1.checkForInput)(this.data.inputs, 0); // making sure we have at least one range(this.data.inputs.length).forEach(idx => this.finalizeInput(idx)); return this; } finalizeInput(inputIndex, finalScriptsFunc = getFinalScripts) { - const input = utils_1.checkForInput(this.data.inputs, inputIndex); + const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput( inputIndex, input, @@ -292,7 +300,7 @@ class Psbt { return this; } getInputType(inputIndex) { - const input = utils_1.checkForInput(this.data.inputs, inputIndex); + const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); const script = getScriptFromUtxo(inputIndex, input, this.__CACHE); const result = getMeaningfulScript( script, @@ -307,39 +315,41 @@ class Psbt { return type + mainType; } inputHasPubkey(inputIndex, pubkey) { - const input = utils_1.checkForInput(this.data.inputs, inputIndex); + const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE); } inputHasHDKey(inputIndex, root) { - const input = utils_1.checkForInput(this.data.inputs, inputIndex); + const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex); const derivationIsMine = bip32DerivationIsMine(root); return ( !!input.bip32Derivation && input.bip32Derivation.some(derivationIsMine) ); } outputHasPubkey(outputIndex, pubkey) { - const output = utils_1.checkForOutput(this.data.outputs, outputIndex); + const output = (0, utils_1.checkForOutput)(this.data.outputs, outputIndex); return pubkeyInOutput(pubkey, output, outputIndex, this.__CACHE); } outputHasHDKey(outputIndex, root) { - const output = utils_1.checkForOutput(this.data.outputs, outputIndex); + const output = (0, utils_1.checkForOutput)(this.data.outputs, outputIndex); const derivationIsMine = bip32DerivationIsMine(root); return ( !!output.bip32Derivation && output.bip32Derivation.some(derivationIsMine) ); } - validateSignaturesOfAllInputs() { - utils_1.checkForInput(this.data.inputs, 0); // making sure we have at least one + validateSignaturesOfAllInputs(validator) { + (0, utils_1.checkForInput)(this.data.inputs, 0); // making sure we have at least one const results = range(this.data.inputs.length).map(idx => - this.validateSignaturesOfInput(idx), + this.validateSignaturesOfInput(idx, validator), ); return results.reduce((final, res) => res === true && final, true); } - validateSignaturesOfInput(inputIndex, pubkey) { + validateSignaturesOfInput(inputIndex, validator, pubkey) { const input = this.data.inputs[inputIndex]; const partialSig = (input || {}).partialSig; if (!input || !partialSig || partialSig.length < 1) throw new Error('No signatures to validate'); + if (typeof validator !== 'function') + throw new Error('Need validator function to validate signatures'); const mySigs = pubkey ? partialSig.filter(sig => sig.pubkey.equals(pubkey)) : partialSig; @@ -363,8 +373,7 @@ class Psbt { hashCache = hash; scriptCache = script; checkScriptForPubkey(pSig.pubkey, script, 'verify'); - const keypair = ecpair_1.fromPublicKey(pSig.pubkey); - results.push(keypair.verify(hash, sig.signature)); + results.push(validator(pSig.pubkey, hash, sig.signature)); } return results.every(res => res === true); } @@ -616,6 +625,7 @@ const transactionFromBuffer = buffer => new PsbtTransaction(buffer); * It contains a bitcoinjs-lib Transaction object. */ class PsbtTransaction { + tx; constructor(buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0])) { this.tx = transaction_1.Transaction.fromBuffer(buffer); checkTxEmpty(this.tx); @@ -641,7 +651,7 @@ class PsbtTransaction { } const hash = typeof input.hash === 'string' - ? bufferutils_1.reverseBuffer(Buffer.from(input.hash, 'hex')) + ? (0, bufferutils_1.reverseBuffer)(Buffer.from(input.hash, 'hex')) : input.hash; this.tx.addInput(hash, input.index, input.sequence); } @@ -684,8 +694,7 @@ function hasSigs(neededSigs, partialSig, pubkeys) { if (pubkeys) { sigs = pubkeys .map(pkey => { - const pubkey = ecpair_1.fromPublicKey(pkey, { compressed: true }) - .publicKey; + const pubkey = compressPubkey(pkey); return partialSig.find(pSig => pSig.pubkey.equals(pubkey)); }) .filter(v => !!v); @@ -816,7 +825,7 @@ function checkTxForDupeIns(tx, cache) { } function checkTxInputCache(cache, input) { const key = - bufferutils_1.reverseBuffer(Buffer.from(input.hash)).toString('hex') + + (0, bufferutils_1.reverseBuffer)(Buffer.from(input.hash)).toString('hex') + ':' + input.index; if (cache.__TX_IN_CACHE[key]) throw new Error('Duplicate input detected.'); @@ -911,7 +920,7 @@ function getHashAndSighashType( cache, sighashTypes, ) { - const input = utils_1.checkForInput(inputs, inputIndex); + const input = (0, utils_1.checkForInput)(inputs, inputIndex); const { hash, sighashType, script } = getHashForSig( inputIndex, input, @@ -997,7 +1006,8 @@ function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) { console.warn( 'Warning: Signing non-segwit inputs without the full parent transaction ' + 'means there is a chance that a miner could feed you incorrect information ' + - 'to trick you into paying large fees. You are not ' + + "to trick you into paying large fees. This behavior is the same as Psbt's predecesor " + + '(TransactionBuilder - now removed) when signing non-segwit scripts. You are not ' + 'able to export this Psbt with toBuffer|toBase64|toHex since it is not ' + 'BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n' + '*********************', @@ -1094,7 +1104,7 @@ function getScriptFromInput(inputIndex, input, cache) { return res; } function getSignersFromHD(inputIndex, inputs, hdKeyPair) { - const input = utils_1.checkForInput(inputs, inputIndex); + const input = (0, utils_1.checkForInput)(inputs, inputIndex); if (!input.bip32Derivation || input.bip32Derivation.length === 0) { throw new Error('Need bip32Derivation to sign with HD'); } @@ -1321,6 +1331,15 @@ function redeemFromFinalWitnessScript(finalScript) { if (!sDecomp) return; return lastItem; } +function compressPubkey(pubkey) { + if (pubkey.length === 65) { + const parity = pubkey[64] & 1; + const newKey = pubkey.slice(0, 33); + newKey[0] = 2 | parity; + return newKey; + } + return pubkey.slice(); +} function isPubkeyLike(buf) { return buf.length === 33 && bscript.isCanonicalPubKey(buf); } @@ -1376,7 +1395,7 @@ function checkInvalidP2WSH(script) { } } function pubkeyInScript(pubkey, script) { - const pubkeyHash = crypto_1.hash160(pubkey); + const pubkeyHash = (0, crypto_1.hash160)(pubkey); const decompiled = bscript.decompile(script); if (decompiled === null) throw new Error('Unknown script error'); return decompiled.some(element => { diff --git a/src/push_data.d.ts b/src/push_data.d.ts new file mode 100644 index 0000000..07c2f91 --- /dev/null +++ b/src/push_data.d.ts @@ -0,0 +1,8 @@ +/// +export declare function encodingLength(i: number): number; +export declare function encode(buffer: Buffer, num: number, offset: number): number; +export declare function decode(buffer: Buffer, offset: number): { + opcode: number; + number: number; + size: number; +} | null; diff --git a/src/push_data.js b/src/push_data.js new file mode 100644 index 0000000..16b6147 --- /dev/null +++ b/src/push_data.js @@ -0,0 +1,61 @@ +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +exports.decode = exports.encode = exports.encodingLength = void 0; +const ops_1 = require('./ops'); +function encodingLength(i) { + return i < ops_1.OPS.OP_PUSHDATA1 ? 1 : i <= 0xff ? 2 : i <= 0xffff ? 3 : 5; +} +exports.encodingLength = encodingLength; +function encode(buffer, num, offset) { + const size = encodingLength(num); + // ~6 bit + if (size === 1) { + buffer.writeUInt8(num, offset); + // 8 bit + } else if (size === 2) { + buffer.writeUInt8(ops_1.OPS.OP_PUSHDATA1, offset); + buffer.writeUInt8(num, offset + 1); + // 16 bit + } else if (size === 3) { + buffer.writeUInt8(ops_1.OPS.OP_PUSHDATA2, offset); + buffer.writeUInt16LE(num, offset + 1); + // 32 bit + } else { + buffer.writeUInt8(ops_1.OPS.OP_PUSHDATA4, offset); + buffer.writeUInt32LE(num, offset + 1); + } + return size; +} +exports.encode = encode; +function decode(buffer, offset) { + const opcode = buffer.readUInt8(offset); + let num; + let size; + // ~6 bit + if (opcode < ops_1.OPS.OP_PUSHDATA1) { + num = opcode; + size = 1; + // 8 bit + } else if (opcode === ops_1.OPS.OP_PUSHDATA1) { + if (offset + 2 > buffer.length) return null; + num = buffer.readUInt8(offset + 1); + size = 2; + // 16 bit + } else if (opcode === ops_1.OPS.OP_PUSHDATA2) { + if (offset + 3 > buffer.length) return null; + num = buffer.readUInt16LE(offset + 1); + size = 3; + // 32 bit + } else { + if (offset + 5 > buffer.length) return null; + if (opcode !== ops_1.OPS.OP_PUSHDATA4) throw new Error('Unexpected opcode'); + num = buffer.readUInt32LE(offset + 1); + size = 5; + } + return { + opcode, + number: num, + size, + }; +} +exports.decode = decode; diff --git a/types/script.d.ts b/src/script.d.ts similarity index 90% rename from types/script.d.ts rename to src/script.d.ts index 4b04615..261ecf4 100644 --- a/types/script.d.ts +++ b/src/script.d.ts @@ -1,10 +1,9 @@ +/// +import { OPS } from './ops'; import { Stack } from './payments'; import * as scriptNumber from './script_number'; import * as scriptSignature from './script_signature'; -export declare type OpCode = number; -export declare const OPS: { - [index: string]: number; -}; +export { OPS }; export declare function isPushOnly(value: Stack): boolean; export declare function compile(chunks: Buffer | Stack): Buffer; export declare function decompile(buffer: Buffer | Array): Array | null; diff --git a/src/script.js b/src/script.js index 39859dc..5e5e17b 100644 --- a/src/script.js +++ b/src/script.js @@ -1,21 +1,26 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.signature = exports.number = exports.isCanonicalScriptSignature = exports.isDefinedHashType = exports.isCanonicalPubKey = exports.toStack = exports.fromASM = exports.toASM = exports.decompile = exports.compile = exports.isPushOnly = exports.OPS = void 0; +const bip66 = require('./bip66'); +const ops_1 = require('./ops'); +Object.defineProperty(exports, 'OPS', { + enumerable: true, + get: function() { + return ops_1.OPS; + }, +}); +const pushdata = require('./push_data'); const scriptNumber = require('./script_number'); const scriptSignature = require('./script_signature'); const types = require('./types'); -const bip66 = require('bip66'); -const ecc = require('tiny-secp256k1'); -const pushdata = require('pushdata-bitcoin'); -const typeforce = require('typeforce'); -exports.OPS = require('bitcoin-ops'); -const REVERSE_OPS = require('bitcoin-ops/map'); -const OP_INT_BASE = exports.OPS.OP_RESERVED; // OP_1 - 1 +const { typeforce } = types; +const OP_INT_BASE = ops_1.OPS.OP_RESERVED; // OP_1 - 1 function isOPInt(value) { return ( types.Number(value) && - (value === exports.OPS.OP_0 || - (value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) || - value === exports.OPS.OP_1NEGATE) + (value === ops_1.OPS.OP_0 || + (value >= ops_1.OPS.OP_1 && value <= ops_1.OPS.OP_16) || + value === ops_1.OPS.OP_1NEGATE) ); } function isPushOnlyChunk(value) { @@ -26,10 +31,10 @@ function isPushOnly(value) { } exports.isPushOnly = isPushOnly; function asMinimalOP(buffer) { - if (buffer.length === 0) return exports.OPS.OP_0; + if (buffer.length === 0) return ops_1.OPS.OP_0; if (buffer.length !== 1) return; if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]; - if (buffer[0] === 0x81) return exports.OPS.OP_1NEGATE; + if (buffer[0] === 0x81) return ops_1.OPS.OP_1NEGATE; } function chunksIsBuffer(buf) { return Buffer.isBuffer(buf); @@ -90,7 +95,7 @@ function decompile(buffer) { while (i < buffer.length) { const opcode = buffer[i]; // data chunk - if (opcode > exports.OPS.OP_0 && opcode <= exports.OPS.OP_PUSHDATA4) { + if (opcode > ops_1.OPS.OP_0 && opcode <= ops_1.OPS.OP_PUSHDATA4) { const d = pushdata.decode(buffer, i); // did reading a pushDataInt fail? if (d === null) return null; @@ -128,7 +133,7 @@ function toASM(chunks) { chunk = op; } // opcode! - return REVERSE_OPS[chunk]; + return ops_1.REVERSE_OPS[chunk]; }) .join(' '); } @@ -138,7 +143,7 @@ function fromASM(asm) { return compile( asm.split(' ').map(chunkStr => { // opcode? - if (exports.OPS[chunkStr] !== undefined) return exports.OPS[chunkStr]; + if (ops_1.OPS[chunkStr] !== undefined) return ops_1.OPS[chunkStr]; typeforce(types.Hex, chunkStr); // data! return Buffer.from(chunkStr, 'hex'); @@ -151,13 +156,13 @@ function toStack(chunks) { typeforce(isPushOnly, chunks); return chunks.map(op => { if (singleChunkIsBuffer(op)) return op; - if (op === exports.OPS.OP_0) return Buffer.allocUnsafe(0); + if (op === ops_1.OPS.OP_0) return Buffer.allocUnsafe(0); return scriptNumber.encode(op - OP_INT_BASE); }); } exports.toStack = toStack; function isCanonicalPubKey(buffer) { - return ecc.isPoint(buffer); + return types.isPoint(buffer); } exports.isCanonicalPubKey = isCanonicalPubKey; function isDefinedHashType(hashType) { diff --git a/types/script_number.d.ts b/src/script_number.d.ts similarity index 83% rename from types/script_number.d.ts rename to src/script_number.d.ts index cf535fc..015bb89 100644 --- a/types/script_number.d.ts +++ b/src/script_number.d.ts @@ -1,2 +1,3 @@ +/// export declare function decode(buffer: Buffer, maxLength?: number, minimal?: boolean): number; export declare function encode(_number: number): Buffer; diff --git a/src/script_number.js b/src/script_number.js index 3f313af..8220a10 100644 --- a/src/script_number.js +++ b/src/script_number.js @@ -1,5 +1,6 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.encode = exports.decode = void 0; function decode(buffer, maxLength, minimal) { maxLength = maxLength || 4; minimal = minimal === undefined ? true : minimal; diff --git a/types/script_signature.d.ts b/src/script_signature.d.ts similarity index 88% rename from types/script_signature.d.ts rename to src/script_signature.d.ts index fbf18d5..2057dd9 100644 --- a/types/script_signature.d.ts +++ b/src/script_signature.d.ts @@ -1,3 +1,4 @@ +/// interface ScriptSignature { signature: Buffer; hashType: number; diff --git a/src/script_signature.js b/src/script_signature.js index fb52fe9..638e5f2 100644 --- a/src/script_signature.js +++ b/src/script_signature.js @@ -1,8 +1,9 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.encode = exports.decode = void 0; +const bip66 = require('./bip66'); const types = require('./types'); -const bip66 = require('bip66'); -const typeforce = require('typeforce'); +const { typeforce } = types; const ZERO = Buffer.alloc(1, 0); function toDER(x) { let i = 0; diff --git a/types/transaction.d.ts b/src/transaction.d.ts similarity index 98% rename from types/transaction.d.ts rename to src/transaction.d.ts index 6846ea5..c4de954 100644 --- a/types/transaction.d.ts +++ b/src/transaction.d.ts @@ -1,3 +1,4 @@ +/// export interface Output { script: Buffer; value: number; diff --git a/src/transaction.js b/src/transaction.js index 8db57c3..e4ebf6f 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -1,20 +1,20 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); +exports.Transaction = void 0; const bufferutils_1 = require('./bufferutils'); const bcrypto = require('./crypto'); const bscript = require('./script'); const script_1 = require('./script'); const types = require('./types'); -const typeforce = require('typeforce'); -const varuint = require('varuint-bitcoin'); +const { typeforce } = types; function varSliceSize(someScript) { const length = someScript.length; - return varuint.encodingLength(length) + length; + return bufferutils_1.varuint.encodingLength(length) + length; } function vectorSize(someVector) { const length = someVector.length; return ( - varuint.encodingLength(length) + + bufferutils_1.varuint.encodingLength(length) + someVector.reduce((sum, witness) => { return sum + varSliceSize(witness); }, 0) @@ -39,12 +39,13 @@ function isOutput(out) { return out.value !== undefined; } class Transaction { - constructor() { - this.version = 1; - this.locktime = 0; - this.ins = []; - this.outs = []; - } + static DEFAULT_SEQUENCE = 0xffffffff; + static SIGHASH_ALL = 0x01; + static SIGHASH_NONE = 0x02; + static SIGHASH_SINGLE = 0x03; + static SIGHASH_ANYONECANPAY = 0x80; + static ADVANCED_TRANSACTION_MARKER = 0x00; + static ADVANCED_TRANSACTION_FLAG = 0x01; static fromBuffer(buffer, _NO_STRICT) { const bufferReader = new bufferutils_1.BufferReader(buffer); const tx = new Transaction(); @@ -101,6 +102,10 @@ class Transaction { } return true; } + version = 1; + locktime = 0; + ins = []; + outs = []; isCoinbase() { return ( this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash) @@ -157,8 +162,8 @@ class Transaction { const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); return ( (hasWitnesses ? 10 : 8) + - varuint.encodingLength(this.ins.length) + - varuint.encodingLength(this.outs.length) + + bufferutils_1.varuint.encodingLength(this.ins.length) + + bufferutils_1.varuint.encodingLength(this.outs.length) + this.ins.reduce((sum, input) => { return sum + 40 + varSliceSize(input.script); }, 0) + @@ -336,7 +341,9 @@ class Transaction { } getId() { // transaction hash's are displayed in reverse order - return bufferutils_1.reverseBuffer(this.getHash(false)).toString('hex'); + return (0, bufferutils_1.reverseBuffer)(this.getHash(false)).toString( + 'hex', + ); } toBuffer(buffer, initialOffset) { return this.__toBuffer(buffer, initialOffset, true); @@ -392,11 +399,4 @@ class Transaction { return buffer; } } -Transaction.DEFAULT_SEQUENCE = 0xffffffff; -Transaction.SIGHASH_ALL = 0x01; -Transaction.SIGHASH_NONE = 0x02; -Transaction.SIGHASH_SINGLE = 0x03; -Transaction.SIGHASH_ANYONECANPAY = 0x80; -Transaction.ADVANCED_TRANSACTION_MARKER = 0x00; -Transaction.ADVANCED_TRANSACTION_FLAG = 0x01; exports.Transaction = Transaction; diff --git a/types/types.d.ts b/src/types.d.ts similarity index 86% rename from types/types.d.ts rename to src/types.d.ts index e7c588d..5a8505d 100644 --- a/types/types.d.ts +++ b/src/types.d.ts @@ -1,3 +1,6 @@ +/// +export declare const typeforce: any; +export declare function isPoint(p: Buffer | number | undefined | null): boolean; export declare function UInt31(value: number): boolean; export declare function BIP32Path(value: string): boolean; export declare namespace BIP32Path { diff --git a/src/types.js b/src/types.js index 8bcee2c..a6d1efa 100644 --- a/src/types.js +++ b/src/types.js @@ -1,13 +1,39 @@ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); -const typeforce = require('typeforce'); +exports.oneOf = exports.Null = exports.BufferN = exports.Function = exports.UInt32 = exports.UInt8 = exports.tuple = exports.maybe = exports.Hex = exports.Buffer = exports.String = exports.Boolean = exports.Array = exports.Number = exports.Hash256bit = exports.Hash160bit = exports.Buffer256bit = exports.Network = exports.ECPoint = exports.Satoshi = exports.Signer = exports.BIP32Path = exports.UInt31 = exports.isPoint = exports.typeforce = void 0; +const buffer_1 = require('buffer'); +exports.typeforce = require('typeforce'); +const ZERO32 = buffer_1.Buffer.alloc(32, 0); +const EC_P = buffer_1.Buffer.from( + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', + 'hex', +); +function isPoint(p) { + if (!buffer_1.Buffer.isBuffer(p)) return false; + if (p.length < 33) return false; + const t = p[0]; + const x = p.slice(1, 33); + if (x.compare(ZERO32) === 0) return false; + if (x.compare(EC_P) >= 0) return false; + if ((t === 0x02 || t === 0x03) && p.length === 33) { + return true; + } + const y = p.slice(33); + if (y.compare(ZERO32) === 0) return false; + if (y.compare(EC_P) >= 0) return false; + if (t === 0x04 && p.length === 65) return true; + return false; +} +exports.isPoint = isPoint; const UINT31_MAX = Math.pow(2, 31) - 1; function UInt31(value) { - return typeforce.UInt32(value) && value <= UINT31_MAX; + return exports.typeforce.UInt32(value) && value <= UINT31_MAX; } exports.UInt31 = UInt31; function BIP32Path(value) { - return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); + return ( + exports.typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/) + ); } exports.BIP32Path = BIP32Path; BIP32Path.toJSON = () => { @@ -15,7 +41,7 @@ BIP32Path.toJSON = () => { }; function Signer(obj) { return ( - (typeforce.Buffer(obj.publicKey) || + (exports.typeforce.Buffer(obj.publicKey) || typeof obj.getPublicKey === 'function') && typeof obj.sign === 'function' ); @@ -23,36 +49,39 @@ function Signer(obj) { exports.Signer = Signer; const SATOSHI_MAX = 21 * 1e14; function Satoshi(value) { - return typeforce.UInt53(value) && value <= SATOSHI_MAX; + return exports.typeforce.UInt53(value) && value <= SATOSHI_MAX; } exports.Satoshi = Satoshi; // external dependent types -exports.ECPoint = typeforce.quacksLike('Point'); +exports.ECPoint = exports.typeforce.quacksLike('Point'); // exposed, external API -exports.Network = typeforce.compile({ - messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), +exports.Network = exports.typeforce.compile({ + messagePrefix: exports.typeforce.oneOf( + exports.typeforce.Buffer, + exports.typeforce.String, + ), bip32: { - public: typeforce.UInt32, - private: typeforce.UInt32, + public: exports.typeforce.UInt32, + private: exports.typeforce.UInt32, }, - pubKeyHash: typeforce.UInt8, - scriptHash: typeforce.UInt8, - wif: typeforce.UInt8, + pubKeyHash: exports.typeforce.UInt8, + scriptHash: exports.typeforce.UInt8, + wif: exports.typeforce.UInt8, }); -exports.Buffer256bit = typeforce.BufferN(32); -exports.Hash160bit = typeforce.BufferN(20); -exports.Hash256bit = typeforce.BufferN(32); -exports.Number = typeforce.Number; // tslint:disable-line variable-name -exports.Array = typeforce.Array; -exports.Boolean = typeforce.Boolean; // tslint:disable-line variable-name -exports.String = typeforce.String; // tslint:disable-line variable-name -exports.Buffer = typeforce.Buffer; -exports.Hex = typeforce.Hex; -exports.maybe = typeforce.maybe; -exports.tuple = typeforce.tuple; -exports.UInt8 = typeforce.UInt8; -exports.UInt32 = typeforce.UInt32; -exports.Function = typeforce.Function; -exports.BufferN = typeforce.BufferN; -exports.Null = typeforce.Null; -exports.oneOf = typeforce.oneOf; +exports.Buffer256bit = exports.typeforce.BufferN(32); +exports.Hash160bit = exports.typeforce.BufferN(20); +exports.Hash256bit = exports.typeforce.BufferN(32); +exports.Number = exports.typeforce.Number; // tslint:disable-line variable-name +exports.Array = exports.typeforce.Array; +exports.Boolean = exports.typeforce.Boolean; // tslint:disable-line variable-name +exports.String = exports.typeforce.String; // tslint:disable-line variable-name +exports.Buffer = exports.typeforce.Buffer; +exports.Hex = exports.typeforce.Hex; +exports.maybe = exports.typeforce.maybe; +exports.tuple = exports.typeforce.tuple; +exports.UInt8 = exports.typeforce.UInt8; +exports.UInt32 = exports.typeforce.UInt32; +exports.Function = exports.typeforce.Function; +exports.BufferN = exports.typeforce.BufferN; +exports.Null = exports.typeforce.Null; +exports.oneOf = exports.typeforce.oneOf; diff --git a/test/bitcoin.core.spec.ts b/test/bitcoin.core.spec.ts index 0263266..9040416 100644 --- a/test/bitcoin.core.spec.ts +++ b/test/bitcoin.core.spec.ts @@ -88,49 +88,6 @@ describe('Bitcoin-core', () => { }); }); - // base58KeysValid - describe('ECPair', () => { - base58KeysValid.forEach(f => { - const strng = f[0] as string; - const hex = f[1]; - const params = f[2] as any; - - if (!params.isPrivkey) return; - - const network = params.isTestnet - ? bitcoin.networks.testnet - : bitcoin.networks.bitcoin; - const keyPair = bitcoin.ECPair.fromWIF(strng, network); - - it('fromWIF imports ' + strng, () => { - assert.strictEqual(keyPair.privateKey!.toString('hex'), hex); - assert.strictEqual(keyPair.compressed, params.isCompressed); - }); - - it('toWIF exports ' + hex + ' to ' + strng, () => { - assert.strictEqual(keyPair.toWIF(), strng); - }); - }); - }); - - // base58KeysInvalid - describe('ECPair.fromWIF', () => { - const allowedNetworks = [ - bitcoin.networks.bitcoin, - bitcoin.networks.testnet, - ]; - - base58KeysInvalid.forEach(f => { - const strng = f[0]; - - it('throws on ' + strng, () => { - assert.throws(() => { - bitcoin.ECPair.fromWIF(strng, allowedNetworks); - }, /(Invalid|Unknown) (checksum|compression flag|network version|WIF length)/); - }); - }); - }); - describe('Block.fromHex', () => { blocksValid.forEach(f => { it('can parse ' + f.id, () => { diff --git a/test/ecpair.spec.ts b/test/ecpair.spec.ts deleted file mode 100644 index 0b54ecc..0000000 --- a/test/ecpair.spec.ts +++ /dev/null @@ -1,341 +0,0 @@ -import * as assert from 'assert'; -import { beforeEach, describe, it } from 'mocha'; -import * as proxyquire from 'proxyquire'; -import { ECPair, ECPairInterface, networks as NETWORKS } from '..'; -import * as fixtures from './fixtures/ecpair.json'; -const hoodwink = require('hoodwink'); -const tinysecp = require('tiny-secp256k1'); - -const NETWORKS_LIST = Object.values(NETWORKS); -const ZERO = Buffer.alloc(32, 0); -const ONE = Buffer.from( - '0000000000000000000000000000000000000000000000000000000000000001', - 'hex', -); -const GROUP_ORDER = Buffer.from( - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', - 'hex', -); -const GROUP_ORDER_LESS_1 = Buffer.from( - 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140', - 'hex', -); - -describe('ECPair', () => { - describe('getPublicKey', () => { - let keyPair: ECPairInterface; - - beforeEach(() => { - keyPair = ECPair.fromPrivateKey(ONE); - }); - - it( - 'calls pointFromScalar lazily', - hoodwink(() => { - assert.strictEqual((keyPair as any).__Q, undefined); - - // .publicKey forces the memoization - assert.strictEqual( - keyPair.publicKey.toString('hex'), - '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - ); - assert.strictEqual( - (keyPair as any).__Q.toString('hex'), - '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798', - ); - }), - ); - }); - - describe('fromPrivateKey', () => { - it('defaults to compressed', () => { - const keyPair = ECPair.fromPrivateKey(ONE); - - assert.strictEqual(keyPair.compressed, true); - }); - - it('supports the uncompressed option', () => { - const keyPair = ECPair.fromPrivateKey(ONE, { - compressed: false, - }); - - assert.strictEqual(keyPair.compressed, false); - }); - - it('supports the network option', () => { - const keyPair = ECPair.fromPrivateKey(ONE, { - compressed: false, - network: NETWORKS.testnet, - }); - - assert.strictEqual(keyPair.network, NETWORKS.testnet); - }); - - fixtures.valid.forEach(f => { - it('derives public key for ' + f.WIF, () => { - const d = Buffer.from(f.d, 'hex'); - const keyPair = ECPair.fromPrivateKey(d, { - compressed: f.compressed, - }); - - assert.strictEqual(keyPair.publicKey.toString('hex'), f.Q); - }); - }); - - fixtures.invalid.fromPrivateKey.forEach(f => { - it('throws ' + f.exception, () => { - const d = Buffer.from(f.d, 'hex'); - assert.throws(() => { - ECPair.fromPrivateKey(d, (f as any).options); - }, new RegExp(f.exception)); - }); - }); - }); - - describe('fromPublicKey', () => { - fixtures.invalid.fromPublicKey.forEach(f => { - it('throws ' + f.exception, () => { - const Q = Buffer.from(f.Q, 'hex'); - assert.throws(() => { - ECPair.fromPublicKey(Q, (f as any).options); - }, new RegExp(f.exception)); - }); - }); - }); - - describe('fromWIF', () => { - fixtures.valid.forEach(f => { - it('imports ' + f.WIF + ' (' + f.network + ')', () => { - const network = (NETWORKS as any)[f.network]; - const keyPair = ECPair.fromWIF(f.WIF, network); - - assert.strictEqual(keyPair.privateKey!.toString('hex'), f.d); - assert.strictEqual(keyPair.compressed, f.compressed); - assert.strictEqual(keyPair.network, network); - }); - }); - - fixtures.valid.forEach(f => { - it('imports ' + f.WIF + ' (via list of networks)', () => { - const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST); - - assert.strictEqual(keyPair.privateKey!.toString('hex'), f.d); - assert.strictEqual(keyPair.compressed, f.compressed); - assert.strictEqual(keyPair.network, (NETWORKS as any)[f.network]); - }); - }); - - fixtures.invalid.fromWIF.forEach(f => { - it('throws on ' + f.WIF, () => { - assert.throws(() => { - const networks = f.network - ? (NETWORKS as any)[f.network] - : NETWORKS_LIST; - - ECPair.fromWIF(f.WIF, networks); - }, new RegExp(f.exception)); - }); - }); - }); - - describe('toWIF', () => { - fixtures.valid.forEach(f => { - it('exports ' + f.WIF, () => { - const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST); - const result = keyPair.toWIF(); - assert.strictEqual(result, f.WIF); - }); - }); - it('throws if no private key is found', () => { - assert.throws(() => { - const keyPair = ECPair.makeRandom(); - delete (keyPair as any).__D; - keyPair.toWIF(); - }, /Missing private key/); - }); - }); - - describe('makeRandom', () => { - const d = Buffer.alloc(32, 4); - const exWIF = 'KwMWvwRJeFqxYyhZgNwYuYjbQENDAPAudQx5VEmKJrUZcq6aL2pv'; - - describe('uses randombytes RNG', () => { - it('generates a ECPair', () => { - const stub = { - randombytes: (): Buffer => { - return d; - }, - }; - const ProxiedECPair = proxyquire('../src/ecpair', stub); - - const keyPair = ProxiedECPair.makeRandom(); - assert.strictEqual(keyPair.toWIF(), exWIF); - }); - }); - - it('allows a custom RNG to be used', () => { - const keyPair = ECPair.makeRandom({ - rng: (size): Buffer => { - return d.slice(0, size); - }, - }); - - assert.strictEqual(keyPair.toWIF(), exWIF); - }); - - it('retains the same defaults as ECPair constructor', () => { - const keyPair = ECPair.makeRandom(); - - assert.strictEqual(keyPair.compressed, true); - assert.strictEqual(keyPair.network, NETWORKS.bitcoin); - }); - - it('supports the options parameter', () => { - const keyPair = ECPair.makeRandom({ - compressed: false, - network: NETWORKS.testnet, - }); - - assert.strictEqual(keyPair.compressed, false); - assert.strictEqual(keyPair.network, NETWORKS.testnet); - }); - - it('throws if d is bad length', () => { - function rng(): Buffer { - return Buffer.alloc(28); - } - - assert.throws(() => { - ECPair.makeRandom({ rng }); - }, /Expected Buffer\(Length: 32\), got Buffer\(Length: 28\)/); - }); - - it( - 'loops until d is within interval [1, n) : 1', - hoodwink(function(this: any): void { - const rng = this.stub(() => { - if (rng.calls === 0) return ZERO; // 0 - return ONE; // >0 - }, 2); - - ECPair.makeRandom({ rng }); - }), - ); - - it( - 'loops until d is within interval [1, n) : n - 1', - hoodwink(function(this: any): void { - const rng = this.stub(() => { - if (rng.calls === 0) return ZERO; // <1 - if (rng.calls === 1) return GROUP_ORDER; // >n-1 - return GROUP_ORDER_LESS_1; // n-1 - }, 3); - - ECPair.makeRandom({ rng }); - }), - ); - }); - - describe('.network', () => { - fixtures.valid.forEach(f => { - it('returns ' + f.network + ' for ' + f.WIF, () => { - const network = (NETWORKS as any)[f.network]; - const keyPair = ECPair.fromWIF(f.WIF, NETWORKS_LIST); - - assert.strictEqual(keyPair.network, network); - }); - }); - }); - - describe('tinysecp wrappers', () => { - let keyPair: ECPairInterface; - let hash: Buffer; - let signature: Buffer; - - beforeEach(() => { - keyPair = ECPair.makeRandom(); - hash = ZERO; - signature = Buffer.alloc(64, 1); - }); - - describe('signing', () => { - it( - 'wraps tinysecp.sign', - hoodwink(function(this: any): void { - this.mock( - tinysecp, - 'sign', - (h: any, d: any) => { - assert.strictEqual(h, hash); - assert.strictEqual(d, keyPair.privateKey); - return signature; - }, - 1, - ); - - assert.strictEqual(keyPair.sign(hash), signature); - }), - ); - - it('throws if no private key is found', () => { - delete (keyPair as any).__D; - - assert.throws(() => { - keyPair.sign(hash); - }, /Missing private key/); - }); - }); - - describe('verify', () => { - it( - 'wraps tinysecp.verify', - hoodwink(function(this: any): void { - this.mock( - tinysecp, - 'verify', - (h: any, q: any, s: any) => { - assert.strictEqual(h, hash); - assert.strictEqual(q, keyPair.publicKey); - assert.strictEqual(s, signature); - return true; - }, - 1, - ); - - assert.strictEqual(keyPair.verify(hash, signature), true); - }), - ); - }); - }); - describe('optional low R signing', () => { - const sig = Buffer.from( - '95a6619140fca3366f1d3b013b0367c4f86e39508a50fdce' + - 'e5245fbb8bd60aa6086449e28cf15387cf9f85100bfd0838624ca96759e59f65c10a00' + - '16b86f5229', - 'hex', - ); - const sigLowR = Buffer.from( - '6a2660c226e8055afad317eeba918a304be79208d505' + - '3bc5ea4a5e4c5892b4a061c717c5284ae5202d721c0e49b4717b79966280906b1d3b52' + - '95d1fdde963c35', - 'hex', - ); - const lowRKeyPair = ECPair.fromWIF( - 'L3nThUzbAwpUiBAjR5zCu66ybXSPMr2zZ3ikp' + 'ScpTPiYTxBynfZu', - ); - const dataToSign = Buffer.from( - 'b6c5c548a7f6164c8aa7af5350901626ebd69f9ae' + '2c1ecf8871f5088ec204cfe', - 'hex', - ); - - it('signs with normal R by default', () => { - const signed = lowRKeyPair.sign(dataToSign); - assert.deepStrictEqual(sig, signed); - }); - - it('signs with low R when true is passed', () => { - const signed = lowRKeyPair.sign(dataToSign, true); - assert.deepStrictEqual(sigLowR, signed); - }); - }); -}); diff --git a/test/integration/addresses.spec.ts b/test/integration/addresses.spec.ts index d8feb7c..2b24ef5 100644 --- a/test/integration/addresses.spec.ts +++ b/test/integration/addresses.spec.ts @@ -1,4 +1,5 @@ import * as assert from 'assert'; +import { ECPair } from 'ecpair'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; @@ -10,7 +11,7 @@ describe('bitcoinjs-lib (addresses)', () => { 'can generate a random address [and support the retrieval of ' + 'transactions for that address (via 3PBP)]', async () => { - const keyPair = bitcoin.ECPair.makeRandom(); + const keyPair = ECPair.makeRandom(); const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey }); // bitcoin P2PKH addresses start with a '1' @@ -29,7 +30,7 @@ describe('bitcoinjs-lib (addresses)', () => { ); it('can import an address via WIF', () => { - const keyPair = bitcoin.ECPair.fromWIF( + const keyPair = ECPair.fromWIF( 'KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn', ); const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey }); @@ -51,7 +52,7 @@ describe('bitcoinjs-lib (addresses)', () => { }); it('can generate a SegWit address', () => { - const keyPair = bitcoin.ECPair.fromWIF( + const keyPair = ECPair.fromWIF( 'KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn', ); const { address } = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey }); @@ -60,7 +61,7 @@ describe('bitcoinjs-lib (addresses)', () => { }); it('can generate a SegWit address (via P2SH)', () => { - const keyPair = bitcoin.ECPair.fromWIF( + const keyPair = ECPair.fromWIF( 'KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn', ); const { address } = bitcoin.payments.p2sh({ @@ -103,7 +104,7 @@ describe('bitcoinjs-lib (addresses)', () => { // examples using other network information it('can generate a Testnet address', () => { - const keyPair = bitcoin.ECPair.makeRandom({ network: TESTNET }); + const keyPair = ECPair.makeRandom({ network: TESTNET }); const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: TESTNET, @@ -130,7 +131,7 @@ describe('bitcoinjs-lib (addresses)', () => { wif: 0xb0, }; - const keyPair = bitcoin.ECPair.makeRandom({ network: LITECOIN }); + const keyPair = ECPair.makeRandom({ network: LITECOIN }); const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: LITECOIN, diff --git a/test/integration/cltv.spec.ts b/test/integration/cltv.spec.ts index d5141c7..4b2eb66 100644 --- a/test/integration/cltv.spec.ts +++ b/test/integration/cltv.spec.ts @@ -1,4 +1,5 @@ import * as assert from 'assert'; +import { ECPair } from 'ecpair'; import { before, describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; @@ -13,11 +14,11 @@ function idToHash(txid: string): Buffer { return Buffer.from(txid, 'hex').reverse(); } -const alice = bitcoin.ECPair.fromWIF( +const alice = ECPair.fromWIF( 'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest, ); -const bob = bitcoin.ECPair.fromWIF( +const bob = ECPair.fromWIF( 'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest, ); diff --git a/test/integration/csv.spec.ts b/test/integration/csv.spec.ts index 6f11a90..81b0943 100644 --- a/test/integration/csv.spec.ts +++ b/test/integration/csv.spec.ts @@ -1,5 +1,6 @@ import * as assert from 'assert'; import { PsbtInput } from 'bip174/src/lib/interfaces'; +import { ECPair } from 'ecpair'; import { before, describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; @@ -15,19 +16,19 @@ function idToHash(txid: string): Buffer { return Buffer.from(txid, 'hex').reverse(); } -const alice = bitcoin.ECPair.fromWIF( +const alice = ECPair.fromWIF( 'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest, ); -const bob = bitcoin.ECPair.fromWIF( +const bob = ECPair.fromWIF( 'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest, ); -const charles = bitcoin.ECPair.fromWIF( +const charles = ECPair.fromWIF( 'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMSb4Ubnf', regtest, ); -const dave = bitcoin.ECPair.fromWIF( +const dave = ECPair.fromWIF( 'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx', regtest, ); diff --git a/test/integration/payments.spec.ts b/test/integration/payments.spec.ts index 58f48f5..ea7294e 100644 --- a/test/integration/payments.spec.ts +++ b/test/integration/payments.spec.ts @@ -1,10 +1,11 @@ +import { ECPair } from 'ecpair'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; const NETWORK = regtestUtils.network; const keyPairs = [ - bitcoin.ECPair.makeRandom({ network: NETWORK }), - bitcoin.ECPair.makeRandom({ network: NETWORK }), + ECPair.makeRandom({ network: NETWORK }), + ECPair.makeRandom({ network: NETWORK }), ]; async function buildAndSign( diff --git a/test/integration/transactions.spec.ts b/test/integration/transactions.spec.ts index 9c98bb4..d4788dc 100644 --- a/test/integration/transactions.spec.ts +++ b/test/integration/transactions.spec.ts @@ -1,16 +1,23 @@ import * as assert from 'assert'; import * as bip32 from 'bip32'; +import { ECPair } from 'ecpair'; import { describe, it } from 'mocha'; import * as bitcoin from '../..'; import { regtestUtils } from './_regtest'; const rng = require('randombytes'); const regtest = regtestUtils.network; +const validator = ( + pubkey: Buffer, + msghash: Buffer, + signature: Buffer, +): boolean => ECPair.fromPublicKey(pubkey).verify(msghash, signature); + // See bottom of file for some helper functions used to make the payment objects needed. describe('bitcoinjs-lib (transactions with psbt)', () => { it('can create a 1-to-1 Transaction', () => { - const alice = bitcoin.ECPair.fromWIF( + const alice = ECPair.fromWIF( 'L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr', ); const psbt = new bitcoin.Psbt(); @@ -59,7 +66,7 @@ describe('bitcoinjs-lib (transactions with psbt)', () => { value: 80000, }); psbt.signInput(0, alice); - psbt.validateSignaturesOfInput(0); + psbt.validateSignaturesOfInput(0, validator); psbt.finalizeAllInputs(); assert.strictEqual( psbt.extractTransaction().toHex(), @@ -147,8 +154,8 @@ describe('bitcoinjs-lib (transactions with psbt)', () => { // Finalizer wants to check all signatures are valid before finalizing. // If the finalizer wants to check for specific pubkeys, the second arg // can be passed. See the first multisig example below. - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); - assert.strictEqual(psbt.validateSignaturesOfInput(1), true); + assert.strictEqual(psbt.validateSignaturesOfInput(0, validator), true); + assert.strictEqual(psbt.validateSignaturesOfInput(1, validator), true); // This step it new. Since we separate the signing operation and // the creation of the scriptSig and witness stack, we are able to @@ -183,7 +190,7 @@ describe('bitcoinjs-lib (transactions with psbt)', () => { }) .signInput(0, alice1.keys[0]); - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual(psbt.validateSignaturesOfInput(0, validator), true); psbt.finalizeAllInputs(); // build and broadcast to the RegTest network @@ -215,13 +222,13 @@ describe('bitcoinjs-lib (transactions with psbt)', () => { .signInput(0, multisig.keys[0]) .signInput(0, multisig.keys[2]); - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual(psbt.validateSignaturesOfInput(0, validator), true); assert.strictEqual( - psbt.validateSignaturesOfInput(0, multisig.keys[0].publicKey), + psbt.validateSignaturesOfInput(0, validator, multisig.keys[0].publicKey), true, ); assert.throws(() => { - psbt.validateSignaturesOfInput(0, multisig.keys[3].publicKey); + psbt.validateSignaturesOfInput(0, validator, multisig.keys[3].publicKey); }, new RegExp('No signatures for this pubkey')); psbt.finalizeAllInputs(); @@ -330,7 +337,7 @@ describe('bitcoinjs-lib (transactions with psbt)', () => { }) .signInput(0, p2wpkh.keys[0]); - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual(psbt.validateSignaturesOfInput(0, validator), true); psbt.finalizeAllInputs(); const tx = psbt.extractTransaction(); @@ -398,7 +405,7 @@ describe('bitcoinjs-lib (transactions with psbt)', () => { }) .signInput(0, p2wsh.keys[0]); - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual(psbt.validateSignaturesOfInput(0, validator), true); psbt.finalizeAllInputs(); const tx = psbt.extractTransaction(); @@ -472,13 +479,13 @@ describe('bitcoinjs-lib (transactions with psbt)', () => { .signInput(0, p2sh.keys[2]) .signInput(0, p2sh.keys[3]); - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual(psbt.validateSignaturesOfInput(0, validator), true); assert.strictEqual( - psbt.validateSignaturesOfInput(0, p2sh.keys[3].publicKey), + psbt.validateSignaturesOfInput(0, validator, p2sh.keys[3].publicKey), true, ); assert.throws(() => { - psbt.validateSignaturesOfInput(0, p2sh.keys[1].publicKey); + psbt.validateSignaturesOfInput(0, validator, p2sh.keys[1].publicKey); }, new RegExp('No signatures for this pubkey')); psbt.finalizeAllInputs(); @@ -534,10 +541,10 @@ describe('bitcoinjs-lib (transactions with psbt)', () => { 'can create (and broadcast via 3PBP) a Transaction, w/ a ' + 'P2SH(P2MS(2 of 2)) input with nonWitnessUtxo', async () => { - const myKey = bitcoin.ECPair.makeRandom({ network: regtest }); + const myKey = ECPair.makeRandom({ network: regtest }); const myKeys = [ myKey, - bitcoin.ECPair.fromPrivateKey(myKey.privateKey!, { network: regtest }), + ECPair.fromPrivateKey(myKey.privateKey!, { network: regtest }), ]; const p2sh = createPayment('p2sh-p2ms(2 of 2)', myKeys); const inputData = await getInputData(5e4, p2sh.payment, false, 'p2sh'); @@ -603,9 +610,9 @@ describe('bitcoinjs-lib (transactions with psbt)', () => { }) .signInputHD(0, hdRoot); // must sign with root!!! - assert.strictEqual(psbt.validateSignaturesOfInput(0), true); + assert.strictEqual(psbt.validateSignaturesOfInput(0, validator), true); assert.strictEqual( - psbt.validateSignaturesOfInput(0, childNode.publicKey), + psbt.validateSignaturesOfInput(0, validator, childNode.publicKey), true, ); psbt.finalizeAllInputs(); @@ -638,11 +645,11 @@ function createPayment(_type: string, myKeys?: any[], network?: any): any { throw new Error('Need n keys for multisig'); } while (!myKeys && n > 1) { - keys.push(bitcoin.ECPair.makeRandom({ network })); + keys.push(ECPair.makeRandom({ network })); n--; } } - if (!myKeys) keys.push(bitcoin.ECPair.makeRandom({ network })); + if (!myKeys) keys.push(ECPair.makeRandom({ network })); let payment: any; splitType.forEach(type => { diff --git a/test/psbt.spec.ts b/test/psbt.spec.ts index f203324..1c6cb09 100644 --- a/test/psbt.spec.ts +++ b/test/psbt.spec.ts @@ -1,10 +1,10 @@ import * as assert from 'assert'; import * as crypto from 'crypto'; +import { ECPair } from 'ecpair'; import { describe, it } from 'mocha'; import { bip32, - ECPair, networks as NETWORKS, payments, Psbt, @@ -14,6 +14,12 @@ import { import * as preFixtures from './fixtures/psbt.json'; +const validator = ( + pubkey: Buffer, + msghash: Buffer, + signature: Buffer, +): boolean => ECPair.fromPublicKey(pubkey).verify(msghash, signature); + const initBuffers = (object: any): typeof preFixtures => JSON.parse(JSON.stringify(object), (_, value) => { const regex = new RegExp(/^Buffer.from\(['"](.*)['"], ['"](.*)['"]\)$/); @@ -896,7 +902,7 @@ describe(`Psbt`, () => { const notAClone = Object.assign(new Psbt(), psbt); // references still active const clone = psbt.clone(); - assert.strictEqual(psbt.validateSignaturesOfAllInputs(), true); + assert.strictEqual(psbt.validateSignaturesOfAllInputs(validator), true); assert.strictEqual(clone.toBase64(), psbt.toBase64()); assert.strictEqual(clone.toBase64(), notAClone.toBase64()); @@ -923,20 +929,27 @@ describe(`Psbt`, () => { it('Correctly validates a signature', () => { const psbt = Psbt.fromBase64(f.psbt); - assert.strictEqual(psbt.validateSignaturesOfInput(f.index), true); + assert.strictEqual( + psbt.validateSignaturesOfInput(f.index, validator), + true, + ); assert.throws(() => { - psbt.validateSignaturesOfInput(f.nonExistantIndex); + psbt.validateSignaturesOfInput(f.nonExistantIndex, validator); }, new RegExp('No signatures to validate')); }); it('Correctly validates a signature against a pubkey', () => { const psbt = Psbt.fromBase64(f.psbt); assert.strictEqual( - psbt.validateSignaturesOfInput(f.index, f.pubkey as any), + psbt.validateSignaturesOfInput(f.index, validator, f.pubkey as any), true, ); assert.throws(() => { - psbt.validateSignaturesOfInput(f.index, f.incorrectPubkey as any); + psbt.validateSignaturesOfInput( + f.index, + validator, + f.incorrectPubkey as any, + ); }, new RegExp('No signatures for this pubkey')); }); }); @@ -985,7 +998,7 @@ describe(`Psbt`, () => { assert.throws(() => { psbt.setVersion(3); }, new RegExp('Can not modify transaction, signatures exist.')); - psbt.validateSignaturesOfInput(0); + psbt.validateSignaturesOfInput(0, validator); psbt.finalizeAllInputs(); assert.throws(() => { psbt.setVersion(3); diff --git a/test/tsconfig.json b/test/tsconfig.json index e29620d..4e4f529 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES2017", + "target": "ESNEXT", "module": "commonjs", "outDir": "../", "declaration": false, diff --git a/ts_src/address.ts b/ts_src/address.ts index 11be51b..d8111a7 100644 --- a/ts_src/address.ts +++ b/ts_src/address.ts @@ -3,10 +3,9 @@ import * as networks from './networks'; import * as payments from './payments'; import * as bscript from './script'; import * as types from './types'; - -const { bech32, bech32m } = require('bech32'); -const bs58check = require('bs58check'); -const typeforce = require('typeforce'); +import { bech32, bech32m } from 'bech32'; +import * as bs58check from 'bs58check'; +const { typeforce } = types; export interface Base58CheckResult { hash: Buffer; diff --git a/ts_src/bip66.ts b/ts_src/bip66.ts new file mode 100644 index 0000000..ab76a4f --- /dev/null +++ b/ts_src/bip66.ts @@ -0,0 +1,111 @@ +// Reference https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki +// Format: 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] +// NOTE: SIGHASH byte ignored AND restricted, truncate before use + +export function check(buffer: Buffer): boolean { + if (buffer.length < 8) return false; + if (buffer.length > 72) return false; + if (buffer[0] !== 0x30) return false; + if (buffer[1] !== buffer.length - 2) return false; + if (buffer[2] !== 0x02) return false; + + const lenR = buffer[3]; + if (lenR === 0) return false; + if (5 + lenR >= buffer.length) return false; + if (buffer[4 + lenR] !== 0x02) return false; + + const lenS = buffer[5 + lenR]; + if (lenS === 0) return false; + if (6 + lenR + lenS !== buffer.length) return false; + + if (buffer[4] & 0x80) return false; + if (lenR > 1 && buffer[4] === 0x00 && !(buffer[5] & 0x80)) return false; + + if (buffer[lenR + 6] & 0x80) return false; + if (lenS > 1 && buffer[lenR + 6] === 0x00 && !(buffer[lenR + 7] & 0x80)) + return false; + return true; +} + +export function decode(buffer: Buffer): { r: Buffer; s: Buffer } { + if (buffer.length < 8) throw new Error('DER sequence length is too short'); + if (buffer.length > 72) throw new Error('DER sequence length is too long'); + if (buffer[0] !== 0x30) throw new Error('Expected DER sequence'); + if (buffer[1] !== buffer.length - 2) + throw new Error('DER sequence length is invalid'); + if (buffer[2] !== 0x02) throw new Error('Expected DER integer'); + + const lenR = buffer[3]; + if (lenR === 0) throw new Error('R length is zero'); + if (5 + lenR >= buffer.length) throw new Error('R length is too long'); + if (buffer[4 + lenR] !== 0x02) throw new Error('Expected DER integer (2)'); + + const lenS = buffer[5 + lenR]; + if (lenS === 0) throw new Error('S length is zero'); + if (6 + lenR + lenS !== buffer.length) throw new Error('S length is invalid'); + + if (buffer[4] & 0x80) throw new Error('R value is negative'); + if (lenR > 1 && buffer[4] === 0x00 && !(buffer[5] & 0x80)) + throw new Error('R value excessively padded'); + + if (buffer[lenR + 6] & 0x80) throw new Error('S value is negative'); + if (lenS > 1 && buffer[lenR + 6] === 0x00 && !(buffer[lenR + 7] & 0x80)) + throw new Error('S value excessively padded'); + + // non-BIP66 - extract R, S values + return { + r: buffer.slice(4, 4 + lenR), + s: buffer.slice(6 + lenR), + }; +} + +/* + * Expects r and s to be positive DER integers. + * + * The DER format uses the most significant bit as a sign bit (& 0x80). + * If the significant bit is set AND the integer is positive, a 0x00 is prepended. + * + * Examples: + * + * 0 => 0x00 + * 1 => 0x01 + * -1 => 0xff + * 127 => 0x7f + * -127 => 0x81 + * 128 => 0x0080 + * -128 => 0x80 + * 255 => 0x00ff + * -255 => 0xff01 + * 16300 => 0x3fac + * -16300 => 0xc054 + * 62300 => 0x00f35c + * -62300 => 0xff0ca4 + */ +export function encode(r: Buffer, s: Buffer): Buffer { + const lenR = r.length; + const lenS = s.length; + if (lenR === 0) throw new Error('R length is zero'); + if (lenS === 0) throw new Error('S length is zero'); + if (lenR > 33) throw new Error('R length is too long'); + if (lenS > 33) throw new Error('S length is too long'); + if (r[0] & 0x80) throw new Error('R value is negative'); + if (s[0] & 0x80) throw new Error('S value is negative'); + if (lenR > 1 && r[0] === 0x00 && !(r[1] & 0x80)) + throw new Error('R value excessively padded'); + if (lenS > 1 && s[0] === 0x00 && !(s[1] & 0x80)) + throw new Error('S value excessively padded'); + + const signature = Buffer.allocUnsafe(6 + lenR + lenS); + + // 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] + signature[0] = 0x30; + signature[1] = signature.length - 2; + signature[2] = 0x02; + signature[3] = r.length; + r.copy(signature, 4); + signature[4 + lenR] = 0x02; + signature[5 + lenR] = s.length; + s.copy(signature, 6 + lenR); + + return signature; +} diff --git a/ts_src/block.ts b/ts_src/block.ts index 2760e5e..c73477e 100644 --- a/ts_src/block.ts +++ b/ts_src/block.ts @@ -1,11 +1,14 @@ -import { BufferReader, BufferWriter, reverseBuffer } from './bufferutils'; +import { + BufferReader, + BufferWriter, + reverseBuffer, + varuint, +} from './bufferutils'; import * as bcrypto from './crypto'; +import { fastMerkleRoot } from './merkle'; import { Transaction } from './transaction'; import * as types from './types'; - -const fastMerkleRoot = require('merkle-lib/fastRoot'); -const typeforce = require('typeforce'); -const varuint = require('varuint-bitcoin'); +const { typeforce } = types; const errorMerkleNoTxes = new TypeError( 'Cannot compute merkle root for zero transactions', diff --git a/ts_src/bufferutils.ts b/ts_src/bufferutils.ts index 9005f2a..43171c4 100644 --- a/ts_src/bufferutils.ts +++ b/ts_src/bufferutils.ts @@ -1,7 +1,7 @@ import * as types from './types'; - -const typeforce = require('typeforce'); -const varuint = require('varuint-bitcoin'); +const { typeforce } = types; +import * as varuint from 'varuint-bitcoin'; +export { varuint }; // https://github.com/feross/buffer/blob/master/index.js#L1127 function verifuint(value: number, max: number): void { diff --git a/ts_src/crypto.ts b/ts_src/crypto.ts index 1cb5a69..7f69c40 100644 --- a/ts_src/crypto.ts +++ b/ts_src/crypto.ts @@ -1,4 +1,4 @@ -const createHash = require('create-hash'); +import * as createHash from 'create-hash'; export function ripemd160(buffer: Buffer): Buffer { try { diff --git a/ts_src/ecpair.ts b/ts_src/ecpair.ts deleted file mode 100644 index 3c7eb1c..0000000 --- a/ts_src/ecpair.ts +++ /dev/null @@ -1,161 +0,0 @@ -import { Network } from './networks'; -import * as NETWORKS from './networks'; -import * as types from './types'; -const ecc = require('tiny-secp256k1'); -const randomBytes = require('randombytes'); -const typeforce = require('typeforce'); -const wif = require('wif'); - -const isOptions = typeforce.maybe( - typeforce.compile({ - compressed: types.maybe(types.Boolean), - network: types.maybe(types.Network), - }), -); - -interface ECPairOptions { - compressed?: boolean; - network?: Network; - rng?(arg0: number): Buffer; -} - -export interface Signer { - publicKey: Buffer; - network?: any; - sign(hash: Buffer, lowR?: boolean): Buffer; - getPublicKey?(): Buffer; -} - -export interface SignerAsync { - publicKey: Buffer; - network?: any; - sign(hash: Buffer, lowR?: boolean): Promise; - getPublicKey?(): Buffer; -} - -export interface ECPairInterface extends Signer { - compressed: boolean; - network: Network; - lowR: boolean; - privateKey?: Buffer; - toWIF(): string; - verify(hash: Buffer, signature: Buffer): boolean; -} - -class ECPair implements ECPairInterface { - compressed: boolean; - network: Network; - lowR: boolean; - - constructor( - private __D?: Buffer, - private __Q?: Buffer, - options?: ECPairOptions, - ) { - this.lowR = false; - if (options === undefined) options = {}; - this.compressed = - options.compressed === undefined ? true : options.compressed; - this.network = options.network || NETWORKS.bitcoin; - - if (__Q !== undefined) this.__Q = ecc.pointCompress(__Q, this.compressed); - } - - get privateKey(): Buffer | undefined { - return this.__D; - } - - get publicKey(): Buffer { - if (!this.__Q) - this.__Q = ecc.pointFromScalar(this.__D, this.compressed) as Buffer; - return this.__Q; - } - - toWIF(): string { - if (!this.__D) throw new Error('Missing private key'); - return wif.encode(this.network.wif, this.__D, this.compressed); - } - - sign(hash: Buffer, lowR?: boolean): Buffer { - if (!this.__D) throw new Error('Missing private key'); - if (lowR === undefined) lowR = this.lowR; - if (lowR === false) { - return ecc.sign(hash, this.__D); - } else { - let sig = ecc.sign(hash, this.__D); - const extraData = Buffer.alloc(32, 0); - let counter = 0; - // if first try is lowR, skip the loop - // for second try and on, add extra entropy counting up - while (sig[0] > 0x7f) { - counter++; - extraData.writeUIntLE(counter, 0, 6); - sig = ecc.signWithEntropy(hash, this.__D, extraData); - } - return sig; - } - } - - verify(hash: Buffer, signature: Buffer): boolean { - return ecc.verify(hash, this.publicKey, signature); - } -} - -function fromPrivateKey(buffer: Buffer, options?: ECPairOptions): ECPair { - typeforce(types.Buffer256bit, buffer); - if (!ecc.isPrivate(buffer)) - throw new TypeError('Private key not in range [1, n)'); - typeforce(isOptions, options); - - return new ECPair(buffer, undefined, options); -} - -function fromPublicKey(buffer: Buffer, options?: ECPairOptions): ECPair { - typeforce(ecc.isPoint, buffer); - typeforce(isOptions, options); - return new ECPair(undefined, buffer, options); -} - -function fromWIF(wifString: string, network?: Network | Network[]): ECPair { - const decoded = wif.decode(wifString); - const version = decoded.version; - - // list of networks? - if (types.Array(network)) { - network = (network as Network[]) - .filter((x: Network) => { - return version === x.wif; - }) - .pop() as Network; - - if (!network) throw new Error('Unknown network version'); - - // otherwise, assume a network object (or default to bitcoin) - } else { - network = network || NETWORKS.bitcoin; - - if (version !== (network as Network).wif) - throw new Error('Invalid network version'); - } - - return fromPrivateKey(decoded.privateKey, { - compressed: decoded.compressed, - network: network as Network, - }); -} - -function makeRandom(options?: ECPairOptions): ECPair { - typeforce(isOptions, options); - if (options === undefined) options = {}; - const rng = options.rng || randomBytes; - - let d; - do { - d = rng(32); - typeforce(types.Buffer256bit, d); - } while (!ecc.isPrivate(d)); - - return fromPrivateKey(d, options); -} - -export { makeRandom, fromPrivateKey, fromPublicKey, fromWIF }; diff --git a/ts_src/index.ts b/ts_src/index.ts index c425d96..66b2f9c 100644 --- a/ts_src/index.ts +++ b/ts_src/index.ts @@ -1,20 +1,26 @@ import * as bip32 from 'bip32'; import * as address from './address'; import * as crypto from './crypto'; -import * as ECPair from './ecpair'; import * as networks from './networks'; import * as payments from './payments'; import * as script from './script'; -export { ECPair, address, bip32, crypto, networks, payments, script }; +export { address, bip32, crypto, networks, payments, script }; export { Block } from './block'; -export { Psbt, PsbtTxInput, PsbtTxOutput } from './psbt'; -export { OPS as opcodes } from './script'; +export { + Psbt, + PsbtTxInput, + PsbtTxOutput, + Signer, + SignerAsync, + HDSigner, + HDSignerAsync, +} from './psbt'; +export { OPS as opcodes } from './ops'; export { Transaction } from './transaction'; export { BIP32Interface } from 'bip32'; -export { ECPairInterface, Signer, SignerAsync } from './ecpair'; export { Network } from './networks'; export { Payment, @@ -23,5 +29,4 @@ export { Stack, StackElement, } from './payments'; -export { OpCode } from './script'; export { Input as TxInput, Output as TxOutput } from './transaction'; diff --git a/ts_src/merkle.ts b/ts_src/merkle.ts new file mode 100644 index 0000000..8ff8c3f --- /dev/null +++ b/ts_src/merkle.ts @@ -0,0 +1,27 @@ +export function fastMerkleRoot( + values: Buffer[], + digestFn: (b: Buffer) => Buffer, +): Buffer { + if (!Array.isArray(values)) throw TypeError('Expected values Array'); + if (typeof digestFn !== 'function') + throw TypeError('Expected digest Function'); + + let length = values.length; + const results = values.concat(); + + while (length > 1) { + let j = 0; + + for (let i = 0; i < length; i += 2, ++j) { + const left = results[i]; + const right = i + 1 === length ? left : results[i + 1]; + const data = Buffer.concat([left, right]); + + results[j] = digestFn(data); + } + + length = j; + } + + return results[0]; +} diff --git a/ts_src/ops.ts b/ts_src/ops.ts new file mode 100644 index 0000000..8e2c41c --- /dev/null +++ b/ts_src/ops.ts @@ -0,0 +1,141 @@ +const OPS: { [key: string]: number } = { + OP_FALSE: 0, + OP_0: 0, + OP_PUSHDATA1: 76, + OP_PUSHDATA2: 77, + OP_PUSHDATA4: 78, + OP_1NEGATE: 79, + OP_RESERVED: 80, + OP_TRUE: 81, + OP_1: 81, + OP_2: 82, + OP_3: 83, + OP_4: 84, + OP_5: 85, + OP_6: 86, + OP_7: 87, + OP_8: 88, + OP_9: 89, + OP_10: 90, + OP_11: 91, + OP_12: 92, + OP_13: 93, + OP_14: 94, + OP_15: 95, + OP_16: 96, + + OP_NOP: 97, + OP_VER: 98, + OP_IF: 99, + OP_NOTIF: 100, + OP_VERIF: 101, + OP_VERNOTIF: 102, + OP_ELSE: 103, + OP_ENDIF: 104, + OP_VERIFY: 105, + OP_RETURN: 106, + + OP_TOALTSTACK: 107, + OP_FROMALTSTACK: 108, + OP_2DROP: 109, + OP_2DUP: 110, + OP_3DUP: 111, + OP_2OVER: 112, + OP_2ROT: 113, + OP_2SWAP: 114, + OP_IFDUP: 115, + OP_DEPTH: 116, + OP_DROP: 117, + OP_DUP: 118, + OP_NIP: 119, + OP_OVER: 120, + OP_PICK: 121, + OP_ROLL: 122, + OP_ROT: 123, + OP_SWAP: 124, + OP_TUCK: 125, + + OP_CAT: 126, + OP_SUBSTR: 127, + OP_LEFT: 128, + OP_RIGHT: 129, + OP_SIZE: 130, + + OP_INVERT: 131, + OP_AND: 132, + OP_OR: 133, + OP_XOR: 134, + OP_EQUAL: 135, + OP_EQUALVERIFY: 136, + OP_RESERVED1: 137, + OP_RESERVED2: 138, + + OP_1ADD: 139, + OP_1SUB: 140, + OP_2MUL: 141, + OP_2DIV: 142, + OP_NEGATE: 143, + OP_ABS: 144, + OP_NOT: 145, + OP_0NOTEQUAL: 146, + OP_ADD: 147, + OP_SUB: 148, + OP_MUL: 149, + OP_DIV: 150, + OP_MOD: 151, + OP_LSHIFT: 152, + OP_RSHIFT: 153, + + OP_BOOLAND: 154, + OP_BOOLOR: 155, + OP_NUMEQUAL: 156, + OP_NUMEQUALVERIFY: 157, + OP_NUMNOTEQUAL: 158, + OP_LESSTHAN: 159, + OP_GREATERTHAN: 160, + OP_LESSTHANOREQUAL: 161, + OP_GREATERTHANOREQUAL: 162, + OP_MIN: 163, + OP_MAX: 164, + + OP_WITHIN: 165, + + OP_RIPEMD160: 166, + OP_SHA1: 167, + OP_SHA256: 168, + OP_HASH160: 169, + OP_HASH256: 170, + OP_CODESEPARATOR: 171, + OP_CHECKSIG: 172, + OP_CHECKSIGVERIFY: 173, + OP_CHECKMULTISIG: 174, + OP_CHECKMULTISIGVERIFY: 175, + + OP_NOP1: 176, + + OP_NOP2: 177, + OP_CHECKLOCKTIMEVERIFY: 177, + + OP_NOP3: 178, + OP_CHECKSEQUENCEVERIFY: 178, + + OP_NOP4: 179, + OP_NOP5: 180, + OP_NOP6: 181, + OP_NOP7: 182, + OP_NOP8: 183, + OP_NOP9: 184, + OP_NOP10: 185, + + OP_PUBKEYHASH: 253, + OP_PUBKEY: 254, + OP_INVALIDOPCODE: 255, +}; + +const REVERSE_OPS: { [key: number]: string } = {}; +for (const op of Object.keys(OPS)) { + const code = OPS[op]; + REVERSE_OPS[code] = op; +} + +export { OPS, REVERSE_OPS }; diff --git a/ts_src/payments/embed.ts b/ts_src/payments/embed.ts index c54e279..c479b89 100644 --- a/ts_src/payments/embed.ts +++ b/ts_src/payments/embed.ts @@ -1,9 +1,9 @@ import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; +import { typeforce as typef } from '../types'; import { Payment, PaymentOpts, Stack } from './index'; import * as lazy from './lazy'; -const typef = require('typeforce'); const OPS = bscript.OPS; function stacksEqual(a: Buffer[], b: Buffer[]): boolean { diff --git a/ts_src/payments/p2ms.ts b/ts_src/payments/p2ms.ts index 7cd6f10..eaa1440 100644 --- a/ts_src/payments/p2ms.ts +++ b/ts_src/payments/p2ms.ts @@ -1,10 +1,9 @@ import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; +import { isPoint, typeforce as typef } from '../types'; import { Payment, PaymentOpts, Stack } from './index'; import * as lazy from './lazy'; const OPS = bscript.OPS; -const typef = require('typeforce'); -const ecc = require('tiny-secp256k1'); const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 @@ -41,7 +40,7 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment { m: typef.maybe(typef.Number), n: typef.maybe(typef.Number), output: typef.maybe(typef.Buffer), - pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), + pubkeys: typef.maybe(typef.arrayOf(isPoint)), signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), input: typef.maybe(typef.Buffer), @@ -119,7 +118,7 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment { if (o.m! <= 0 || o.n! > 16 || o.m! > o.n! || o.n !== chunks.length - 3) throw new TypeError('Output is invalid'); - if (!o.pubkeys!.every(x => ecc.isPoint(x))) + if (!o.pubkeys!.every(x => isPoint(x))) throw new TypeError('Output is invalid'); if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch'); diff --git a/ts_src/payments/p2pk.ts b/ts_src/payments/p2pk.ts index b4fdbc5..7273f53 100644 --- a/ts_src/payments/p2pk.ts +++ b/ts_src/payments/p2pk.ts @@ -1,10 +1,9 @@ import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; +import { isPoint, typeforce as typef } from '../types'; import { Payment, PaymentOpts, StackFunction } from './index'; import * as lazy from './lazy'; -const typef = require('typeforce'); const OPS = bscript.OPS; -const ecc = require('tiny-secp256k1'); // input: {signature} // output: {pubKey} OP_CHECKSIG @@ -17,7 +16,7 @@ export function p2pk(a: Payment, opts?: PaymentOpts): Payment { { network: typef.maybe(typef.Object), output: typef.maybe(typef.Buffer), - pubkey: typef.maybe(ecc.isPoint), + pubkey: typef.maybe(isPoint), signature: typef.maybe(bscript.isCanonicalScriptSignature), input: typef.maybe(typef.Buffer), @@ -58,8 +57,7 @@ export function p2pk(a: Payment, opts?: PaymentOpts): Payment { if (a.output) { if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) throw new TypeError('Output is invalid'); - if (!ecc.isPoint(o.pubkey)) - throw new TypeError('Output pubkey is invalid'); + if (!isPoint(o.pubkey)) throw new TypeError('Output pubkey is invalid'); if (a.pubkey && !a.pubkey.equals(o.pubkey!)) throw new TypeError('Pubkey mismatch'); } diff --git a/ts_src/payments/p2pkh.ts b/ts_src/payments/p2pkh.ts index 6503093..3b5bd6f 100644 --- a/ts_src/payments/p2pkh.ts +++ b/ts_src/payments/p2pkh.ts @@ -1,13 +1,11 @@ import * as bcrypto from '../crypto'; import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; +import { isPoint, typeforce as typef } from '../types'; import { Payment, PaymentOpts, StackFunction } from './index'; import * as lazy from './lazy'; -const typef = require('typeforce'); +import * as bs58check from 'bs58check'; const OPS = bscript.OPS; -const ecc = require('tiny-secp256k1'); - -const bs58check = require('bs58check'); // input: {signature} {pubkey} // output: OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG @@ -23,7 +21,7 @@ export function p2pkh(a: Payment, opts?: PaymentOpts): Payment { hash: typef.maybe(typef.BufferN(20)), output: typef.maybe(typef.BufferN(25)), - pubkey: typef.maybe(ecc.isPoint), + pubkey: typef.maybe(isPoint), signature: typef.maybe(bscript.isCanonicalScriptSignature), input: typef.maybe(typef.Buffer), }, @@ -31,7 +29,7 @@ export function p2pkh(a: Payment, opts?: PaymentOpts): Payment { ); const _address = lazy.value(() => { - const payload = bs58check.decode(a.address); + const payload = bs58check.decode(a.address!); const version = payload.readUInt8(0); const hash = payload.slice(1); return { version, hash }; @@ -129,8 +127,7 @@ export function p2pkh(a: Payment, opts?: PaymentOpts): Payment { if (chunks.length !== 2) throw new TypeError('Input is invalid'); if (!bscript.isCanonicalScriptSignature(chunks[0] as Buffer)) throw new TypeError('Input has invalid signature'); - if (!ecc.isPoint(chunks[1])) - throw new TypeError('Input has invalid pubkey'); + if (!isPoint(chunks[1])) throw new TypeError('Input has invalid pubkey'); if (a.signature && !a.signature.equals(chunks[0] as Buffer)) throw new TypeError('Signature mismatch'); diff --git a/ts_src/payments/p2sh.ts b/ts_src/payments/p2sh.ts index 3b53fdc..9be5a8c 100644 --- a/ts_src/payments/p2sh.ts +++ b/ts_src/payments/p2sh.ts @@ -1,6 +1,7 @@ import * as bcrypto from '../crypto'; import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; +import { typeforce as typef } from '../types'; import { Payment, PaymentFunction, @@ -9,11 +10,9 @@ import { StackFunction, } from './index'; import * as lazy from './lazy'; -const typef = require('typeforce'); +import * as bs58check from 'bs58check'; const OPS = bscript.OPS; -const bs58check = require('bs58check'); - function stacksEqual(a: Buffer[], b: Buffer[]): boolean { if (a.length !== b.length) return false; @@ -58,7 +57,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment { const o: Payment = { network }; const _address = lazy.value(() => { - const payload = bs58check.decode(a.address); + const payload = bs58check.decode(a.address!); const version = payload.readUInt8(0); const hash = payload.slice(1); return { version, hash }; diff --git a/ts_src/payments/p2wpkh.ts b/ts_src/payments/p2wpkh.ts index 3d8f86f..a4497fe 100644 --- a/ts_src/payments/p2wpkh.ts +++ b/ts_src/payments/p2wpkh.ts @@ -1,13 +1,11 @@ import * as bcrypto from '../crypto'; import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; +import { isPoint, typeforce as typef } from '../types'; import { Payment, PaymentOpts } from './index'; import * as lazy from './lazy'; -const typef = require('typeforce'); +import { bech32 } from 'bech32'; const OPS = bscript.OPS; -const ecc = require('tiny-secp256k1'); - -const { bech32 } = require('bech32'); const EMPTY_BUFFER = Buffer.alloc(0); @@ -26,7 +24,7 @@ export function p2wpkh(a: Payment, opts?: PaymentOpts): Payment { input: typef.maybe(typef.BufferN(0)), network: typef.maybe(typef.Object), output: typef.maybe(typef.BufferN(22)), - pubkey: typef.maybe(ecc.isPoint), + pubkey: typef.maybe(isPoint), signature: typef.maybe(bscript.isCanonicalScriptSignature), witness: typef.maybe(typef.arrayOf(typef.Buffer)), }, @@ -34,7 +32,7 @@ export function p2wpkh(a: Payment, opts?: PaymentOpts): Payment { ); const _address = lazy.value(() => { - const result = bech32.decode(a.address); + const result = bech32.decode(a.address!); const version = result.words.shift(); const data = bech32.fromWords(result.words); return { @@ -118,7 +116,7 @@ export function p2wpkh(a: Payment, opts?: PaymentOpts): Payment { if (hash.length > 0 && !hash.equals(pkh)) throw new TypeError('Hash mismatch'); else hash = pkh; - if (!ecc.isPoint(a.pubkey) || a.pubkey.length !== 33) + if (!isPoint(a.pubkey) || a.pubkey.length !== 33) throw new TypeError('Invalid pubkey for p2wpkh'); } @@ -126,7 +124,7 @@ export function p2wpkh(a: Payment, opts?: PaymentOpts): Payment { if (a.witness.length !== 2) throw new TypeError('Witness is invalid'); if (!bscript.isCanonicalScriptSignature(a.witness[0])) throw new TypeError('Witness has invalid signature'); - if (!ecc.isPoint(a.witness[1]) || a.witness[1].length !== 33) + if (!isPoint(a.witness[1]) || a.witness[1].length !== 33) throw new TypeError('Witness has invalid pubkey'); if (a.signature && !a.signature.equals(a.witness[0])) diff --git a/ts_src/payments/p2wsh.ts b/ts_src/payments/p2wsh.ts index e28cd57..00860e0 100644 --- a/ts_src/payments/p2wsh.ts +++ b/ts_src/payments/p2wsh.ts @@ -1,13 +1,11 @@ import * as bcrypto from '../crypto'; import { bitcoin as BITCOIN_NETWORK } from '../networks'; import * as bscript from '../script'; +import { isPoint, typeforce as typef } from '../types'; import { Payment, PaymentOpts, StackElement, StackFunction } from './index'; import * as lazy from './lazy'; -const typef = require('typeforce'); +import { bech32 } from 'bech32'; const OPS = bscript.OPS; -const ecc = require('tiny-secp256k1'); - -const { bech32 } = require('bech32'); const EMPTY_BUFFER = Buffer.alloc(0); @@ -24,7 +22,7 @@ function chunkHasUncompressedPubkey(chunk: StackElement): boolean { Buffer.isBuffer(chunk) && chunk.length === 65 && chunk[0] === 0x04 && - ecc.isPoint(chunk) + isPoint(chunk) ) { return true; } else { @@ -61,7 +59,7 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment { ); const _address = lazy.value(() => { - const result = bech32.decode(a.address); + const result = bech32.decode(a.address!); const version = result.words.shift(); const data = bech32.fromWords(result.words); return { diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index c23fa1f..b9af10f 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -16,11 +16,6 @@ import { checkForInput, checkForOutput } from 'bip174/src/lib/utils'; import { fromOutputScript, toOutputScript } from './address'; import { cloneBuffer, reverseBuffer } from './bufferutils'; import { hash160 } from './crypto'; -import { - fromPublicKey as ecPairFromPublicKey, - Signer, - SignerAsync, -} from './ecpair'; import { bitcoin as btcNetwork, Network } from './networks'; import * as payments from './payments'; import * as bscript from './script'; @@ -45,6 +40,13 @@ export interface PsbtTxOutput extends TransactionOutput { address: string | undefined; } +// msghash is 32 byte hash of preimage, signature is 64 byte compact signature (r,s 32 bytes each) +export type ValidateSigFunction = ( + pubkey: Buffer, + msghash: Buffer, + signature: Buffer, +) => boolean; + /** * These are the default arguments for a Psbt instance. */ @@ -416,19 +418,25 @@ export class Psbt { ); } - validateSignaturesOfAllInputs(): boolean { + validateSignaturesOfAllInputs(validator: ValidateSigFunction): boolean { checkForInput(this.data.inputs, 0); // making sure we have at least one const results = range(this.data.inputs.length).map(idx => - this.validateSignaturesOfInput(idx), + this.validateSignaturesOfInput(idx, validator), ); return results.reduce((final, res) => res === true && final, true); } - validateSignaturesOfInput(inputIndex: number, pubkey?: Buffer): boolean { + validateSignaturesOfInput( + inputIndex: number, + validator: ValidateSigFunction, + pubkey?: Buffer, + ): boolean { const input = this.data.inputs[inputIndex]; const partialSig = (input || {}).partialSig; if (!input || !partialSig || partialSig.length < 1) throw new Error('No signatures to validate'); + if (typeof validator !== 'function') + throw new Error('Need validator function to validate signatures'); const mySigs = pubkey ? partialSig.filter(sig => sig.pubkey.equals(pubkey)) : partialSig; @@ -452,8 +460,7 @@ export class Psbt { hashCache = hash; scriptCache = script; checkScriptForPubkey(pSig.pubkey, script, 'verify'); - const keypair = ecPairFromPublicKey(pSig.pubkey); - results.push(keypair.verify(hash, sig.signature)); + results.push(validator(pSig.pubkey, hash, sig.signature)); } return results.every(res => res === true); } @@ -780,7 +787,7 @@ interface HDSignerBase { fingerprint: Buffer; } -interface HDSigner extends HDSignerBase { +export interface HDSigner extends HDSignerBase { /** * The path string must match /^m(\/\d+'?)+$/ * ex. m/44'/0'/0'/1/23 levels with ' must be hard derivations @@ -796,11 +803,25 @@ interface HDSigner extends HDSignerBase { /** * Same as above but with async sign method */ -interface HDSignerAsync extends HDSignerBase { +export interface HDSignerAsync extends HDSignerBase { derivePath(path: string): HDSignerAsync; sign(hash: Buffer): Promise; } +export interface Signer { + publicKey: Buffer; + network?: any; + sign(hash: Buffer, lowR?: boolean): Buffer; + getPublicKey?(): Buffer; +} + +export interface SignerAsync { + publicKey: Buffer; + network?: any; + sign(hash: Buffer, lowR?: boolean): Promise; + getPublicKey?(): Buffer; +} + /** * This function is needed to pass to the bip174 base class's fromBuffer. * It takes the "transaction buffer" portion of the psbt buffer and returns a @@ -903,8 +924,7 @@ function hasSigs( if (pubkeys) { sigs = pubkeys .map(pkey => { - const pubkey = ecPairFromPublicKey(pkey, { compressed: true }) - .publicKey; + const pubkey = compressPubkey(pkey); return partialSig.find(pSig => pSig.pubkey.equals(pubkey)); }) .filter(v => !!v); @@ -1305,7 +1325,7 @@ function getHashForSig( console.warn( 'Warning: Signing non-segwit inputs without the full parent transaction ' + 'means there is a chance that a miner could feed you incorrect information ' + - 'to trick you into paying large fees. This behavior is the same as Psbt\'s predecesor ' + + "to trick you into paying large fees. This behavior is the same as Psbt's predecesor " + '(TransactionBuilder - now removed) when signing non-segwit scripts. You are not ' + 'able to export this Psbt with toBuffer|toBase64|toHex since it is not ' + 'BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n' + @@ -1714,6 +1734,16 @@ function redeemFromFinalWitnessScript( return lastItem; } +function compressPubkey(pubkey: Buffer): Buffer { + if (pubkey.length === 65) { + const parity = pubkey[64] & 1; + const newKey = pubkey.slice(0, 33); + newKey[0] = 2 | parity; + return newKey; + } + return pubkey.slice(); +} + function isPubkeyLike(buf: Buffer): boolean { return buf.length === 33 && bscript.isCanonicalPubKey(buf); } diff --git a/ts_src/push_data.ts b/ts_src/push_data.ts new file mode 100644 index 0000000..56bb02a --- /dev/null +++ b/ts_src/push_data.ts @@ -0,0 +1,76 @@ +import { OPS } from './ops'; + +export function encodingLength(i: number): number { + return i < OPS.OP_PUSHDATA1 ? 1 : i <= 0xff ? 2 : i <= 0xffff ? 3 : 5; +} + +export function encode(buffer: Buffer, num: number, offset: number): number { + const size = encodingLength(num); + + // ~6 bit + if (size === 1) { + buffer.writeUInt8(num, offset); + + // 8 bit + } else if (size === 2) { + buffer.writeUInt8(OPS.OP_PUSHDATA1, offset); + buffer.writeUInt8(num, offset + 1); + + // 16 bit + } else if (size === 3) { + buffer.writeUInt8(OPS.OP_PUSHDATA2, offset); + buffer.writeUInt16LE(num, offset + 1); + + // 32 bit + } else { + buffer.writeUInt8(OPS.OP_PUSHDATA4, offset); + buffer.writeUInt32LE(num, offset + 1); + } + + return size; +} + +export function decode( + buffer: Buffer, + offset: number, +): { + opcode: number; + number: number; + size: number; +} | null { + const opcode = buffer.readUInt8(offset); + let num: number; + let size: number; + + // ~6 bit + if (opcode < OPS.OP_PUSHDATA1) { + num = opcode; + size = 1; + + // 8 bit + } else if (opcode === OPS.OP_PUSHDATA1) { + if (offset + 2 > buffer.length) return null; + num = buffer.readUInt8(offset + 1); + size = 2; + + // 16 bit + } else if (opcode === OPS.OP_PUSHDATA2) { + if (offset + 3 > buffer.length) return null; + num = buffer.readUInt16LE(offset + 1); + size = 3; + + // 32 bit + } else { + if (offset + 5 > buffer.length) return null; + if (opcode !== OPS.OP_PUSHDATA4) throw new Error('Unexpected opcode'); + + num = buffer.readUInt32LE(offset + 1); + size = 5; + } + + return { + opcode, + number: num, + size, + }; +} diff --git a/ts_src/script.ts b/ts_src/script.ts index 951f48b..5d20ebc 100644 --- a/ts_src/script.ts +++ b/ts_src/script.ts @@ -1,17 +1,14 @@ +import * as bip66 from './bip66'; +import { OPS, REVERSE_OPS } from './ops'; import { Stack } from './payments'; +import * as pushdata from './push_data'; import * as scriptNumber from './script_number'; import * as scriptSignature from './script_signature'; import * as types from './types'; -const bip66 = require('bip66'); -const ecc = require('tiny-secp256k1'); -const pushdata = require('pushdata-bitcoin'); -const typeforce = require('typeforce'); +const { typeforce } = types; -export type OpCode = number; -export const OPS = require('bitcoin-ops') as { [index: string]: OpCode }; - -const REVERSE_OPS = require('bitcoin-ops/map') as { [index: number]: string }; const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1 +export { OPS }; function isOPInt(value: number): boolean { return ( @@ -194,7 +191,7 @@ export function toStack(chunks: Buffer | Array): Buffer[] { } export function isCanonicalPubKey(buffer: Buffer): boolean { - return ecc.isPoint(buffer); + return types.isPoint(buffer); } export function isDefinedHashType(hashType: number): boolean { diff --git a/ts_src/script_signature.ts b/ts_src/script_signature.ts index af9930e..df206e8 100644 --- a/ts_src/script_signature.ts +++ b/ts_src/script_signature.ts @@ -1,7 +1,6 @@ +import * as bip66 from './bip66'; import * as types from './types'; -const bip66 = require('bip66'); - -const typeforce = require('typeforce'); +const { typeforce } = types; const ZERO = Buffer.alloc(1, 0); function toDER(x: Buffer): Buffer { diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts index b1ac302..c5dde9a 100644 --- a/ts_src/transaction.ts +++ b/ts_src/transaction.ts @@ -1,11 +1,14 @@ -import { BufferReader, BufferWriter, reverseBuffer } from './bufferutils'; +import { + BufferReader, + BufferWriter, + reverseBuffer, + varuint, +} from './bufferutils'; import * as bcrypto from './crypto'; import * as bscript from './script'; import { OPS as opcodes } from './script'; import * as types from './types'; - -const typeforce = require('typeforce'); -const varuint = require('varuint-bitcoin'); +const { typeforce } = types; function varSliceSize(someScript: Buffer): number { const length = someScript.length; diff --git a/ts_src/types.ts b/ts_src/types.ts index 2e41267..c035b40 100644 --- a/ts_src/types.ts +++ b/ts_src/types.ts @@ -1,4 +1,29 @@ -const typeforce = require('typeforce'); +import { Buffer as NBuffer } from 'buffer'; +export const typeforce = require('typeforce'); + +const ZERO32 = NBuffer.alloc(32, 0); +const EC_P = NBuffer.from( + 'fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f', + 'hex', +); +export function isPoint(p: Buffer | number | undefined | null): boolean { + if (!NBuffer.isBuffer(p)) return false; + if (p.length < 33) return false; + + const t = p[0]; + const x = p.slice(1, 33); + if (x.compare(ZERO32) === 0) return false; + if (x.compare(EC_P) >= 0) return false; + if ((t === 0x02 || t === 0x03) && p.length === 33) { + return true; + } + + const y = p.slice(33); + if (y.compare(ZERO32) === 0) return false; + if (y.compare(EC_P) >= 0) return false; + if (t === 0x04 && p.length === 65) return true; + return false; +} const UINT31_MAX: number = Math.pow(2, 31) - 1; export function UInt31(value: number): boolean { diff --git a/tsconfig.json b/tsconfig.json index f770a45..3c74061 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,9 @@ { "compilerOptions": { - "target": "ES2015", + "target": "ESNEXT", "module": "commonjs", "outDir": "./src", "declaration": true, - "declarationDir": "./types", "rootDir": "./ts_src", "types": [ "node" diff --git a/tslint.json b/tslint.json index d42da60..90e513d 100644 --- a/tslint.json +++ b/tslint.json @@ -2,7 +2,8 @@ "defaultSeverity": "error", "extends": ["tslint:recommended"], "rules": { - "arrow-parens": [true, "ban-single-arg-parens"], + "array-type": false, + "arrow-parens": false, "curly": false, "indent": [ true, @@ -25,7 +26,6 @@ "typedef": [ true, "call-signature", - "arrow-call-signature", "property-declaration" ], "variable-name": [ diff --git a/types/ecpair.d.ts b/types/ecpair.d.ts deleted file mode 100644 index 447c608..0000000 --- a/types/ecpair.d.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Network } from './networks'; -interface ECPairOptions { - compressed?: boolean; - network?: Network; - rng?(arg0: number): Buffer; -} -export interface Signer { - publicKey: Buffer; - network?: any; - sign(hash: Buffer, lowR?: boolean): Buffer; - getPublicKey?(): Buffer; -} -export interface SignerAsync { - publicKey: Buffer; - network?: any; - sign(hash: Buffer, lowR?: boolean): Promise; - getPublicKey?(): Buffer; -} -export interface ECPairInterface extends Signer { - compressed: boolean; - network: Network; - lowR: boolean; - privateKey?: Buffer; - toWIF(): string; - verify(hash: Buffer, signature: Buffer): boolean; -} -declare class ECPair implements ECPairInterface { - private __D?; - private __Q?; - compressed: boolean; - network: Network; - lowR: boolean; - constructor(__D?: Buffer | undefined, __Q?: Buffer | undefined, options?: ECPairOptions); - readonly privateKey: Buffer | undefined; - readonly publicKey: Buffer; - toWIF(): string; - sign(hash: Buffer, lowR?: boolean): Buffer; - verify(hash: Buffer, signature: Buffer): boolean; -} -declare function fromPrivateKey(buffer: Buffer, options?: ECPairOptions): ECPair; -declare function fromPublicKey(buffer: Buffer, options?: ECPairOptions): ECPair; -declare function fromWIF(wifString: string, network?: Network | Network[]): ECPair; -declare function makeRandom(options?: ECPairOptions): ECPair; -export { makeRandom, fromPrivateKey, fromPublicKey, fromWIF };