From 9e67e0ff4368c55cff170dd8d5e076f293a87cc3 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 22 Sep 2021 14:02:17 +0200 Subject: [PATCH 01/14] block deser dynafed support --- .idea/.gitignore | 5 + .idea/codeStyles/Project.xml | 61 + .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/inspectionProfiles/Project_Default.xml | 6 + .idea/liquidjs-lib.iml | 12 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + package-lock.json | 197 +- package.json | 5 +- src/address.js | 524 ++-- src/block.js | 594 ++-- src/bufferutils.js | 411 ++- src/classify.js | 128 +- src/confidential.js | 333 +- src/crypto.js | 39 +- src/ecpair.js | 193 +- src/index.js | 40 +- src/issuance.js | 154 +- src/networks.js | 52 +- src/payments/embed.js | 94 +- src/payments/index.js | 18 +- src/payments/lazy.js | 47 +- src/payments/p2ms.js | 275 +- src/payments/p2pk.js | 141 +- src/payments/p2pkh.js | 383 +-- src/payments/p2sh.js | 469 +-- src/payments/p2wpkh.js | 365 +-- src/payments/p2wsh.js | 459 ++- src/psbt.js | 2905 ++++++++---------- src/script.js | 276 +- src/script_number.js | 108 +- src/script_signature.js | 89 +- src/sha256d.js | 558 ++-- src/templates/multisig/index.js | 20 +- src/templates/multisig/input.js | 42 +- src/templates/multisig/output.js | 61 +- src/templates/nulldata.js | 24 +- src/templates/pubkey/index.js | 20 +- src/templates/pubkey/input.js | 25 +- src/templates/pubkey/output.js | 32 +- src/templates/pubkeyhash/index.js | 20 +- src/templates/pubkeyhash/input.js | 30 +- src/templates/pubkeyhash/output.js | 38 +- src/templates/scripthash/index.js | 20 +- src/templates/scripthash/input.js | 94 +- src/templates/scripthash/output.js | 34 +- src/templates/witnesscommitment/index.js | 18 +- src/templates/witnesscommitment/output.js | 50 +- src/templates/witnesspubkeyhash/index.js | 20 +- src/templates/witnesspubkeyhash/input.js | 32 +- src/templates/witnesspubkeyhash/output.js | 30 +- src/templates/witnessscripthash/index.js | 20 +- src/templates/witnessscripthash/input.js | 73 +- src/templates/witnessscripthash/output.js | 30 +- src/transaction.js | 1162 ++++--- src/types.js | 40 +- test/block.spec.ts | 9 + test/fixtures/block_deserialize.json | 24 + ts_src/block.ts | 162 +- tsconfig.json | 3 +- types/block.d.ts | 20 + 61 files changed, 5544 insertions(+), 5569 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/liquidjs-lib.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 test/block.spec.ts create mode 100644 test/fixtures/block_deserialize.json diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..b58b603fe --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 000000000..7ba3bc069 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 000000000..79ee123c2 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000..df7825df6 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/liquidjs-lib.iml b/.idea/liquidjs-lib.iml new file mode 100644 index 000000000..0c8867d7e --- /dev/null +++ b/.idea/liquidjs-lib.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..731207430 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..94a25f7f4 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index afc687abb..2e80f9b34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -140,6 +140,63 @@ "to-fast-properties": "^2.0.0" } }, + "@jest/types": { + "version": "27.1.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.1.1.tgz", + "integrity": "sha512-yqJPDDseb0mXgKqmNqypCsb85C22K1aY5+LUxh7syIM9n/b0AsaltxNy+o6tt29VcfGDpYEve175bm3uOhcehA==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "@types/bs58": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz", @@ -158,6 +215,36 @@ "@types/node": "*" } }, + "@types/istanbul-lib-coverage": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", + "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==" + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz", + "integrity": "sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==", + "requires": { + "jest-diff": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, "@types/mocha": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", @@ -167,8 +254,7 @@ "@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==", - "dev": true + "integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==" }, "@types/proxyquire": { "version": "1.3.28", @@ -176,6 +262,19 @@ "integrity": "sha512-SQaNzWQ2YZSr7FqAyPPiA3FYpux2Lqh3HWMZQk47x3xbMCqgC/w0dY3dw9rGqlweDDkrySQBcaScXWeR+Yb11Q==", "dev": true }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" + }, "@vulpemventures/secp256k1-zkp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@vulpemventures/secp256k1-zkp/-/secp256k1-zkp-2.0.0.tgz", @@ -657,6 +756,11 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "diff-sequences": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", + "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==" + }, "elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", @@ -1231,6 +1335,67 @@ "html-escaper": "^2.0.0" } }, + "jest-diff": { + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.2.0.tgz", + "integrity": "sha512-QSO9WC6btFYWtRJ3Hac0sRrkspf7B01mGrrQEiCW6TobtViJ9RWL0EmOs/WnBsZDsI/Y2IoSHZA2x6offu0sYw==", + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1697,6 +1862,29 @@ "integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==", "dev": true }, + "pretty-format": { + "version": "27.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.2.0.tgz", + "integrity": "sha512-KyJdmgBkMscLqo8A7K77omgLx5PWPiXJswtTtFV7XgVZv2+qPk6UivpXXO+5k6ZEbWIbLoKdx1pZ6ldINzbwTA==", + "requires": { + "@jest/types": "^27.1.1", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" + } + } + }, "proxyquire": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", @@ -1730,6 +1918,11 @@ "safe-buffer": "^5.1.0" } }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", diff --git a/package.json b/package.json index 496da6a99..14ebbb63d 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "types" ], "dependencies": { + "@types/jest": "^27.0.2", "@vulpemventures/secp256k1-zkp": "^2.0.0", "axios": "^0.21.1", "bech32": "^1.1.2", @@ -88,8 +89,8 @@ "proxyquire": "^2.0.1", "rimraf": "^2.6.3", "ts-node": "^8.3.0", - "tslint": "^5.16.0", - "typescript": "3.2.2" + "tslint": "^5.20.1", + "typescript": "^3.2.2" }, "license": "MIT" } diff --git a/src/address.js b/src/address.js index e35453133..b9f87b6e3 100644 --- a/src/address.js +++ b/src/address.js @@ -1,337 +1,325 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -var __importDefault = - (this && this.__importDefault) || - function(mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const networks = __importStar(require('./networks')); -const payments = __importStar(require('./payments')); -const bscript = __importStar(require('./script')); -const types = __importStar(require('./types')); -const blech32_1 = require('blech32'); -const bech32_1 = __importDefault(require('bech32')); -const bs58check_1 = __importDefault(require('bs58check')); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const networks = __importStar(require("./networks")); +const payments = __importStar(require("./payments")); +const bscript = __importStar(require("./script")); +const types = __importStar(require("./types")); +const blech32_1 = require("blech32"); +const bech32_1 = __importDefault(require("bech32")); +const bs58check_1 = __importDefault(require("bs58check")); const typeforce = require('typeforce'); // negative value for confidential types var AddressType; -(function(AddressType) { - AddressType[(AddressType['P2Pkh'] = 0)] = 'P2Pkh'; - AddressType[(AddressType['P2Sh'] = 1)] = 'P2Sh'; - AddressType[(AddressType['P2Wpkh'] = 2)] = 'P2Wpkh'; - AddressType[(AddressType['P2Wsh'] = 3)] = 'P2Wsh'; - AddressType[(AddressType['ConfidentialP2Pkh'] = 4)] = 'ConfidentialP2Pkh'; - AddressType[(AddressType['ConfidentialP2Sh'] = 5)] = 'ConfidentialP2Sh'; - AddressType[(AddressType['ConfidentialP2Wpkh'] = 6)] = 'ConfidentialP2Wpkh'; - AddressType[(AddressType['ConfidentialP2Wsh'] = 7)] = 'ConfidentialP2Wsh'; +(function (AddressType) { + AddressType[AddressType["P2Pkh"] = 0] = "P2Pkh"; + AddressType[AddressType["P2Sh"] = 1] = "P2Sh"; + AddressType[AddressType["P2Wpkh"] = 2] = "P2Wpkh"; + AddressType[AddressType["P2Wsh"] = 3] = "P2Wsh"; + AddressType[AddressType["ConfidentialP2Pkh"] = 4] = "ConfidentialP2Pkh"; + AddressType[AddressType["ConfidentialP2Sh"] = 5] = "ConfidentialP2Sh"; + AddressType[AddressType["ConfidentialP2Wpkh"] = 6] = "ConfidentialP2Wpkh"; + AddressType[AddressType["ConfidentialP2Wsh"] = 7] = "ConfidentialP2Wsh"; })(AddressType || (AddressType = {})); function isConfidentialAddressType(addressType) { - return addressType >= 4; + return addressType >= 4; } function fromBase58Check(address) { - const payload = bs58check_1.default.decode(address); - // TODO: 4.0.0, move to "toOutputScript" - if (payload.length < 21) throw new TypeError(address + ' is too short'); - if (payload.length > 21) throw new TypeError(address + ' is too long'); - const version = payload.readUInt8(0); - const hash = payload.slice(1); - return { version, hash }; + const payload = bs58check_1.default.decode(address); + // TODO: 4.0.0, move to "toOutputScript" + if (payload.length < 21) + throw new TypeError(address + ' is too short'); + if (payload.length > 21) + throw new TypeError(address + ' is too long'); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; } exports.fromBase58Check = fromBase58Check; function fromBech32(address) { - const result = bech32_1.default.decode(address); - const data = bech32_1.default.fromWords(result.words.slice(1)); - return { - version: result.words[0], - prefix: result.prefix, - data: Buffer.from(data), - }; + const result = bech32_1.default.decode(address); + const data = bech32_1.default.fromWords(result.words.slice(1)); + return { + version: result.words[0], + prefix: result.prefix, + data: Buffer.from(data), + }; } exports.fromBech32 = fromBech32; function fromBlech32(address) { - const result = blech32_1.Blech32Address.fromString(address); - const pubkey = Buffer.from(result.blindingPublicKey, 'hex'); - const prg = Buffer.from(result.witness, 'hex'); - const data = Buffer.concat([ - Buffer.from([result.witnessVersion, prg.length]), - prg, - ]); - return { - version: result.witnessVersion, - pubkey, - data, - }; + const result = blech32_1.Blech32Address.fromString(address); + const pubkey = Buffer.from(result.blindingPublicKey, 'hex'); + const prg = Buffer.from(result.witness, 'hex'); + const data = Buffer.concat([ + Buffer.from([result.witnessVersion, prg.length]), + prg, + ]); + return { + version: result.witnessVersion, + pubkey, + data, + }; } exports.fromBlech32 = fromBlech32; function fromConfidential(address) { - const network = getNetwork(address); - if (address.startsWith(network.blech32)) - return fromConfidentialSegwit(address, network); - return fromConfidentialLegacy(address, network); + const network = getNetwork(address); + if (address.startsWith(network.blech32)) + return fromConfidentialSegwit(address, network); + return fromConfidentialLegacy(address, network); } exports.fromConfidential = fromConfidential; function toBase58Check(hash, version) { - typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments); - const payload = Buffer.allocUnsafe(21); - payload.writeUInt8(version, 0); - hash.copy(payload, 1); - return bs58check_1.default.encode(payload); + typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments); + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(version, 0); + hash.copy(payload, 1); + return bs58check_1.default.encode(payload); } exports.toBase58Check = toBase58Check; function toBech32(data, version, prefix) { - const words = bech32_1.default.toWords(data); - words.unshift(version); - return bech32_1.default.encode(prefix, words); + const words = bech32_1.default.toWords(data); + words.unshift(version); + return bech32_1.default.encode(prefix, words); } exports.toBech32 = toBech32; function toBlech32(data, pubkey, prefix) { - return blech32_1.Blech32Address.from( - data.slice(2).toString('hex'), - pubkey.toString('hex'), - prefix, - ).address; + return blech32_1.Blech32Address.from(data.slice(2).toString('hex'), pubkey.toString('hex'), prefix).address; } exports.toBlech32 = toBlech32; function toConfidential(address, blindingKey) { - const network = getNetwork(address); - if (address.startsWith(network.bech32)) - return toConfidentialSegwit(address, blindingKey, network); - return toConfidentialLegacy(address, blindingKey, network); + const network = getNetwork(address); + if (address.startsWith(network.bech32)) + return toConfidentialSegwit(address, blindingKey, network); + return toConfidentialLegacy(address, blindingKey, network); } exports.toConfidential = toConfidential; function fromOutputScript(output, network) { - // TODO: Network - network = network || networks.liquid; - try { - return payments.p2pkh({ output, network }).address; - } catch (e) {} - try { - return payments.p2sh({ output, network }).address; - } catch (e) {} - try { - return payments.p2wpkh({ output, network }).address; - } catch (e) {} - try { - return payments.p2wsh({ output, network }).address; - } catch (e) {} - throw new Error(bscript.toASM(output) + ' has no matching Address'); + // TODO: Network + network = network || networks.liquid; + try { + return payments.p2pkh({ output, network }).address; + } + catch (e) { } + try { + return payments.p2sh({ output, network }).address; + } + catch (e) { } + try { + return payments.p2wpkh({ output, network }).address; + } + catch (e) { } + try { + return payments.p2wsh({ output, network }).address; + } + catch (e) { } + throw new Error(bscript.toASM(output) + ' has no matching Address'); } exports.fromOutputScript = fromOutputScript; function toOutputScript(address, network) { - network = network || getNetwork(address); - let decodeBase58result; - let decodeBech32result; - let decodeConfidentialresult; - try { - decodeBase58result = fromBase58Check(address); - } catch (e) {} - if (decodeBase58result) { - if (decodeBase58result.version === network.pubKeyHash) - return payments.p2pkh({ hash: decodeBase58result.hash }).output; - if (decodeBase58result.version === network.scriptHash) - return payments.p2sh({ hash: decodeBase58result.hash }).output; - } else { + network = network || getNetwork(address); + let decodeBase58result; + let decodeBech32result; + let decodeConfidentialresult; try { - decodeBech32result = fromBech32(address); - } catch (e) {} - if (decodeBech32result) { - if (decodeBech32result.prefix !== network.bech32) - throw new Error(address + ' has an invalid prefix'); - if (decodeBech32result.version === 0) { - if (decodeBech32result.data.length === 20) - return payments.p2wpkh({ hash: decodeBech32result.data }).output; - if (decodeBech32result.data.length === 32) - return payments.p2wsh({ hash: decodeBech32result.data }).output; - } - } else { - try { - decodeConfidentialresult = fromConfidential(address); - } catch (e) {} - if (decodeConfidentialresult) { - return toOutputScript( - decodeConfidentialresult.unconfidentialAddress, - network, - ); - } + decodeBase58result = fromBase58Check(address); } - } - throw new Error(address + ' has no matching Script'); + catch (e) { } + if (decodeBase58result) { + if (decodeBase58result.version === network.pubKeyHash) + return payments.p2pkh({ hash: decodeBase58result.hash }).output; + if (decodeBase58result.version === network.scriptHash) + return payments.p2sh({ hash: decodeBase58result.hash }).output; + } + else { + try { + decodeBech32result = fromBech32(address); + } + catch (e) { } + if (decodeBech32result) { + if (decodeBech32result.prefix !== network.bech32) + throw new Error(address + ' has an invalid prefix'); + if (decodeBech32result.version === 0) { + if (decodeBech32result.data.length === 20) + return payments.p2wpkh({ hash: decodeBech32result.data }) + .output; + if (decodeBech32result.data.length === 32) + return payments.p2wsh({ hash: decodeBech32result.data }) + .output; + } + } + else { + try { + decodeConfidentialresult = fromConfidential(address); + } + catch (e) { } + if (decodeConfidentialresult) { + return toOutputScript(decodeConfidentialresult.unconfidentialAddress, network); + } + } + } + throw new Error(address + ' has no matching Script'); } exports.toOutputScript = toOutputScript; function getNetwork(address) { - if ( - address.startsWith(networks.liquid.blech32) || - address.startsWith(networks.liquid.bech32) - ) - return networks.liquid; - if ( - address.startsWith(networks.regtest.blech32) || - address.startsWith(networks.regtest.bech32) - ) - return networks.regtest; - const payload = bs58check_1.default.decode(address); - const prefix = payload.readUInt8(0); - if ( - prefix === networks.liquid.confidentialPrefix || - prefix === networks.liquid.pubKeyHash || - prefix === networks.liquid.scriptHash - ) - return networks.liquid; - if ( - prefix === networks.regtest.confidentialPrefix || - prefix === networks.regtest.pubKeyHash || - prefix === networks.regtest.scriptHash - ) - return networks.regtest; - throw new Error(address + ' has an invalid prefix'); + if (address.startsWith(networks.liquid.blech32) || + address.startsWith(networks.liquid.bech32)) + return networks.liquid; + if (address.startsWith(networks.regtest.blech32) || + address.startsWith(networks.regtest.bech32)) + return networks.regtest; + const payload = bs58check_1.default.decode(address); + const prefix = payload.readUInt8(0); + if (prefix === networks.liquid.confidentialPrefix || + prefix === networks.liquid.pubKeyHash || + prefix === networks.liquid.scriptHash) + return networks.liquid; + if (prefix === networks.regtest.confidentialPrefix || + prefix === networks.regtest.pubKeyHash || + prefix === networks.regtest.scriptHash) + return networks.regtest; + throw new Error(address + ' has an invalid prefix'); } exports.getNetwork = getNetwork; function fromConfidentialLegacy(address, network) { - const payload = bs58check_1.default.decode(address); - const prefix = payload.readUInt8(1); - // Check if address has valid length and prefix - if (prefix !== network.pubKeyHash && prefix !== network.scriptHash) - throw new TypeError(address + 'is not valid'); - if (payload.length < 55) throw new TypeError(address + ' is too short'); - if (payload.length > 55) throw new TypeError(address + ' is too long'); - // Blinded decoded haddress has the form: - // BLIND_PREFIX|ADDRESS_PREFIX|BLINDING_KEY|SCRIPT_HASH - // Prefixes are 1 byte long, thus blinding key always starts at 3rd byte - const blindingKey = payload.slice(2, 35); - const unconfidential = payload.slice(35, payload.length); - const versionBuf = Buffer.alloc(1); - versionBuf[0] = prefix; - const unconfidentialAddressBuffer = Buffer.concat([ - versionBuf, - unconfidential, - ]); - const unconfidentialAddress = bs58check_1.default.encode( - unconfidentialAddressBuffer, - ); - return { blindingKey, unconfidentialAddress }; + const payload = bs58check_1.default.decode(address); + const prefix = payload.readUInt8(1); + // Check if address has valid length and prefix + if (prefix !== network.pubKeyHash && prefix !== network.scriptHash) + throw new TypeError(address + 'is not valid'); + if (payload.length < 55) + throw new TypeError(address + ' is too short'); + if (payload.length > 55) + throw new TypeError(address + ' is too long'); + // Blinded decoded haddress has the form: + // BLIND_PREFIX|ADDRESS_PREFIX|BLINDING_KEY|SCRIPT_HASH + // Prefixes are 1 byte long, thus blinding key always starts at 3rd byte + const blindingKey = payload.slice(2, 35); + const unconfidential = payload.slice(35, payload.length); + const versionBuf = Buffer.alloc(1); + versionBuf[0] = prefix; + const unconfidentialAddressBuffer = Buffer.concat([ + versionBuf, + unconfidential, + ]); + const unconfidentialAddress = bs58check_1.default.encode(unconfidentialAddressBuffer); + return { blindingKey, unconfidentialAddress }; } function fromConfidentialSegwit(address, network) { - const result = fromBlech32(address); - const unconfidentialAddress = fromOutputScript(result.data, network); - return { blindingKey: result.pubkey, unconfidentialAddress }; + const result = fromBlech32(address); + const unconfidentialAddress = fromOutputScript(result.data, network); + return { blindingKey: result.pubkey, unconfidentialAddress }; } function toConfidentialLegacy(address, blindingKey, network) { - const payload = bs58check_1.default.decode(address); - const prefix = payload.readUInt8(0); - // Check if address has valid length and prefix - if ( - payload.length !== 21 || - (prefix !== network.pubKeyHash && prefix !== network.scriptHash) - ) - throw new TypeError(address + 'is not valid'); - // Check if blind key has valid length - if (blindingKey.length < 33) throw new TypeError('Blinding key is too short'); - if (blindingKey.length > 33) throw new TypeError('Blinding key is too long'); - const prefixBuf = Buffer.alloc(2); - prefixBuf[0] = network.confidentialPrefix; - prefixBuf[1] = prefix; - const confidentialAddress = Buffer.concat([ - prefixBuf, - blindingKey, - Buffer.from(payload.slice(1)), - ]); - return bs58check_1.default.encode(confidentialAddress); + const payload = bs58check_1.default.decode(address); + const prefix = payload.readUInt8(0); + // Check if address has valid length and prefix + if (payload.length !== 21 || + (prefix !== network.pubKeyHash && prefix !== network.scriptHash)) + throw new TypeError(address + 'is not valid'); + // Check if blind key has valid length + if (blindingKey.length < 33) + throw new TypeError('Blinding key is too short'); + if (blindingKey.length > 33) + throw new TypeError('Blinding key is too long'); + const prefixBuf = Buffer.alloc(2); + prefixBuf[0] = network.confidentialPrefix; + prefixBuf[1] = prefix; + const confidentialAddress = Buffer.concat([ + prefixBuf, + blindingKey, + Buffer.from(payload.slice(1)), + ]); + return bs58check_1.default.encode(confidentialAddress); } function toConfidentialSegwit(address, blindingKey, network) { - const data = toOutputScript(address, network); - return toBlech32(data, blindingKey, network.blech32); + const data = toOutputScript(address, network); + return toBlech32(data, blindingKey, network.blech32); } function isBlech32(address, network) { - return address.startsWith(network.blech32); + return address.startsWith(network.blech32); } function decodeBlech32(address) { - const blech32addr = fromBlech32(address); - switch (blech32addr.data.length) { - case 20: - return AddressType.ConfidentialP2Wpkh; - case 32: - return AddressType.ConfidentialP2Wsh; - default: - throw new Error('invalid program length'); - } + const blech32addr = fromBlech32(address); + switch (blech32addr.data.length) { + case 20: + return AddressType.ConfidentialP2Wpkh; + case 32: + return AddressType.ConfidentialP2Wsh; + default: + throw new Error('invalid program length'); + } } function isBech32(address, network) { - return address.startsWith(network.bech32); + return address.startsWith(network.bech32); } function decodeBech32(address) { - const bech32addr = fromBech32(address); - switch (bech32addr.data.length) { - case 20: - return AddressType.P2Wpkh; - case 32: - return AddressType.P2Wsh; - default: - throw new Error('invalid program length'); - } + const bech32addr = fromBech32(address); + switch (bech32addr.data.length) { + case 20: + return AddressType.P2Wpkh; + case 32: + return AddressType.P2Wsh; + default: + throw new Error('invalid program length'); + } } function UnkownPrefixError(prefix, network) { - return new Error( - `unknown address prefix (${prefix}), need ${network.pubKeyHash} or ${ - network.scriptHash - }`, - ); + return new Error(`unknown address prefix (${prefix}), need ${network.pubKeyHash} or ${network.scriptHash}`); } function decodeBase58(address, network) { - const payload = bs58check_1.default.decode(address); - // Blinded decoded haddress has the form: - // BLIND_PREFIX|ADDRESS_PREFIX|BLINDING_KEY|SCRIPT_HASH - // Prefixes are 1 byte long, thus blinding key always starts at 3rd byte - const prefix = payload.readUInt8(1); - if (payload.readUInt8(0) === network.confidentialPrefix) { - const unconfidentialPart = payload.slice(35); // ignore the blinding key - if (unconfidentialPart.length !== 20) { - // ripem160 hash size - throw new Error('decoded address is of unknown size'); + const payload = bs58check_1.default.decode(address); + // Blinded decoded haddress has the form: + // BLIND_PREFIX|ADDRESS_PREFIX|BLINDING_KEY|SCRIPT_HASH + // Prefixes are 1 byte long, thus blinding key always starts at 3rd byte + const prefix = payload.readUInt8(1); + if (payload.readUInt8(0) === network.confidentialPrefix) { + const unconfidentialPart = payload.slice(35); // ignore the blinding key + if (unconfidentialPart.length !== 20) { + // ripem160 hash size + throw new Error('decoded address is of unknown size'); + } + switch (prefix) { + case network.pubKeyHash: + return AddressType.ConfidentialP2Pkh; + case network.scriptHash: + return AddressType.ConfidentialP2Sh; + default: + throw UnkownPrefixError(prefix, network); + } + } + // unconf case + const unconfidential = payload.slice(2); + if (unconfidential.length !== 20) { + // ripem160 hash size + throw new Error('decoded address is of unknown size'); } switch (prefix) { - case network.pubKeyHash: - return AddressType.ConfidentialP2Pkh; - case network.scriptHash: - return AddressType.ConfidentialP2Sh; - default: - throw UnkownPrefixError(prefix, network); + case network.pubKeyHash: + return AddressType.P2Pkh; + case network.scriptHash: + return AddressType.P2Sh; + default: + throw UnkownPrefixError(prefix, network); } - } - // unconf case - const unconfidential = payload.slice(2); - if (unconfidential.length !== 20) { - // ripem160 hash size - throw new Error('decoded address is of unknown size'); - } - switch (prefix) { - case network.pubKeyHash: - return AddressType.P2Pkh; - case network.scriptHash: - return AddressType.P2Sh; - default: - throw UnkownPrefixError(prefix, network); - } } function decodeType(address, network) { - network = network || getNetwork(address); - if (isBech32(address, network)) { - return decodeBech32(address); - } - if (isBlech32(address, network)) { - return decodeBlech32(address); - } - return decodeBase58(address, network); + network = network || getNetwork(address); + if (isBech32(address, network)) { + return decodeBech32(address); + } + if (isBlech32(address, network)) { + return decodeBlech32(address); + } + return decodeBase58(address, network); } exports.decodeType = decodeType; /** @@ -339,7 +327,7 @@ exports.decodeType = decodeType; * @param address address to check. */ function isConfidential(address) { - const type = decodeType(address); - return isConfidentialAddressType(type); + const type = decodeType(address); + return isConfidentialAddressType(type); } exports.isConfidential = isConfidential; diff --git a/src/block.js b/src/block.js index 3a2e3c5c8..d2fbe53d9 100644 --- a/src/block.js +++ b/src/block.js @@ -1,258 +1,364 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bufferutils_1 = require('./bufferutils'); -const bcrypto = __importStar(require('./crypto')); -const transaction_1 = require('./transaction'); -const types = __importStar(require('./types')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bufferutils_1 = require("./bufferutils"); +const bcrypto = __importStar(require("./crypto")); +const transaction_1 = require("./transaction"); +const types = __importStar(require("./types")); const fastMerkleRoot = require('merkle-lib/fastRoot'); const typeforce = require('typeforce'); const varuint = require('varuint-bitcoin'); -const errorMerkleNoTxes = new TypeError( - 'Cannot compute merkle root for zero transactions', -); -const errorWitnessNotSegwit = new TypeError( - 'Cannot compute witness commit for non-segwit block', -); +const errorMerkleNoTxes = new TypeError('Cannot compute merkle root for zero transactions'); +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)'); - let offset = 0; - const readSlice = n => { - offset += n; - return buffer.slice(offset - n, offset); - }; - const readUInt32 = () => { - const i = buffer.readUInt32LE(offset); - offset += 4; - return i; - }; - const readInt32 = () => { - const i = buffer.readInt32LE(offset); - offset += 4; - return i; - }; - const block = new Block(); - block.version = readInt32(); - block.prevHash = readSlice(32); - block.merkleRoot = readSlice(32); - block.timestamp = readUInt32(); - block.bits = readUInt32(); - block.nonce = readUInt32(); - if (buffer.length === 80) return block; - const readVarInt = () => { - const vi = varuint.decode(buffer, offset); - offset += varuint.decode.bytes; - return vi; - }; - const readTransaction = () => { - const tx = transaction_1.Transaction.fromBuffer( - buffer.slice(offset), - true, - ); - offset += tx.byteLength(); - return tx; - }; - const nTransactions = readVarInt(); - block.transactions = []; - for (let i = 0; i < nTransactions; ++i) { - const tx = readTransaction(); - block.transactions.push(tx); + 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; + this.blockHeight = 0; + // DYNAMIC FEDERATION PARAMS + // current compact params + this.currentSignBlockScript = undefined; + this.currentSignBlockWitnessLimit = 0; + this.currentElidedRoot = undefined; + // current full param + this.currentSignBlockScriptFull = undefined; + this.currentSignBlockWitnessLimitFull = 0; + this.currentFedpegProgram = undefined; + this.currentFedpegScript = undefined; + this.currentExtensionSpace = undefined; + // proposed compact params + this.proposedSignBlockScript = undefined; + this.proposedSignBlockWitnessLimit = 0; + this.proposedElidedRoot = undefined; + // proposed full param + this.proposedSignBlockScriptFull = undefined; + this.proposedSignBlockWitnessLimitFull = 0; + this.proposedFedpegProgram = undefined; + this.proposedFedpegScript = undefined; + this.proposedExtensionSpace = undefined; + // SignBlockWitness + this.signBlockWitness = undefined; + this.challenge = undefined; + this.solution = undefined; + } + static fromBuffer(buffer) { + if (buffer.length < 80) + throw new Error('Buffer too small (< 80 bytes)'); + let offset = 0; + const readSlice = (n) => { + offset += n; + return buffer.slice(offset - n, offset); + }; + const readUInt32 = () => { + const i = buffer.readUInt32LE(offset); + offset += 4; + return i; + }; + const readUInt8 = () => { + const i = buffer.readUInt8(offset); + offset += 1; + return i; + }; + // const readInt32 = (): number => { + // const i = buffer.readInt32LE(offset); + // offset += 4; + // return i; + // }; + const readVarInt = () => { + const vi = varuint.decode(buffer, offset); + offset += varuint.decode.bytes; + return vi; + }; + const block = new Block(); + block.version = readUInt32(); + const isDyna = block.version >>> 31 === 1; + if (isDyna) { + block.version &= 2147483647; + } + block.prevHash = readSlice(32); + block.merkleRoot = readSlice(32); + block.timestamp = readUInt32(); + block.blockHeight = readUInt32(); + block.bits = readUInt32(); // remove + block.nonce = readUInt32(); // remove + if (isDyna) { + // current params + let serializeType = readUInt8(); + switch (serializeType) { + case 0: // null + break; + case 1: // compact params + const signBlockScriptLengthCompact = readVarInt(); + const signBlockScriptCompact = readSlice(signBlockScriptLengthCompact); + const signBlockWitnessLimitCompact = readUInt8(); + const elidedRootCompact = readSlice(32); + block.currentSignBlockScript = signBlockScriptCompact; + block.currentSignBlockWitnessLimit = signBlockWitnessLimitCompact; + block.currentElidedRoot = elidedRootCompact; + break; + case 2: // full params + const signBlockScriptLengthFull = readVarInt(); + const signBlockScriptFull = readSlice(signBlockScriptLengthFull); + const signBlockWitnessLimitFull = readUInt32(); + const fedpegProgramLength = readVarInt(); + const fedpegProgram = readSlice(fedpegProgramLength); + const fedpegScriptLength = readVarInt(); + const fedpegScript = readSlice(fedpegScriptLength); + const extensionSpaceLength = readVarInt(); + const extensionSpace = []; + for (let i = 0; i < extensionSpaceLength; i++) { + const tmpLen = readVarInt(); + const tmp = readSlice(tmpLen); + extensionSpace.unshift(tmp); + } + block.currentSignBlockScriptFull = signBlockScriptFull; + block.currentSignBlockWitnessLimitFull = signBlockWitnessLimitFull; + block.currentFedpegProgram = fedpegProgram; + block.currentFedpegScript = fedpegScript; + block.currentExtensionSpace = extensionSpace; + break; + default: + throw new Error('bad serialize type for dynafed parameters'); + } + // proposed params + serializeType = readUInt8(); + switch (serializeType) { + case 0: // null + break; + case 1: // compact params + const signBlockScriptLengthCompact = readVarInt(); + const signBlockScriptCompact = readSlice(signBlockScriptLengthCompact); + const signBlockWitnessLimitCompact = readUInt8(); + const elidedRootCompact = readSlice(32); + block.proposedSignBlockScript = signBlockScriptCompact; + block.proposedSignBlockWitnessLimit = signBlockWitnessLimitCompact; + block.proposedElidedRoot = elidedRootCompact; + break; + case 2: // full params + const signBlockScriptLengthFull = readVarInt(); + const signBlockScriptFull = readSlice(signBlockScriptLengthFull); + const signBlockWitnessLimitFull = readUInt32(); + const fedpegProgramLength = readVarInt(); + const fedpegProgram = readSlice(fedpegProgramLength); + const fedpegScriptLength = readVarInt(); + const fedpegScript = readSlice(fedpegScriptLength); + const extensionSpaceLength = readVarInt(); + const extensionSpace = []; + for (let i = 0; i < extensionSpaceLength; i++) { + const tmpLen = readVarInt(); + const tmp = readSlice(tmpLen); + extensionSpace.unshift(tmp); + } + block.proposedSignBlockScriptFull = signBlockScriptFull; + block.proposedSignBlockWitnessLimitFull = signBlockWitnessLimitFull; + block.proposedFedpegProgram = fedpegProgram; + block.proposedFedpegScript = fedpegScript; + block.proposedExtensionSpace = extensionSpace; + break; + default: + throw new Error('bad serialize type for dynafed parameters'); + } + const signBlockWitnessLength = readVarInt(); + const signBlockWitness = []; + for (let i = 0; i < signBlockWitnessLength; i++) { + const tmpLen = readVarInt(); + const tmp = readSlice(tmpLen); + signBlockWitness.unshift(tmp); + } + block.signBlockWitness = signBlockWitness; + } + else { + const challengeLength = readVarInt(); + const challenge = readSlice(challengeLength); + const solutionLength = readVarInt(); + const solution = readSlice(solutionLength); + block.challenge = challenge; + block.solution = solution; + } + if (buffer.length === 80) + return block; + const readTransaction = () => { + const tx = transaction_1.Transaction.fromBuffer(buffer.slice(offset), true); + offset += tx.byteLength(); + return tx; + }; + const nTransactions = readVarInt(); + block.transactions = []; + for (let i = 0; i < nTransactions; ++i) { + const tx = readTransaction(); + block.transactions.push(tx); + } + const witnessCommit = block.getWitnessCommit(); + // This Block contains a witness commit + if (witnessCommit) + block.witnessCommit = witnessCommit; + return block; + } + static fromHex(hex) { + return Block.fromBuffer(Buffer.from(hex, 'hex')); + } + static calculateTarget(bits) { + const exponent = ((bits & 0xff000000) >> 24) - 3; + const mantissa = bits & 0x007fffff; + const target = Buffer.alloc(32, 0); + target.writeUIntBE(mantissa, 29 - exponent, 3); + return target; + } + static calculateMerkleRoot(transactions, forWitness) { + typeforce([{ getHash: types.Function }], transactions); + if (transactions.length === 0) + throw errorMerkleNoTxes; + if (forWitness && !txesHaveWitnessCommit(transactions)) + throw errorWitnessNotSegwit; + const hashes = transactions.map(transaction => transaction.getHash(forWitness)); + const rootHash = fastMerkleRoot(hashes, bcrypto.hash256); + return forWitness + ? bcrypto.hash256(Buffer.concat([rootHash, transactions[0].ins[0].witness[0]])) + : rootHash; + } + getWitnessCommit() { + if (!txesHaveWitnessCommit(this.transactions)) + return null; + // The merkle root for the witness data is in an OP_RETURN output. + // There is no rule for the index of the output, so use filter to find it. + // The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed + // If multiple commits are found, the output with highest index is assumed. + const witnessCommits = this.transactions[0].outs.filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex'))).map(out => out.script.slice(6, 38)); + if (witnessCommits.length === 0) + return null; + // Use the commit with the highest output (should only be one though) + const result = witnessCommits[witnessCommits.length - 1]; + if (!(result instanceof Buffer && result.length === 32)) + return null; + return result; + } + hasWitnessCommit() { + if (this.witnessCommit instanceof Buffer && + this.witnessCommit.length === 32) + return true; + if (this.getWitnessCommit() !== null) + return true; + return false; + } + hasWitness() { + return anyTxHasWitness(this.transactions); + } + weight() { + const base = this.byteLength(false, false); + const total = this.byteLength(false, true); + return base * 3 + total; + } + byteLength(headersOnly, allowWitness = true) { + if (headersOnly || !this.transactions) + return 80; + return (80 + + varuint.encodingLength(this.transactions.length) + + this.transactions.reduce((a, x) => a + x.byteLength(allowWitness), 0)); + } + getHash() { + return bcrypto.hash256(this.toBuffer(true)); + } + getId() { + return bufferutils_1.reverseBuffer(this.getHash()).toString('hex'); + } + getUTCDate() { + const date = new Date(0); // epoch + date.setUTCSeconds(this.timestamp); + return date; + } + // TODO: buffer, offset compatibility + toBuffer(headersOnly) { + const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)); + let offset = 0; + const writeSlice = (slice) => { + slice.copy(buffer, offset); + offset += slice.length; + }; + const writeInt32 = (i) => { + buffer.writeInt32LE(i, offset); + offset += 4; + }; + const writeUInt32 = (i) => { + buffer.writeUInt32LE(i, offset); + offset += 4; + }; + writeInt32(this.version); + writeSlice(this.prevHash); + writeSlice(this.merkleRoot); + writeUInt32(this.timestamp); + writeUInt32(this.bits); + writeUInt32(this.nonce); + if (headersOnly || !this.transactions) + return buffer; + varuint.encode(this.transactions.length, buffer, offset); + offset += varuint.encode.bytes; + this.transactions.forEach(tx => { + const txSize = tx.byteLength(); // TODO: extract from toBuffer? + tx.toBuffer(buffer, offset); + offset += txSize; + }); + return buffer; + } + toHex(headersOnly) { + return this.toBuffer(headersOnly).toString('hex'); + } + checkTxRoots() { + // If the Block has segwit transactions but no witness commit, + // there's no way it can be valid, so fail the check. + const hasWitnessCommit = this.hasWitnessCommit(); + if (!hasWitnessCommit && this.hasWitness()) + return false; + return (this.__checkMerkleRoot() && + (hasWitnessCommit ? this.__checkWitnessCommit() : true)); + } + checkProofOfWork() { + const hash = bufferutils_1.reverseBuffer(this.getHash()); + const target = Block.calculateTarget(this.bits); + return hash.compare(target) <= 0; + } + __checkMerkleRoot() { + if (!this.transactions) + throw errorMerkleNoTxes; + const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions); + return this.merkleRoot.compare(actualMerkleRoot) === 0; + } + __checkWitnessCommit() { + if (!this.transactions) + throw errorMerkleNoTxes; + if (!this.hasWitnessCommit()) + throw errorWitnessNotSegwit; + const actualWitnessCommit = Block.calculateMerkleRoot(this.transactions, true); + return this.witnessCommit.compare(actualWitnessCommit) === 0; } - const witnessCommit = block.getWitnessCommit(); - // This Block contains a witness commit - if (witnessCommit) block.witnessCommit = witnessCommit; - return block; - } - static fromHex(hex) { - return Block.fromBuffer(Buffer.from(hex, 'hex')); - } - static calculateTarget(bits) { - const exponent = ((bits & 0xff000000) >> 24) - 3; - const mantissa = bits & 0x007fffff; - const target = Buffer.alloc(32, 0); - target.writeUIntBE(mantissa, 29 - exponent, 3); - return target; - } - static calculateMerkleRoot(transactions, forWitness) { - typeforce([{ getHash: types.Function }], transactions); - if (transactions.length === 0) throw errorMerkleNoTxes; - if (forWitness && !txesHaveWitnessCommit(transactions)) - throw errorWitnessNotSegwit; - const hashes = transactions.map(transaction => - transaction.getHash(forWitness), - ); - const rootHash = fastMerkleRoot(hashes, bcrypto.hash256); - return forWitness - ? bcrypto.hash256( - Buffer.concat([rootHash, transactions[0].ins[0].witness[0]]), - ) - : rootHash; - } - getWitnessCommit() { - if (!txesHaveWitnessCommit(this.transactions)) return null; - // The merkle root for the witness data is in an OP_RETURN output. - // There is no rule for the index of the output, so use filter to find it. - // The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed - // If multiple commits are found, the output with highest index is assumed. - const witnessCommits = this.transactions[0].outs - .filter(out => - out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex')), - ) - .map(out => out.script.slice(6, 38)); - if (witnessCommits.length === 0) return null; - // Use the commit with the highest output (should only be one though) - const result = witnessCommits[witnessCommits.length - 1]; - if (!(result instanceof Buffer && result.length === 32)) return null; - return result; - } - hasWitnessCommit() { - if ( - this.witnessCommit instanceof Buffer && - this.witnessCommit.length === 32 - ) - return true; - if (this.getWitnessCommit() !== null) return true; - return false; - } - hasWitness() { - return anyTxHasWitness(this.transactions); - } - weight() { - const base = this.byteLength(false, false); - const total = this.byteLength(false, true); - return base * 3 + total; - } - byteLength(headersOnly, allowWitness = true) { - if (headersOnly || !this.transactions) return 80; - return ( - 80 + - varuint.encodingLength(this.transactions.length) + - this.transactions.reduce((a, x) => a + x.byteLength(allowWitness), 0) - ); - } - getHash() { - return bcrypto.hash256(this.toBuffer(true)); - } - getId() { - return bufferutils_1.reverseBuffer(this.getHash()).toString('hex'); - } - getUTCDate() { - const date = new Date(0); // epoch - date.setUTCSeconds(this.timestamp); - return date; - } - // TODO: buffer, offset compatibility - toBuffer(headersOnly) { - const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)); - let offset = 0; - const writeSlice = slice => { - slice.copy(buffer, offset); - offset += slice.length; - }; - const writeInt32 = i => { - buffer.writeInt32LE(i, offset); - offset += 4; - }; - const writeUInt32 = i => { - buffer.writeUInt32LE(i, offset); - offset += 4; - }; - writeInt32(this.version); - writeSlice(this.prevHash); - writeSlice(this.merkleRoot); - writeUInt32(this.timestamp); - writeUInt32(this.bits); - writeUInt32(this.nonce); - if (headersOnly || !this.transactions) return buffer; - varuint.encode(this.transactions.length, buffer, offset); - offset += varuint.encode.bytes; - this.transactions.forEach(tx => { - const txSize = tx.byteLength(); // TODO: extract from toBuffer? - tx.toBuffer(buffer, offset); - offset += txSize; - }); - return buffer; - } - toHex(headersOnly) { - return this.toBuffer(headersOnly).toString('hex'); - } - checkTxRoots() { - // If the Block has segwit transactions but no witness commit, - // there's no way it can be valid, so fail the check. - const hasWitnessCommit = this.hasWitnessCommit(); - if (!hasWitnessCommit && this.hasWitness()) return false; - return ( - this.__checkMerkleRoot() && - (hasWitnessCommit ? this.__checkWitnessCommit() : true) - ); - } - checkProofOfWork() { - const hash = bufferutils_1.reverseBuffer(this.getHash()); - const target = Block.calculateTarget(this.bits); - return hash.compare(target) <= 0; - } - __checkMerkleRoot() { - if (!this.transactions) throw errorMerkleNoTxes; - const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions); - return this.merkleRoot.compare(actualMerkleRoot) === 0; - } - __checkWitnessCommit() { - if (!this.transactions) throw errorMerkleNoTxes; - if (!this.hasWitnessCommit()) throw errorWitnessNotSegwit; - const actualWitnessCommit = Block.calculateMerkleRoot( - this.transactions, - true, - ); - return this.witnessCommit.compare(actualWitnessCommit) === 0; - } } exports.Block = Block; function txesHaveWitnessCommit(transactions) { - return ( - transactions instanceof Array && - transactions[0] && - transactions[0].ins && - transactions[0].ins instanceof Array && - transactions[0].ins[0] && - transactions[0].ins[0].witness && - transactions[0].ins[0].witness instanceof Array && - transactions[0].ins[0].witness.length > 0 - ); + return (transactions instanceof Array && + transactions[0] && + transactions[0].ins && + transactions[0].ins instanceof Array && + transactions[0].ins[0] && + transactions[0].ins[0].witness && + transactions[0].ins[0].witness instanceof Array && + transactions[0].ins[0].witness.length > 0); } function anyTxHasWitness(transactions) { - return ( - transactions instanceof Array && - transactions.some( - tx => - typeof tx === 'object' && - tx.ins instanceof Array && - tx.ins.some( - input => - typeof input === 'object' && - input.witness instanceof Array && - input.witness.length > 0, - ), - ) - ); + return (transactions instanceof Array && + transactions.some(tx => typeof tx === 'object' && + tx.ins instanceof Array && + tx.ins.some(input => typeof input === 'object' && + input.witness instanceof Array && + input.witness.length > 0))); } diff --git a/src/bufferutils.js b/src/bufferutils.js index 8ce70f504..3493f4b6a 100644 --- a/src/bufferutils.js +++ b/src/bufferutils.js @@ -1,237 +1,236 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const types = __importStar(require('./types')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const types = __importStar(require("./types")); const typeforce = require('typeforce'); const varuint = require('varuint-bitcoin'); const CONFIDENTIAL_COMMITMENT = 33; // default size of confidential commitments (i.e. asset, value, nonce) const CONFIDENTIAL_VALUE = 9; // explicit size of confidential values // https://github.com/feross/buffer/blob/master/index.js#L1127 function verifuint(value, max) { - if (typeof value !== 'number') - throw new Error('cannot write a non-number as a number'); - if (value < 0) - throw new Error('specified a negative value for writing an unsigned value'); - if (value > max) throw new Error('RangeError: value out of range'); - if (Math.floor(value) !== value) - throw new Error('value has a fractional component'); + if (typeof value !== 'number') + throw new Error('cannot write a non-number as a number'); + if (value < 0) + throw new Error('specified a negative value for writing an unsigned value'); + if (value > max) + throw new Error('RangeError: value out of range'); + if (Math.floor(value) !== value) + throw new Error('value has a fractional component'); } function readUInt64LE(buffer, offset) { - const a = buffer.readUInt32LE(offset); - let b = buffer.readUInt32LE(offset + 4); - b *= 0x100000000; - verifuint(b + a, 0x001fffffffffffff); - return b + a; + const a = buffer.readUInt32LE(offset); + let b = buffer.readUInt32LE(offset + 4); + b *= 0x100000000; + verifuint(b + a, 0x001fffffffffffff); + return b + a; } exports.readUInt64LE = readUInt64LE; function writeUInt64LE(buffer, value, offset) { - verifuint(value, 0x001fffffffffffff); - buffer.writeInt32LE(value & -1, offset); - buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4); - return offset + 8; + verifuint(value, 0x001fffffffffffff); + buffer.writeInt32LE(value & -1, offset); + buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4); + return offset + 8; } exports.writeUInt64LE = writeUInt64LE; function reverseBuffer(buffer) { - if (buffer.length < 1) return buffer; - let j = buffer.length - 1; - let tmp = 0; - for (let i = 0; i < buffer.length / 2; i++) { - tmp = buffer[i]; - buffer[i] = buffer[j]; - buffer[j] = tmp; - j--; - } - return buffer; + if (buffer.length < 1) + return buffer; + let j = buffer.length - 1; + let tmp = 0; + for (let i = 0; i < buffer.length / 2; i++) { + tmp = buffer[i]; + buffer[i] = buffer[j]; + buffer[j] = tmp; + j--; + } + return buffer; } exports.reverseBuffer = reverseBuffer; /** * Helper class for serialization of bitcoin data types into a pre-allocated buffer. */ class BufferWriter { - constructor(buffer, offset = 0) { - this.buffer = buffer; - this.offset = offset; - typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); - } - writeUInt8(i) { - this.offset = this.buffer.writeUInt8(i, this.offset); - } - writeInt32(i) { - this.offset = this.buffer.writeInt32LE(i, this.offset); - } - writeUInt32(i) { - this.offset = this.buffer.writeUInt32LE(i, this.offset); - } - writeUInt64(i) { - this.offset = writeUInt64LE(this.buffer, i, this.offset); - } - writeVarInt(i) { - varuint.encode(i, this.buffer, this.offset); - this.offset += varuint.encode.bytes; - } - writeSlice(slice) { - if (this.buffer.length < this.offset + slice.length) { - throw new Error('Cannot write slice out of bounds'); - } - this.offset += slice.copy(this.buffer, this.offset); - } - writeVarSlice(slice) { - this.writeVarInt(slice.length); - this.writeSlice(slice); - } - writeVector(vector) { - this.writeVarInt(vector.length); - vector.forEach(buf => this.writeVarSlice(buf)); - } - writeConfidentialInFields(input) { - this.writeVarSlice(input.issuanceRangeProof); - this.writeVarSlice(input.inflationRangeProof); - this.writeVector(input.witness); - this.writeVector(input.peginWitness); - } - writeConfidentialOutFields(output) { - this.writeVarSlice(output.surjectionProof); - this.writeVarSlice(output.rangeProof); - } + constructor(buffer, offset = 0) { + this.buffer = buffer; + this.offset = offset; + typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); + } + writeUInt8(i) { + this.offset = this.buffer.writeUInt8(i, this.offset); + } + writeInt32(i) { + this.offset = this.buffer.writeInt32LE(i, this.offset); + } + writeUInt32(i) { + this.offset = this.buffer.writeUInt32LE(i, this.offset); + } + writeUInt64(i) { + this.offset = writeUInt64LE(this.buffer, i, this.offset); + } + writeVarInt(i) { + varuint.encode(i, this.buffer, this.offset); + this.offset += varuint.encode.bytes; + } + writeSlice(slice) { + if (this.buffer.length < this.offset + slice.length) { + throw new Error('Cannot write slice out of bounds'); + } + this.offset += slice.copy(this.buffer, this.offset); + } + writeVarSlice(slice) { + this.writeVarInt(slice.length); + this.writeSlice(slice); + } + writeVector(vector) { + this.writeVarInt(vector.length); + vector.forEach((buf) => this.writeVarSlice(buf)); + } + writeConfidentialInFields(input) { + this.writeVarSlice(input.issuanceRangeProof); + this.writeVarSlice(input.inflationRangeProof); + this.writeVector(input.witness); + this.writeVector(input.peginWitness); + } + writeConfidentialOutFields(output) { + this.writeVarSlice(output.surjectionProof); + this.writeVarSlice(output.rangeProof); + } } exports.BufferWriter = BufferWriter; /** * Helper class for reading of bitcoin data types from a buffer. */ class BufferReader { - constructor(buffer, offset = 0) { - this.buffer = buffer; - this.offset = offset; - typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); - } - readUInt8() { - const result = this.buffer.readUInt8(this.offset); - this.offset++; - return result; - } - readInt32() { - const result = this.buffer.readInt32LE(this.offset); - this.offset += 4; - return result; - } - readUInt32() { - const result = this.buffer.readUInt32LE(this.offset); - this.offset += 4; - return result; - } - readUInt64() { - const result = readUInt64LE(this.buffer, this.offset); - this.offset += 8; - return result; - } - readVarInt() { - const vi = varuint.decode(this.buffer, this.offset); - this.offset += varuint.decode.bytes; - return vi; - } - readSlice(n) { - if (this.buffer.length < this.offset + n) { - throw new Error('Cannot read slice out of bounds'); - } - const result = this.buffer.slice(this.offset, this.offset + n); - this.offset += n; - return result; - } - readVarSlice() { - return this.readSlice(this.readVarInt()); - } - readVector() { - const count = this.readVarInt(); - const vector = []; - for (let i = 0; i < count; i++) vector.push(this.readVarSlice()); - return vector; - } - // CConfidentialAsset size 33, prefixA 10, prefixB 11 - readConfidentialAsset() { - const version = this.readUInt8(); - const versionBuffer = this.buffer.slice(this.offset - 1, this.offset); - if (version === 1 || version === 0xff) - return Buffer.concat([ - versionBuffer, - this.readSlice(CONFIDENTIAL_COMMITMENT - 1), - ]); - else if (version === 10 || version === 11) - return Buffer.concat([ - versionBuffer, - this.readSlice(CONFIDENTIAL_COMMITMENT - 1), - ]); - return versionBuffer; - } - // CConfidentialNonce size 33, prefixA 2, prefixB 3 - readConfidentialNonce() { - const version = this.readUInt8(); - const versionBuffer = this.buffer.slice(this.offset - 1, this.offset); - if (version === 1 || version === 0xff) - return Buffer.concat([ - versionBuffer, - this.readSlice(CONFIDENTIAL_COMMITMENT - 1), - ]); - else if (version === 2 || version === 3) - return Buffer.concat([ - versionBuffer, - this.readSlice(CONFIDENTIAL_COMMITMENT - 1), - ]); - return versionBuffer; - } - // CConfidentialValue size 9, prefixA 8, prefixB 9 - readConfidentialValue() { - const version = this.readUInt8(); - const versionBuffer = this.buffer.slice(this.offset - 1, this.offset); - if (version === 1 || version === 0xff) - return Buffer.concat([ - versionBuffer, - this.readSlice(CONFIDENTIAL_VALUE - 1), - ]); - else if (version === 8 || version === 9) - return Buffer.concat([ - versionBuffer, - this.readSlice(CONFIDENTIAL_COMMITMENT - 1), - ]); - return versionBuffer; - } - readConfidentialInFields() { - const issuanceRangeProof = this.readVarSlice(); - const inflationRangeProof = this.readVarSlice(); - const witness = this.readVector(); - const peginWitness = this.readVector(); - return { - issuanceRangeProof, - inflationRangeProof, - witness, - peginWitness, - }; - } - readConfidentialOutFields() { - const surjectionProof = this.readVarSlice(); - const rangeProof = this.readVarSlice(); - return { surjectionProof, rangeProof }; - } - readIssuance() { - const issuanceNonce = this.readSlice(32); - const issuanceEntropy = this.readSlice(32); - const amount = this.readConfidentialValue(); - const inflation = this.readConfidentialValue(); - return { - assetBlindingNonce: issuanceNonce, - assetEntropy: issuanceEntropy, - assetAmount: amount, - tokenAmount: inflation, - }; - } + constructor(buffer, offset = 0) { + this.buffer = buffer; + this.offset = offset; + typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); + } + readUInt8() { + const result = this.buffer.readUInt8(this.offset); + this.offset++; + return result; + } + readInt32() { + const result = this.buffer.readInt32LE(this.offset); + this.offset += 4; + return result; + } + readUInt32() { + const result = this.buffer.readUInt32LE(this.offset); + this.offset += 4; + return result; + } + readUInt64() { + const result = readUInt64LE(this.buffer, this.offset); + this.offset += 8; + return result; + } + readVarInt() { + const vi = varuint.decode(this.buffer, this.offset); + this.offset += varuint.decode.bytes; + return vi; + } + readSlice(n) { + if (this.buffer.length < this.offset + n) { + throw new Error('Cannot read slice out of bounds'); + } + const result = this.buffer.slice(this.offset, this.offset + n); + this.offset += n; + return result; + } + readVarSlice() { + return this.readSlice(this.readVarInt()); + } + readVector() { + const count = this.readVarInt(); + const vector = []; + for (let i = 0; i < count; i++) + vector.push(this.readVarSlice()); + return vector; + } + // CConfidentialAsset size 33, prefixA 10, prefixB 11 + readConfidentialAsset() { + const version = this.readUInt8(); + const versionBuffer = this.buffer.slice(this.offset - 1, this.offset); + if (version === 1 || version === 0xff) + return Buffer.concat([ + versionBuffer, + this.readSlice(CONFIDENTIAL_COMMITMENT - 1), + ]); + else if (version === 10 || version === 11) + return Buffer.concat([ + versionBuffer, + this.readSlice(CONFIDENTIAL_COMMITMENT - 1), + ]); + return versionBuffer; + } + // CConfidentialNonce size 33, prefixA 2, prefixB 3 + readConfidentialNonce() { + const version = this.readUInt8(); + const versionBuffer = this.buffer.slice(this.offset - 1, this.offset); + if (version === 1 || version === 0xff) + return Buffer.concat([ + versionBuffer, + this.readSlice(CONFIDENTIAL_COMMITMENT - 1), + ]); + else if (version === 2 || version === 3) + return Buffer.concat([ + versionBuffer, + this.readSlice(CONFIDENTIAL_COMMITMENT - 1), + ]); + return versionBuffer; + } + // CConfidentialValue size 9, prefixA 8, prefixB 9 + readConfidentialValue() { + const version = this.readUInt8(); + const versionBuffer = this.buffer.slice(this.offset - 1, this.offset); + if (version === 1 || version === 0xff) + return Buffer.concat([ + versionBuffer, + this.readSlice(CONFIDENTIAL_VALUE - 1), + ]); + else if (version === 8 || version === 9) + return Buffer.concat([ + versionBuffer, + this.readSlice(CONFIDENTIAL_COMMITMENT - 1), + ]); + return versionBuffer; + } + readConfidentialInFields() { + const issuanceRangeProof = this.readVarSlice(); + const inflationRangeProof = this.readVarSlice(); + const witness = this.readVector(); + const peginWitness = this.readVector(); + return { + issuanceRangeProof, + inflationRangeProof, + witness, + peginWitness, + }; + } + readConfidentialOutFields() { + const surjectionProof = this.readVarSlice(); + const rangeProof = this.readVarSlice(); + return { surjectionProof, rangeProof }; + } + readIssuance() { + const issuanceNonce = this.readSlice(32); + const issuanceEntropy = this.readSlice(32); + const amount = this.readConfidentialValue(); + const inflation = this.readConfidentialValue(); + return { + assetBlindingNonce: issuanceNonce, + assetEntropy: issuanceEntropy, + assetAmount: amount, + tokenAmount: inflation, + }; + } } exports.BufferReader = BufferReader; diff --git a/src/classify.js b/src/classify.js index 71e619aa2..25a827793 100644 --- a/src/classify.js +++ b/src/classify.js @@ -1,76 +1,82 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const script_1 = require('./script'); -const multisig = __importStar(require('./templates/multisig')); -const nullData = __importStar(require('./templates/nulldata')); -const pubKey = __importStar(require('./templates/pubkey')); -const pubKeyHash = __importStar(require('./templates/pubkeyhash')); -const scriptHash = __importStar(require('./templates/scripthash')); -const witnessCommitment = __importStar( - require('./templates/witnesscommitment'), -); -const witnessPubKeyHash = __importStar( - require('./templates/witnesspubkeyhash'), -); -const witnessScriptHash = __importStar( - require('./templates/witnessscripthash'), -); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const script_1 = require("./script"); +const multisig = __importStar(require("./templates/multisig")); +const nullData = __importStar(require("./templates/nulldata")); +const pubKey = __importStar(require("./templates/pubkey")); +const pubKeyHash = __importStar(require("./templates/pubkeyhash")); +const scriptHash = __importStar(require("./templates/scripthash")); +const witnessCommitment = __importStar(require("./templates/witnesscommitment")); +const witnessPubKeyHash = __importStar(require("./templates/witnesspubkeyhash")); +const witnessScriptHash = __importStar(require("./templates/witnessscripthash")); const types = { - P2MS: 'multisig', - NONSTANDARD: 'nonstandard', - NULLDATA: 'nulldata', - P2PK: 'pubkey', - P2PKH: 'pubkeyhash', - P2SH: 'scripthash', - P2WPKH: 'witnesspubkeyhash', - P2WSH: 'witnessscripthash', - WITNESS_COMMITMENT: 'witnesscommitment', + P2MS: 'multisig', + NONSTANDARD: 'nonstandard', + NULLDATA: 'nulldata', + P2PK: 'pubkey', + P2PKH: 'pubkeyhash', + P2SH: 'scripthash', + P2WPKH: 'witnesspubkeyhash', + P2WSH: 'witnessscripthash', + WITNESS_COMMITMENT: 'witnesscommitment', }; exports.types = types; function classifyOutput(script) { - if (witnessPubKeyHash.output.check(script)) return types.P2WPKH; - if (witnessScriptHash.output.check(script)) return types.P2WSH; - if (pubKeyHash.output.check(script)) return types.P2PKH; - if (scriptHash.output.check(script)) return types.P2SH; - // XXX: optimization, below functions .decompile before use - const chunks = script_1.decompile(script); - if (!chunks) throw new TypeError('Invalid script'); - if (multisig.output.check(chunks)) return types.P2MS; - if (pubKey.output.check(chunks)) return types.P2PK; - if (witnessCommitment.output.check(chunks)) return types.WITNESS_COMMITMENT; - if (nullData.output.check(chunks)) return types.NULLDATA; - return types.NONSTANDARD; + if (witnessPubKeyHash.output.check(script)) + return types.P2WPKH; + if (witnessScriptHash.output.check(script)) + return types.P2WSH; + if (pubKeyHash.output.check(script)) + return types.P2PKH; + if (scriptHash.output.check(script)) + return types.P2SH; + // XXX: optimization, below functions .decompile before use + const chunks = script_1.decompile(script); + if (!chunks) + throw new TypeError('Invalid script'); + if (multisig.output.check(chunks)) + return types.P2MS; + if (pubKey.output.check(chunks)) + return types.P2PK; + if (witnessCommitment.output.check(chunks)) + return types.WITNESS_COMMITMENT; + if (nullData.output.check(chunks)) + return types.NULLDATA; + return types.NONSTANDARD; } exports.output = classifyOutput; function classifyInput(script, allowIncomplete) { - // XXX: optimization, below functions .decompile before use - const chunks = script_1.decompile(script); - if (!chunks) throw new TypeError('Invalid script'); - if (pubKeyHash.input.check(chunks)) return types.P2PKH; - if (scriptHash.input.check(chunks, allowIncomplete)) return types.P2SH; - if (multisig.input.check(chunks, allowIncomplete)) return types.P2MS; - if (pubKey.input.check(chunks)) return types.P2PK; - return types.NONSTANDARD; + // XXX: optimization, below functions .decompile before use + const chunks = script_1.decompile(script); + if (!chunks) + throw new TypeError('Invalid script'); + if (pubKeyHash.input.check(chunks)) + return types.P2PKH; + if (scriptHash.input.check(chunks, allowIncomplete)) + return types.P2SH; + if (multisig.input.check(chunks, allowIncomplete)) + return types.P2MS; + if (pubKey.input.check(chunks)) + return types.P2PK; + return types.NONSTANDARD; } exports.input = classifyInput; function classifyWitness(script, allowIncomplete) { - // XXX: optimization, below functions .decompile before use - const chunks = script_1.decompile(script); - if (!chunks) throw new TypeError('Invalid script'); - if (witnessPubKeyHash.input.check(chunks)) return types.P2WPKH; - if (witnessScriptHash.input.check(chunks, allowIncomplete)) - return types.P2WSH; - return types.NONSTANDARD; + // XXX: optimization, below functions .decompile before use + const chunks = script_1.decompile(script); + if (!chunks) + throw new TypeError('Invalid script'); + if (witnessPubKeyHash.input.check(chunks)) + return types.P2WPKH; + if (witnessScriptHash.input.check(chunks, allowIncomplete)) + return types.P2WSH; + return types.NONSTANDARD; } exports.witness = classifyWitness; diff --git a/src/confidential.js b/src/confidential.js index fd67a5214..4be84051e 100644 --- a/src/confidential.js +++ b/src/confidential.js @@ -1,271 +1,154 @@ -'use strict'; -var __awaiter = - (this && this.__awaiter) || - function(thisArg, _arguments, P, generator) { - return new (P || (P = Promise))(function(resolve, reject) { - function fulfilled(value) { - try { - step(generator.next(value)); - } catch (e) { - reject(e); - } - } - function rejected(value) { - try { - step(generator['throw'](value)); - } catch (e) { - reject(e); - } - } - function step(result) { - result.done - ? resolve(result.value) - : new P(function(resolve) { - resolve(result.value); - }).then(fulfilled, rejected); - } - step((generator = generator.apply(thisArg, _arguments || [])).next()); +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); }); - }; -var __importStar = - (this && this.__importStar) || - function(mod) { +}; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -var __importDefault = - (this && this.__importDefault) || - function(mod) { - return mod && mod.__esModule ? mod : { default: mod }; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bufferutils = __importStar(require('./bufferutils')); -const crypto = __importStar(require('./crypto')); -const secp256k1_zkp_1 = __importDefault( - require('@vulpemventures/secp256k1-zkp'), -); +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bufferutils = __importStar(require("./bufferutils")); +const crypto = __importStar(require("./crypto")); +const secp256k1_zkp_1 = __importDefault(require("@vulpemventures/secp256k1-zkp")); const secp256k1Promise = secp256k1_zkp_1.default(); function nonceHash(pubkey, privkey) { - return __awaiter(this, void 0, void 0, function*() { - const { ecdh } = yield secp256k1Promise; - return crypto.sha256(ecdh(pubkey, privkey)); - }); + return __awaiter(this, void 0, void 0, function* () { + const { ecdh } = yield secp256k1Promise; + return crypto.sha256(ecdh(pubkey, privkey)); + }); } -function valueBlindingFactor( - inValues, - outValues, - inGenerators, - outGenerators, - inFactors, - outFactors, -) { - return __awaiter(this, void 0, void 0, function*() { - const { pedersen } = yield secp256k1Promise; - const values = inValues.concat(outValues); - const nInputs = inValues.length; - const generators = inGenerators.concat(outGenerators); - const factors = inFactors.concat(outFactors); - return pedersen.blindGeneratorBlindSum( - values, - nInputs, - generators, - factors, - ); - }); +function valueBlindingFactor(inValues, outValues, inGenerators, outGenerators, inFactors, outFactors) { + return __awaiter(this, void 0, void 0, function* () { + const { pedersen } = yield secp256k1Promise; + const values = inValues.concat(outValues); + const nInputs = inValues.length; + const generators = inGenerators.concat(outGenerators); + const factors = inFactors.concat(outFactors); + return pedersen.blindGeneratorBlindSum(values, nInputs, generators, factors); + }); } exports.valueBlindingFactor = valueBlindingFactor; function valueCommitment(value, gen, factor) { - return __awaiter(this, void 0, void 0, function*() { - const { generator, pedersen } = yield secp256k1Promise; - const generatorParsed = generator.parse(gen); - const commit = pedersen.commit(factor, value, generatorParsed); - return pedersen.commitSerialize(commit); - }); + return __awaiter(this, void 0, void 0, function* () { + const { generator, pedersen } = yield secp256k1Promise; + const generatorParsed = generator.parse(gen); + const commit = pedersen.commit(factor, value, generatorParsed); + return pedersen.commitSerialize(commit); + }); } exports.valueCommitment = valueCommitment; function assetCommitment(asset, factor) { - return __awaiter(this, void 0, void 0, function*() { - const { generator } = yield secp256k1Promise; - const gen = generator.generateBlinded(asset, factor); - return generator.serialize(gen); - }); + return __awaiter(this, void 0, void 0, function* () { + const { generator } = yield secp256k1Promise; + const gen = generator.generateBlinded(asset, factor); + return generator.serialize(gen); + }); } exports.assetCommitment = assetCommitment; function unblindOutputWithKey(out, blindingPrivKey) { - return __awaiter(this, void 0, void 0, function*() { - const nonce = yield nonceHash(out.nonce, blindingPrivKey); - return unblindOutputWithNonce(out, nonce); - }); + return __awaiter(this, void 0, void 0, function* () { + const nonce = yield nonceHash(out.nonce, blindingPrivKey); + return unblindOutputWithNonce(out, nonce); + }); } exports.unblindOutputWithKey = unblindOutputWithKey; function unblindOutputWithNonce(out, nonce) { - return __awaiter(this, void 0, void 0, function*() { - const secp = yield secp256k1Promise; - const gen = secp.generator.parse(out.asset); - const { value, blindFactor, message } = secp.rangeproof.rewind( - out.value, - out.rangeProof, - nonce, - gen, - out.script, - ); - return { - value, - asset: message.slice(0, 32), - valueBlindingFactor: blindFactor, - assetBlindingFactor: message.slice(32), - }; - }); + return __awaiter(this, void 0, void 0, function* () { + const secp = yield secp256k1Promise; + const gen = secp.generator.parse(out.asset); + const { value, blindFactor, message } = secp.rangeproof.rewind(out.value, out.rangeProof, nonce, gen, out.script); + return { + value, + asset: message.slice(0, 32), + valueBlindingFactor: blindFactor, + assetBlindingFactor: message.slice(32), + }; + }); } exports.unblindOutputWithNonce = unblindOutputWithNonce; function rangeProofInfo(proof) { - return __awaiter(this, void 0, void 0, function*() { - const { rangeproof } = yield secp256k1Promise; - const { exp, mantissa, minValue, maxValue } = rangeproof.info(proof); - return { - minValue: parseInt(minValue, 10), - maxValue: parseInt(maxValue, 10), - ctExp: exp, - ctBits: parseInt(mantissa, 10), - }; - }); + return __awaiter(this, void 0, void 0, function* () { + const { rangeproof } = yield secp256k1Promise; + const { exp, mantissa, minValue, maxValue } = rangeproof.info(proof); + return { + minValue: parseInt(minValue, 10), + maxValue: parseInt(maxValue, 10), + ctExp: exp, + ctBits: parseInt(mantissa, 10), + }; + }); } exports.rangeProofInfo = rangeProofInfo; /** * nonceHash from blinding key + ephemeral key and then rangeProof computation */ -function rangeProofWithNonceHash( - value, - blindingPubkey, - ephemeralPrivkey, - asset, - assetBlindingFactor, - valueBlindFactor, - valueCommit, - scriptPubkey, - minValue, - exp, - minBits, -) { - return __awaiter(this, void 0, void 0, function*() { - const nonce = yield nonceHash(blindingPubkey, ephemeralPrivkey); - return rangeProof( - value, - nonce, - asset, - assetBlindingFactor, - valueBlindFactor, - valueCommit, - scriptPubkey, - minValue, - exp, - minBits, - ); - }); +function rangeProofWithNonceHash(value, blindingPubkey, ephemeralPrivkey, asset, assetBlindingFactor, valueBlindFactor, valueCommit, scriptPubkey, minValue, exp, minBits) { + return __awaiter(this, void 0, void 0, function* () { + const nonce = yield nonceHash(blindingPubkey, ephemeralPrivkey); + return rangeProof(value, nonce, asset, assetBlindingFactor, valueBlindFactor, valueCommit, scriptPubkey, minValue, exp, minBits); + }); } exports.rangeProofWithNonceHash = rangeProofWithNonceHash; /** * rangeProof computation without nonceHash step. */ -function rangeProof( - value, - nonce, - asset, - assetBlindingFactor, - valueBlindFactor, - valueCommit, - scriptPubkey, - minValue, - exp, - minBits, -) { - return __awaiter(this, void 0, void 0, function*() { - const { generator, pedersen, rangeproof } = yield secp256k1Promise; - const gen = generator.generateBlinded(asset, assetBlindingFactor); - const message = Buffer.concat([asset, assetBlindingFactor]); - const commit = pedersen.commitParse(valueCommit); - const mv = minValue ? minValue : '1'; - const e = exp ? exp : 0; - const mb = minBits ? minBits : 36; - return rangeproof.sign( - commit, - valueBlindFactor, - nonce, - value, - gen, - mv, - e, - mb, - message, - scriptPubkey, - ); - }); +function rangeProof(value, nonce, asset, assetBlindingFactor, valueBlindFactor, valueCommit, scriptPubkey, minValue, exp, minBits) { + return __awaiter(this, void 0, void 0, function* () { + const { generator, pedersen, rangeproof } = yield secp256k1Promise; + const gen = generator.generateBlinded(asset, assetBlindingFactor); + const message = Buffer.concat([asset, assetBlindingFactor]); + const commit = pedersen.commitParse(valueCommit); + const mv = minValue ? minValue : '1'; + const e = exp ? exp : 0; + const mb = minBits ? minBits : 36; + return rangeproof.sign(commit, valueBlindFactor, nonce, value, gen, mv, e, mb, message, scriptPubkey); + }); } exports.rangeProof = rangeProof; -function surjectionProof( - outputAsset, - outputAssetBlindingFactor, - inputAssets, - inputAssetBlindingFactors, - seed, -) { - return __awaiter(this, void 0, void 0, function*() { - const { generator, surjectionproof } = yield secp256k1Promise; - const outputGenerator = generator.generateBlinded( - outputAsset, - outputAssetBlindingFactor, - ); - const inputGenerators = inputAssets.map((v, i) => - generator.generateBlinded(v, inputAssetBlindingFactors[i]), - ); - const nInputsToUse = inputAssets.length > 3 ? 3 : inputAssets.length; - const maxIterations = 100; - const init = surjectionproof.initialize( - inputAssets, - nInputsToUse, - outputAsset, - maxIterations, - seed, - ); - const proof = surjectionproof.generate( - init.proof, - inputGenerators, - outputGenerator, - init.inputIndex, - inputAssetBlindingFactors[init.inputIndex], - outputAssetBlindingFactor, - ); - return surjectionproof.serialize(proof); - }); +function surjectionProof(outputAsset, outputAssetBlindingFactor, inputAssets, inputAssetBlindingFactors, seed) { + return __awaiter(this, void 0, void 0, function* () { + const { generator, surjectionproof } = yield secp256k1Promise; + const outputGenerator = generator.generateBlinded(outputAsset, outputAssetBlindingFactor); + const inputGenerators = inputAssets.map((v, i) => generator.generateBlinded(v, inputAssetBlindingFactors[i])); + const nInputsToUse = inputAssets.length > 3 ? 3 : inputAssets.length; + const maxIterations = 100; + const init = surjectionproof.initialize(inputAssets, nInputsToUse, outputAsset, maxIterations, seed); + const proof = surjectionproof.generate(init.proof, inputGenerators, outputGenerator, init.inputIndex, inputAssetBlindingFactors[init.inputIndex], outputAssetBlindingFactor); + return surjectionproof.serialize(proof); + }); } exports.surjectionProof = surjectionProof; const CONFIDENTIAL_VALUE = 9; // explicit size of confidential values function confidentialValueToSatoshi(value) { - if (!isUnconfidentialValue(value)) { - throw new Error( - 'Value must be unconfidential, length or the prefix are not valid', - ); - } - const reverseValueBuffer = Buffer.allocUnsafe(CONFIDENTIAL_VALUE - 1); - value.slice(1, CONFIDENTIAL_VALUE).copy(reverseValueBuffer, 0); - bufferutils.reverseBuffer(reverseValueBuffer); - return bufferutils.readUInt64LE(reverseValueBuffer, 0); + if (!isUnconfidentialValue(value)) { + throw new Error('Value must be unconfidential, length or the prefix are not valid'); + } + const reverseValueBuffer = Buffer.allocUnsafe(CONFIDENTIAL_VALUE - 1); + value.slice(1, CONFIDENTIAL_VALUE).copy(reverseValueBuffer, 0); + bufferutils.reverseBuffer(reverseValueBuffer); + return bufferutils.readUInt64LE(reverseValueBuffer, 0); } exports.confidentialValueToSatoshi = confidentialValueToSatoshi; function satoshiToConfidentialValue(amount) { - const unconfPrefix = Buffer.allocUnsafe(1); - const valueBuffer = Buffer.allocUnsafe(CONFIDENTIAL_VALUE - 1); - unconfPrefix.writeUInt8(1, 0); - bufferutils.writeUInt64LE(valueBuffer, amount, 0); - return Buffer.concat([unconfPrefix, bufferutils.reverseBuffer(valueBuffer)]); + const unconfPrefix = Buffer.allocUnsafe(1); + const valueBuffer = Buffer.allocUnsafe(CONFIDENTIAL_VALUE - 1); + unconfPrefix.writeUInt8(1, 0); + bufferutils.writeUInt64LE(valueBuffer, amount, 0); + return Buffer.concat([unconfPrefix, bufferutils.reverseBuffer(valueBuffer)]); } exports.satoshiToConfidentialValue = satoshiToConfidentialValue; function isUnconfidentialValue(value) { - return value.length === CONFIDENTIAL_VALUE && value.readUInt8(0) === 1; + return value.length === CONFIDENTIAL_VALUE && value.readUInt8(0) === 1; } exports.isUnconfidentialValue = isUnconfidentialValue; diff --git a/src/crypto.js b/src/crypto.js index e7dd596bd..38ec4f9b1 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -1,35 +1,36 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); const createHash = require('create-hash'); function ripemd160(buffer) { - try { - return createHash('rmd160') - .update(buffer) - .digest(); - } catch (err) { - return createHash('ripemd160') - .update(buffer) - .digest(); - } + try { + return createHash('rmd160') + .update(buffer) + .digest(); + } + catch (err) { + return createHash('ripemd160') + .update(buffer) + .digest(); + } } exports.ripemd160 = ripemd160; function sha1(buffer) { - return createHash('sha1') - .update(buffer) - .digest(); + return createHash('sha1') + .update(buffer) + .digest(); } exports.sha1 = sha1; function sha256(buffer) { - return createHash('sha256') - .update(buffer) - .digest(); + return createHash('sha256') + .update(buffer) + .digest(); } exports.sha256 = sha256; function hash160(buffer) { - return ripemd160(sha256(buffer)); + return ripemd160(sha256(buffer)); } exports.hash160 = hash160; function hash256(buffer) { - return sha256(sha256(buffer)); + return sha256(sha256(buffer)); } exports.hash256 = hash256; diff --git a/src/ecpair.js b/src/ecpair.js index f96e8243d..19613c6b7 100644 --- a/src/ecpair.js +++ b/src/ecpair.js @@ -1,118 +1,123 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const NETWORKS = __importStar(require('./networks')); -const types = __importStar(require('./types')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const NETWORKS = __importStar(require("./networks")); +const types = __importStar(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({ +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.liquid; - 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; + 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.liquid; + 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); } - } - 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); + 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); + 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.liquid; - if (version !== network.wif) throw new Error('Invalid network version'); - } - return fromPrivateKey(decoded.privateKey, { - compressed: decoded.compressed, - network: 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.liquid; + 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); + 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/src/index.js b/src/index.js index 125764819..ec8c16b80 100644 --- a/src/index.js +++ b/src/index.js @@ -1,37 +1,33 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bip32 = __importStar(require('bip32')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bip32 = __importStar(require("bip32")); exports.bip32 = bip32; -const address = __importStar(require('./address')); +const address = __importStar(require("./address")); exports.address = address; -const confidential = __importStar(require('./confidential')); +const confidential = __importStar(require("./confidential")); exports.confidential = confidential; -const crypto = __importStar(require('./crypto')); +const crypto = __importStar(require("./crypto")); exports.crypto = crypto; -const ECPair = __importStar(require('./ecpair')); +const ECPair = __importStar(require("./ecpair")); exports.ECPair = ECPair; -const networks = __importStar(require('./networks')); +const networks = __importStar(require("./networks")); exports.networks = networks; -const payments = __importStar(require('./payments')); +const payments = __importStar(require("./payments")); exports.payments = payments; -const script = __importStar(require('./script')); +const script = __importStar(require("./script")); exports.script = script; -var block_1 = require('./block'); +var block_1 = require("./block"); exports.Block = block_1.Block; -var psbt_1 = require('./psbt'); +var psbt_1 = require("./psbt"); exports.Psbt = psbt_1.Psbt; -var script_1 = require('./script'); +var script_1 = require("./script"); exports.opcodes = script_1.OPS; -var transaction_1 = require('./transaction'); +var transaction_1 = require("./transaction"); exports.Transaction = transaction_1.Transaction; diff --git a/src/issuance.js b/src/issuance.js index 64ff7e670..cf739d1d0 100644 --- a/src/issuance.js +++ b/src/issuance.js @@ -1,28 +1,25 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const address_1 = require('./address'); -const bufferutils_1 = require('./bufferutils'); -const confidential_1 = require('./confidential'); -const bcrypto = __importStar(require('./crypto')); -const sha256d_1 = require('./sha256d'); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const address_1 = require("./address"); +const bufferutils_1 = require("./bufferutils"); +const confidential_1 = require("./confidential"); +const bcrypto = __importStar(require("./crypto")); +const sha256d_1 = require("./sha256d"); /** * returns true if the issuance's token amount is not 0x00 or null buffer. * @param issuance issuance to test */ function hasTokenAmount(issuance) { - if (issuance.tokenAmount && issuance.tokenAmount.length > 1) return true; - return false; + if (issuance.tokenAmount && issuance.tokenAmount.length > 1) + return true; + return false; } exports.hasTokenAmount = hasTokenAmount; /** @@ -30,8 +27,8 @@ exports.hasTokenAmount = hasTokenAmount; * @param contract contract to validate. */ function validateIssuanceContract(contract) { - const precisionIsValid = contract.precision >= 0 && contract.precision <= 8; - return precisionIsValid; + const precisionIsValid = contract.precision >= 0 && contract.precision <= 8; + return precisionIsValid; } exports.validateIssuanceContract = validateIssuanceContract; /** @@ -39,9 +36,9 @@ exports.validateIssuanceContract = validateIssuanceContract; * @param contract the contract to digest. */ function hashContract(contract) { - if (!validateIssuanceContract(contract)) - throw new Error('Invalid asset contract'); - return bcrypto.sha256(Buffer.from(JSON.stringify(contract))); + if (!validateIssuanceContract(contract)) + throw new Error('Invalid asset contract'); + return bcrypto.sha256(Buffer.from(JSON.stringify(contract))); } exports.hashContract = hashContract; /** @@ -52,23 +49,26 @@ exports.hashContract = hashContract; * @param contract the asset ricarding contract of the issuance. */ function newIssuance(assetAmount, tokenAmount, precision = 8, contract) { - if (assetAmount < 0) throw new Error('Invalid asset amount'); - if (tokenAmount < 0) throw new Error('Invalid token amount'); - if (precision < 0 || precision > 8) throw new Error('Invalid precision'); - let contractHash = Buffer.alloc(32); - if (contract) { - if (contract.precision !== precision) - throw new Error('precision is not equal to the asset contract precision'); - contractHash = hashContract(contract); - } - const iss = { - assetAmount: toConfidentialAssetAmount(assetAmount, precision), - tokenAmount: toConfidentialTokenAmount(tokenAmount, precision), - assetBlindingNonce: Buffer.alloc(32), - // in case of issuance, the asset entropy = the contract hash. - assetEntropy: contractHash, - }; - return iss; + if (assetAmount < 0) + throw new Error('Invalid asset amount'); + if (tokenAmount < 0) + throw new Error('Invalid token amount'); + if (precision < 0 || precision > 8) + throw new Error('Invalid precision'); + let contractHash = Buffer.alloc(32); + if (contract) { + if (contract.precision !== precision) + throw new Error('precision is not equal to the asset contract precision'); + contractHash = hashContract(contract); + } + const iss = { + assetAmount: toConfidentialAssetAmount(assetAmount, precision), + tokenAmount: toConfidentialTokenAmount(tokenAmount, precision), + assetBlindingNonce: Buffer.alloc(32), + // in case of issuance, the asset entropy = the contract hash. + assetEntropy: contractHash, + }; + return iss; } exports.newIssuance = newIssuance; /** @@ -77,16 +77,16 @@ exports.newIssuance = newIssuance; * @param contractHash the 32 bytes contract hash. */ function generateEntropy(outPoint, contractHash = Buffer.alloc(32)) { - if (outPoint.txHash.length !== 32) { - throw new Error('Invalid txHash length'); - } - const tBuffer = Buffer.allocUnsafe(36); - const s = new bufferutils_1.BufferWriter(tBuffer, 0); - s.writeSlice(outPoint.txHash); - s.writeInt32(outPoint.vout); - const prevoutHash = bcrypto.hash256(s.buffer); - const concatened = Buffer.concat([prevoutHash, contractHash]); - return sha256d_1.sha256Midstate(concatened); + if (outPoint.txHash.length !== 32) { + throw new Error('Invalid txHash length'); + } + const tBuffer = Buffer.allocUnsafe(36); + const s = new bufferutils_1.BufferWriter(tBuffer, 0); + s.writeSlice(outPoint.txHash); + s.writeInt32(outPoint.vout); + const prevoutHash = bcrypto.hash256(s.buffer); + const concatened = Buffer.concat([prevoutHash, contractHash]); + return sha256d_1.sha256Midstate(concatened); } exports.generateEntropy = generateEntropy; /** @@ -94,9 +94,10 @@ exports.generateEntropy = generateEntropy; * @param entropy the entropy used to compute the asset tag. */ function calculateAsset(entropy) { - if (entropy.length !== 32) throw new Error('Invalid entropy length'); - const kZero = Buffer.alloc(32); - return sha256d_1.sha256Midstate(Buffer.concat([entropy, kZero])); + if (entropy.length !== 32) + throw new Error('Invalid entropy length'); + const kZero = Buffer.alloc(32); + return sha256d_1.sha256Midstate(Buffer.concat([entropy, kZero])); } exports.calculateAsset = calculateAsset; /** @@ -105,19 +106,19 @@ exports.calculateAsset = calculateAsset; * @param confidential true if confidential. */ function calculateReissuanceToken(entropy, confidential = false) { - if (entropy.length !== 32) throw new Error('Invalid entropy length'); - return sha256d_1.sha256Midstate( - Buffer.concat([ - entropy, - Buffer.of(getTokenFlag(confidential) + 1), - Buffer.alloc(31), - ]), - ); + if (entropy.length !== 32) + throw new Error('Invalid entropy length'); + return sha256d_1.sha256Midstate(Buffer.concat([ + entropy, + Buffer.of(getTokenFlag(confidential) + 1), + Buffer.alloc(31), + ])); } exports.calculateReissuanceToken = calculateReissuanceToken; function getTokenFlag(confidential) { - if (confidential) return 1; - return 0; + if (confidential) + return 1; + return 0; } /** * converts asset amount to confidential value. @@ -125,8 +126,8 @@ function getTokenFlag(confidential) { * @param precision the precision, 8 by default. */ function toConfidentialAssetAmount(assetAmount, precision = 8) { - const amount = Math.pow(10, precision) * assetAmount; - return confidential_1.satoshiToConfidentialValue(amount); + const amount = Math.pow(10, precision) * assetAmount; + return confidential_1.satoshiToConfidentialValue(amount); } /** * converts token amount to confidential value. @@ -134,22 +135,19 @@ function toConfidentialAssetAmount(assetAmount, precision = 8) { * @param precision the precision, 8 by default. */ function toConfidentialTokenAmount(tokenAmount, precision = 8) { - if (tokenAmount === 0) return Buffer.from('00', 'hex'); - return toConfidentialAssetAmount(tokenAmount, precision); + if (tokenAmount === 0) + return Buffer.from('00', 'hex'); + return toConfidentialAssetAmount(tokenAmount, precision); } function validateAddIssuanceArgs(args) { - if (args.assetAmount <= 0) - throw new Error('asset amount must be greater than zero.'); - if (args.tokenAmount < 0) throw new Error('token amount must be positive.'); - if (args.tokenAddress) { - if ( - address_1.isConfidential(args.assetAddress) !== - address_1.isConfidential(args.tokenAddress) - ) { - throw new Error( - 'tokenAddress and assetAddress are not of the same type (confidential or unconfidential).', - ); + if (args.assetAmount <= 0) + throw new Error('asset amount must be greater than zero.'); + if (args.tokenAmount < 0) + throw new Error('token amount must be positive.'); + if (args.tokenAddress) { + if (address_1.isConfidential(args.assetAddress) !== address_1.isConfidential(args.tokenAddress)) { + throw new Error('tokenAddress and assetAddress are not of the same type (confidential or unconfidential).'); + } } - } } exports.validateAddIssuanceArgs = validateAddIssuanceArgs; diff --git a/src/networks.js b/src/networks.js index 66872d2d1..0200bf8b0 100644 --- a/src/networks.js +++ b/src/networks.js @@ -1,30 +1,30 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); exports.liquid = { - messagePrefix: '\x18Liquid Signed Message:\n', - bech32: 'ex', - blech32: 'lq', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4, - }, - pubKeyHash: 57, - scriptHash: 39, - wif: 0x80, - confidentialPrefix: 12, - assetHash: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d', + messagePrefix: '\x18Liquid Signed Message:\n', + bech32: 'ex', + blech32: 'lq', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 57, + scriptHash: 39, + wif: 0x80, + confidentialPrefix: 12, + assetHash: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d', }; exports.regtest = { - messagePrefix: '\x18Liquid Signed Message:\n', - bech32: 'ert', - blech32: 'el', - bip32: { - public: 0x043587cf, - private: 0x04358394, - }, - pubKeyHash: 235, - scriptHash: 75, - wif: 0xef, - confidentialPrefix: 4, - assetHash: '5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225', + messagePrefix: '\x18Liquid Signed Message:\n', + bech32: 'ert', + blech32: 'el', + bip32: { + public: 0x043587cf, + private: 0x04358394, + }, + pubKeyHash: 235, + scriptHash: 75, + wif: 0xef, + confidentialPrefix: 4, + assetHash: '5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225', }; diff --git a/src/payments/embed.js b/src/payments/embed.js index 817de800a..df4c9bc57 100644 --- a/src/payments/embed.js +++ b/src/payments/embed.js @@ -1,60 +1,58 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const networks_1 = require('../networks'); -const bscript = __importStar(require('../script')); -const lazy = __importStar(require('./lazy')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const networks_1 = require("../networks"); +const bscript = __importStar(require("../script")); +const lazy = __importStar(require("./lazy")); const typef = require('typeforce'); const OPS = bscript.OPS; function stacksEqual(a, b) { - if (a.length !== b.length) return false; - return a.every((x, i) => { - return x.equals(b[i]); - }); + if (a.length !== b.length) + return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); } // output: OP_RETURN ... function p2data(a, opts) { - if (!a.data && !a.output) throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef( - { - network: typef.maybe(typef.Object), - output: typef.maybe(typef.Buffer), - data: typef.maybe(typef.arrayOf(typef.Buffer)), - }, - a, - ); - const network = a.network || networks_1.liquid; - const o = { name: 'embed', network }; - lazy.prop(o, 'output', () => { - if (!a.data) return; - return bscript.compile([OPS.OP_RETURN].concat(a.data)); - }); - lazy.prop(o, 'data', () => { - if (!a.output) return; - return bscript.decompile(a.output).slice(1); - }); - // extended validation - if (opts.validate) { - 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)) - throw new TypeError('Output is invalid'); - if (a.data && !stacksEqual(a.data, o.data)) - throw new TypeError('Data mismatch'); + if (!a.data && !a.output) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef({ + network: typef.maybe(typef.Object), + output: typef.maybe(typef.Buffer), + data: typef.maybe(typef.arrayOf(typef.Buffer)), + }, a); + const network = a.network || networks_1.liquid; + const o = { name: 'embed', network }; + lazy.prop(o, 'output', () => { + if (!a.data) + return; + return bscript.compile([OPS.OP_RETURN].concat(a.data)); + }); + lazy.prop(o, 'data', () => { + if (!a.output) + return; + return bscript.decompile(a.output).slice(1); + }); + // extended validation + if (opts.validate) { + 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)) + throw new TypeError('Output is invalid'); + if (a.data && !stacksEqual(a.data, o.data)) + throw new TypeError('Data mismatch'); + } } - } - return Object.assign(o, a); + return Object.assign(o, a); } exports.p2data = p2data; diff --git a/src/payments/index.js b/src/payments/index.js index ddab97768..f21762ddd 100644 --- a/src/payments/index.js +++ b/src/payments/index.js @@ -1,18 +1,18 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); -const embed_1 = require('./embed'); +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const embed_1 = require("./embed"); exports.embed = embed_1.p2data; -const p2ms_1 = require('./p2ms'); +const p2ms_1 = require("./p2ms"); exports.p2ms = p2ms_1.p2ms; -const p2pk_1 = require('./p2pk'); +const p2pk_1 = require("./p2pk"); exports.p2pk = p2pk_1.p2pk; -const p2pkh_1 = require('./p2pkh'); +const p2pkh_1 = require("./p2pkh"); exports.p2pkh = p2pkh_1.p2pkh; -const p2sh_1 = require('./p2sh'); +const p2sh_1 = require("./p2sh"); exports.p2sh = p2sh_1.p2sh; -const p2wpkh_1 = require('./p2wpkh'); +const p2wpkh_1 = require("./p2wpkh"); exports.p2wpkh = p2wpkh_1.p2wpkh; -const p2wsh_1 = require('./p2wsh'); +const p2wsh_1 = require("./p2wsh"); exports.p2wsh = p2wsh_1.p2wsh; // TODO // witness commitment diff --git a/src/payments/lazy.js b/src/payments/lazy.js index 1a7152158..d8494fdda 100644 --- a/src/payments/lazy.js +++ b/src/payments/lazy.js @@ -1,31 +1,32 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); function prop(object, name, f) { - Object.defineProperty(object, name, { - configurable: true, - enumerable: true, - get() { - const _value = f.call(this); - this[name] = _value; - return _value; - }, - set(_value) { - Object.defineProperty(this, name, { + Object.defineProperty(object, name, { configurable: true, enumerable: true, - value: _value, - writable: true, - }); - }, - }); + get() { + const _value = f.call(this); + this[name] = _value; + return _value; + }, + set(_value) { + Object.defineProperty(this, name, { + configurable: true, + enumerable: true, + value: _value, + writable: true, + }); + }, + }); } exports.prop = prop; function value(f) { - let _value; - return () => { - if (_value !== undefined) return _value; - _value = f(); - return _value; - }; + let _value; + return () => { + if (_value !== undefined) + return _value; + _value = f(); + return _value; + }; } exports.value = value; diff --git a/src/payments/p2ms.js b/src/payments/p2ms.js index a441379c5..ad05744d0 100644 --- a/src/payments/p2ms.js +++ b/src/payments/p2ms.js @@ -1,156 +1,153 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const networks_1 = require('../networks'); -const bscript = __importStar(require('../script')); -const lazy = __importStar(require('./lazy')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const networks_1 = require("../networks"); +const bscript = __importStar(require("../script")); +const lazy = __importStar(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; - return a.every((x, i) => { - return x.equals(b[i]); - }); + if (a.length !== b.length) + return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); } // input: OP_0 [signatures ...] // output: m [pubKeys ...] n OP_CHECKMULTISIG function p2ms(a, opts) { - if ( - !a.input && - !a.output && - !(a.pubkeys && a.m !== undefined) && - !a.signatures - ) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - function isAcceptableSignature(x) { - return ( - bscript.isCanonicalScriptSignature(x) || - (opts.allowIncomplete && x === OPS.OP_0) !== undefined - ); - } - typef( - { - 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), - }, - a, - ); - const network = a.network || networks_1.liquid; - const o = { network }; - let chunks = []; - let decoded = false; - function decode(output) { - if (decoded) return; - decoded = true; - chunks = bscript.decompile(output); - o.m = chunks[0] - OP_INT_BASE; - o.n = chunks[chunks.length - 2] - OP_INT_BASE; - o.pubkeys = chunks.slice(1, -2); - } - lazy.prop(o, 'output', () => { - if (!a.m) return; - if (!o.n) return; - if (!a.pubkeys) return; - return bscript.compile( - [].concat( - OP_INT_BASE + a.m, - a.pubkeys, - OP_INT_BASE + o.n, - OPS.OP_CHECKMULTISIG, - ), - ); - }); - lazy.prop(o, 'm', () => { - if (!o.output) return; - decode(o.output); - return o.m; - }); - lazy.prop(o, 'n', () => { - if (!o.pubkeys) return; - return o.pubkeys.length; - }); - lazy.prop(o, 'pubkeys', () => { - if (!a.output) return; - decode(a.output); - return o.pubkeys; - }); - lazy.prop(o, 'signatures', () => { - if (!a.input) return; - return bscript.decompile(a.input).slice(1); - }); - lazy.prop(o, 'input', () => { - if (!a.signatures) return; - return bscript.compile([OPS.OP_0].concat(a.signatures)); - }); - lazy.prop(o, 'witness', () => { - if (!o.input) return; - return []; - }); - lazy.prop(o, 'name', () => { - if (!o.m || !o.n) return; - return `p2ms(${o.m} of ${o.n})`; - }); - // extended validation - 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])) - 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))) - 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'); - if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) - throw new TypeError('Pubkeys mismatch'); + if (!a.input && + !a.output && + !(a.pubkeys && a.m !== undefined) && + !a.signatures) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + function isAcceptableSignature(x) { + return (bscript.isCanonicalScriptSignature(x) || + (opts.allowIncomplete && x === OPS.OP_0) !== undefined); } - if (a.pubkeys) { - if (a.n !== undefined && a.n !== a.pubkeys.length) - throw new TypeError('Pubkey count mismatch'); - o.n = a.pubkeys.length; - if (o.n < o.m) throw new TypeError('Pubkey count cannot be less than m'); + typef({ + 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), + }, a); + const network = a.network || networks_1.liquid; + const o = { network }; + let chunks = []; + let decoded = false; + function decode(output) { + if (decoded) + return; + decoded = true; + chunks = bscript.decompile(output); + o.m = chunks[0] - OP_INT_BASE; + o.n = chunks[chunks.length - 2] - OP_INT_BASE; + o.pubkeys = chunks.slice(1, -2); } - if (a.signatures) { - if (a.signatures.length < o.m) - throw new TypeError('Not enough signatures provided'); - if (a.signatures.length > o.m) - throw new TypeError('Too many signatures provided'); + lazy.prop(o, 'output', () => { + if (!a.m) + return; + if (!o.n) + return; + if (!a.pubkeys) + return; + return bscript.compile([].concat(OP_INT_BASE + a.m, a.pubkeys, OP_INT_BASE + o.n, OPS.OP_CHECKMULTISIG)); + }); + lazy.prop(o, 'm', () => { + if (!o.output) + return; + decode(o.output); + return o.m; + }); + lazy.prop(o, 'n', () => { + if (!o.pubkeys) + return; + return o.pubkeys.length; + }); + lazy.prop(o, 'pubkeys', () => { + if (!a.output) + return; + decode(a.output); + return o.pubkeys; + }); + lazy.prop(o, 'signatures', () => { + if (!a.input) + return; + return bscript.decompile(a.input).slice(1); + }); + lazy.prop(o, 'input', () => { + if (!a.signatures) + return; + return bscript.compile([OPS.OP_0].concat(a.signatures)); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) + return; + return []; + }); + lazy.prop(o, 'name', () => { + if (!o.m || !o.n) + return; + return `p2ms(${o.m} of ${o.n})`; + }); + // extended validation + 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])) + 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))) + 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'); + if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) + throw new TypeError('Pubkeys mismatch'); + } + if (a.pubkeys) { + if (a.n !== undefined && a.n !== a.pubkeys.length) + throw new TypeError('Pubkey count mismatch'); + o.n = a.pubkeys.length; + if (o.n < o.m) + throw new TypeError('Pubkey count cannot be less than m'); + } + if (a.signatures) { + if (a.signatures.length < o.m) + throw new TypeError('Not enough signatures provided'); + if (a.signatures.length > o.m) + throw new TypeError('Too many signatures provided'); + } + if (a.input) { + if (a.input[0] !== OPS.OP_0) + throw new TypeError('Input is invalid'); + if (o.signatures.length === 0 || + !o.signatures.every(isAcceptableSignature)) + throw new TypeError('Input has invalid signature(s)'); + if (a.signatures && !stacksEqual(a.signatures, o.signatures)) + throw new TypeError('Signature mismatch'); + if (a.m !== undefined && a.m !== a.signatures.length) + throw new TypeError('Signature count mismatch'); + } } - if (a.input) { - if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid'); - if ( - o.signatures.length === 0 || - !o.signatures.every(isAcceptableSignature) - ) - throw new TypeError('Input has invalid signature(s)'); - if (a.signatures && !stacksEqual(a.signatures, o.signatures)) - throw new TypeError('Signature mismatch'); - if (a.m !== undefined && a.m !== a.signatures.length) - throw new TypeError('Signature count mismatch'); - } - } - return Object.assign(o, a); + return Object.assign(o, a); } exports.p2ms = p2ms; diff --git a/src/payments/p2pk.js b/src/payments/p2pk.js index fb874ece9..bc9cebae0 100644 --- a/src/payments/p2pk.js +++ b/src/payments/p2pk.js @@ -1,83 +1,82 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const networks_1 = require('../networks'); -const bscript = __importStar(require('../script')); -const lazy = __importStar(require('./lazy')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const networks_1 = require("../networks"); +const bscript = __importStar(require("../script")); +const lazy = __importStar(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( - { - 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), - }, - a, - ); - const _chunks = lazy.value(() => { - return bscript.decompile(a.input); - }); - const network = a.network || networks_1.liquid; - const o = { name: 'p2pk', network }; - lazy.prop(o, 'output', () => { - if (!a.pubkey) return; - return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]); - }); - lazy.prop(o, 'pubkey', () => { - if (!a.output) return; - return a.output.slice(1, -1); - }); - lazy.prop(o, 'signature', () => { - if (!a.input) return; - return _chunks()[0]; - }); - lazy.prop(o, 'input', () => { - if (!a.signature) return; - return bscript.compile([a.signature]); - }); - lazy.prop(o, 'witness', () => { - if (!o.input) return; - return []; - }); - // extended validation - if (opts.validate) { - 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 (a.pubkey && !a.pubkey.equals(o.pubkey)) - throw new TypeError('Pubkey mismatch'); + if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef({ + 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), + }, a); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input); + }); + const network = a.network || networks_1.liquid; + const o = { name: 'p2pk', network }; + lazy.prop(o, 'output', () => { + if (!a.pubkey) + return; + return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]); + }); + lazy.prop(o, 'pubkey', () => { + if (!a.output) + return; + return a.output.slice(1, -1); + }); + lazy.prop(o, 'signature', () => { + if (!a.input) + return; + return _chunks()[0]; + }); + lazy.prop(o, 'input', () => { + if (!a.signature) + return; + return bscript.compile([a.signature]); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) + return; + return []; + }); + // extended validation + if (opts.validate) { + 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 (a.pubkey && !a.pubkey.equals(o.pubkey)) + throw new TypeError('Pubkey mismatch'); + } + if (a.signature) { + if (a.input && !a.input.equals(o.input)) + throw new TypeError('Signature mismatch'); + } + if (a.input) { + if (_chunks().length !== 1) + throw new TypeError('Input is invalid'); + if (!bscript.isCanonicalScriptSignature(o.signature)) + throw new TypeError('Input has invalid signature'); + } } - if (a.signature) { - if (a.input && !a.input.equals(o.input)) - throw new TypeError('Signature mismatch'); - } - if (a.input) { - if (_chunks().length !== 1) throw new TypeError('Input is invalid'); - if (!bscript.isCanonicalScriptSignature(o.signature)) - throw new TypeError('Input has invalid signature'); - } - } - return Object.assign(o, a); + return Object.assign(o, a); } exports.p2pk = p2pk; diff --git a/src/payments/p2pkh.js b/src/payments/p2pkh.js index eed38a92e..07ac251d0 100644 --- a/src/payments/p2pkh.js +++ b/src/payments/p2pkh.js @@ -1,20 +1,16 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bcrypto = __importStar(require('../crypto')); -const networks_1 = require('../networks'); -const bscript = __importStar(require('../script')); -const lazy = __importStar(require('./lazy')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bcrypto = __importStar(require("../crypto")); +const networks_1 = require("../networks"); +const bscript = __importStar(require("../script")); +const lazy = __importStar(require("./lazy")); const typef = require('typeforce'); const OPS = bscript.OPS; const ecc = require('tiny-secp256k1'); @@ -22,180 +18,191 @@ const bs58check = require('bs58check'); // 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 && - !a.confidentialAddress - ) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef( - { - 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), - blindkey: typef.maybe(ecc.isPoint), - confidentialAddress: typef.maybe(typef.String), - }, - a, - ); - const _address = lazy.value(() => { - const payload = bs58check.decode(a.address); - const version = payload.readUInt8(0); - const hash = payload.slice(1); - return { version, hash }; - }); - const _chunks = lazy.value(() => { - return bscript.decompile(a.input); - }); - const _confidentialAddress = lazy.value(() => { - const payload = bs58check.decode(a.confidentialAddress); - const blindkey = payload.slice(2, 35); - const unconfidentialAddressBuffer = Buffer.concat([ - Buffer.from([payload.readUInt8(1)]), - payload.slice(35), - ]); - const unconfidentialAddress = bs58check.encode(unconfidentialAddressBuffer); - return { blindkey, unconfidentialAddress }; - }); - const network = a.network || networks_1.liquid; - const o = { name: 'p2pkh', network }; - lazy.prop(o, 'address', () => { - if (!o.hash) return; - const payload = Buffer.allocUnsafe(21); - payload.writeUInt8(network.pubKeyHash, 0); - o.hash.copy(payload, 1); - return bs58check.encode(payload); - }); - lazy.prop(o, 'hash', () => { - if (a.output) return a.output.slice(3, 23); - if (a.address) return _address().hash; - if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey); - if (a.confidentialAddress) { - const address = _confidentialAddress().unconfidentialAddress; - return bs58check.decode(address).slice(1); + if (!a.address && + !a.hash && + !a.output && + !a.pubkey && + !a.input && + !a.confidentialAddress) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef({ + 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), + blindkey: typef.maybe(ecc.isPoint), + confidentialAddress: typef.maybe(typef.String), + }, a); + const _address = lazy.value(() => { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input); + }); + const _confidentialAddress = lazy.value(() => { + const payload = bs58check.decode(a.confidentialAddress); + const blindkey = payload.slice(2, 35); + const unconfidentialAddressBuffer = Buffer.concat([ + Buffer.from([payload.readUInt8(1)]), + payload.slice(35), + ]); + const unconfidentialAddress = bs58check.encode(unconfidentialAddressBuffer); + return { blindkey, unconfidentialAddress }; + }); + const network = a.network || networks_1.liquid; + const o = { name: 'p2pkh', network }; + lazy.prop(o, 'address', () => { + if (!o.hash) + return; + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(network.pubKeyHash, 0); + o.hash.copy(payload, 1); + return bs58check.encode(payload); + }); + lazy.prop(o, 'hash', () => { + if (a.output) + return a.output.slice(3, 23); + if (a.address) + return _address().hash; + if (a.pubkey || o.pubkey) + return bcrypto.hash160(a.pubkey || o.pubkey); + if (a.confidentialAddress) { + const address = _confidentialAddress().unconfidentialAddress; + return bs58check.decode(address).slice(1); + } + }); + lazy.prop(o, 'output', () => { + if (!o.hash) + return; + return bscript.compile([ + OPS.OP_DUP, + OPS.OP_HASH160, + o.hash, + OPS.OP_EQUALVERIFY, + OPS.OP_CHECKSIG, + ]); + }); + lazy.prop(o, 'pubkey', () => { + if (!a.input) + return; + return _chunks()[1]; + }); + lazy.prop(o, 'signature', () => { + if (!a.input) + return; + return _chunks()[0]; + }); + lazy.prop(o, 'input', () => { + if (!a.pubkey) + return; + if (!a.signature) + return; + return bscript.compile([a.signature, a.pubkey]); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) + return; + return []; + }); + lazy.prop(o, 'blindkey', () => { + if (a.confidentialAddress) + return _confidentialAddress().blindkey; + if (a.blindkey) + return a.blindkey; + }); + lazy.prop(o, 'confidentialAddress', () => { + if (!o.address) + return; + if (!o.blindkey) + return; + const payload = bs58check.decode(o.address); + const confidentialAddress = Buffer.concat([ + Buffer.from([network.confidentialPrefix, payload.readUInt8(0)]), + o.blindkey, + Buffer.from(payload.slice(1)), + ]); + return bs58check.encode(confidentialAddress); + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + let blindkey = Buffer.from([]); + if (a.address) { + if (_address().version !== network.pubKeyHash) + throw new TypeError('Invalid version or Network mismatch'); + if (_address().hash.length !== 20) + throw new TypeError('Invalid address'); + hash = _address().hash; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else + hash = a.hash; + } + if (a.output) { + if (a.output.length !== 25 || + a.output[0] !== OPS.OP_DUP || + a.output[1] !== OPS.OP_HASH160 || + a.output[2] !== 0x14 || + a.output[23] !== OPS.OP_EQUALVERIFY || + a.output[24] !== OPS.OP_CHECKSIG) + throw new TypeError('Output is invalid'); + const hash2 = a.output.slice(3, 23); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else + hash = hash2; + } + if (a.pubkey) { + const pkh = bcrypto.hash160(a.pubkey); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + else + hash = pkh; + } + if (a.input) { + const chunks = _chunks(); + 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])) + throw new TypeError('Input has invalid pubkey'); + if (a.signature && !a.signature.equals(chunks[0])) + throw new TypeError('Signature mismatch'); + if (a.pubkey && !a.pubkey.equals(chunks[1])) + throw new TypeError('Pubkey mismatch'); + const pkh = bcrypto.hash160(chunks[1]); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + } + if (a.confidentialAddress) { + if (a.address && + a.address !== _confidentialAddress().unconfidentialAddress) + throw new TypeError('Address mismatch'); + if (blindkey.length > 0 && + !blindkey.equals(_confidentialAddress().blindkey)) + throw new TypeError('Blindkey mismatch'); + else + blindkey = _confidentialAddress().blindkey; + } + if (a.blindkey) { + if (!ecc.isPoint(a.blindkey)) + throw new TypeError('Blindkey is invalid'); + if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) + throw new TypeError('Blindkey mismatch'); + else + blindkey = a.blindkey; + } } - }); - lazy.prop(o, 'output', () => { - if (!o.hash) return; - return bscript.compile([ - OPS.OP_DUP, - OPS.OP_HASH160, - o.hash, - OPS.OP_EQUALVERIFY, - OPS.OP_CHECKSIG, - ]); - }); - lazy.prop(o, 'pubkey', () => { - if (!a.input) return; - return _chunks()[1]; - }); - lazy.prop(o, 'signature', () => { - if (!a.input) return; - return _chunks()[0]; - }); - lazy.prop(o, 'input', () => { - if (!a.pubkey) return; - if (!a.signature) return; - return bscript.compile([a.signature, a.pubkey]); - }); - lazy.prop(o, 'witness', () => { - if (!o.input) return; - return []; - }); - lazy.prop(o, 'blindkey', () => { - if (a.confidentialAddress) return _confidentialAddress().blindkey; - if (a.blindkey) return a.blindkey; - }); - lazy.prop(o, 'confidentialAddress', () => { - if (!o.address) return; - if (!o.blindkey) return; - const payload = bs58check.decode(o.address); - const confidentialAddress = Buffer.concat([ - Buffer.from([network.confidentialPrefix, payload.readUInt8(0)]), - o.blindkey, - Buffer.from(payload.slice(1)), - ]); - return bs58check.encode(confidentialAddress); - }); - // extended validation - if (opts.validate) { - let hash = Buffer.from([]); - let blindkey = Buffer.from([]); - if (a.address) { - if (_address().version !== network.pubKeyHash) - throw new TypeError('Invalid version or Network mismatch'); - if (_address().hash.length !== 20) throw new TypeError('Invalid address'); - hash = _address().hash; - } - if (a.hash) { - if (hash.length > 0 && !hash.equals(a.hash)) - throw new TypeError('Hash mismatch'); - else hash = a.hash; - } - if (a.output) { - if ( - a.output.length !== 25 || - a.output[0] !== OPS.OP_DUP || - a.output[1] !== OPS.OP_HASH160 || - a.output[2] !== 0x14 || - a.output[23] !== OPS.OP_EQUALVERIFY || - a.output[24] !== OPS.OP_CHECKSIG - ) - throw new TypeError('Output is invalid'); - const hash2 = a.output.slice(3, 23); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else hash = hash2; - } - if (a.pubkey) { - const pkh = bcrypto.hash160(a.pubkey); - if (hash.length > 0 && !hash.equals(pkh)) - throw new TypeError('Hash mismatch'); - else hash = pkh; - } - if (a.input) { - const chunks = _chunks(); - 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])) - throw new TypeError('Input has invalid pubkey'); - if (a.signature && !a.signature.equals(chunks[0])) - throw new TypeError('Signature mismatch'); - if (a.pubkey && !a.pubkey.equals(chunks[1])) - throw new TypeError('Pubkey mismatch'); - const pkh = bcrypto.hash160(chunks[1]); - if (hash.length > 0 && !hash.equals(pkh)) - throw new TypeError('Hash mismatch'); - } - if (a.confidentialAddress) { - if ( - a.address && - a.address !== _confidentialAddress().unconfidentialAddress - ) - throw new TypeError('Address mismatch'); - if ( - blindkey.length > 0 && - !blindkey.equals(_confidentialAddress().blindkey) - ) - throw new TypeError('Blindkey mismatch'); - else blindkey = _confidentialAddress().blindkey; - } - if (a.blindkey) { - if (!ecc.isPoint(a.blindkey)) throw new TypeError('Blindkey is invalid'); - if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) - throw new TypeError('Blindkey mismatch'); - else blindkey = a.blindkey; - } - } - return Object.assign(o, a); + return Object.assign(o, a); } exports.p2pkh = p2pkh; diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index d00ebcb3d..9b8f34f0c 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -1,253 +1,258 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bcrypto = __importStar(require('../crypto')); -const networks_1 = require('../networks'); -const bscript = __importStar(require('../script')); -const lazy = __importStar(require('./lazy')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bcrypto = __importStar(require("../crypto")); +const networks_1 = require("../networks"); +const bscript = __importStar(require("../script")); +const lazy = __importStar(require("./lazy")); const typef = require('typeforce'); const OPS = bscript.OPS; const ecc = require('tiny-secp256k1'); const bs58check = require('bs58check'); function stacksEqual(a, b) { - if (a.length !== b.length) return false; - return a.every((x, i) => { - return x.equals(b[i]); - }); + if (a.length !== b.length) + return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); } // input: [redeemScriptSig ...] {redeemScript} // witness: // output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL function p2sh(a, opts) { - if ( - !a.address && - !a.hash && - !a.output && - !a.redeem && - !a.input && - !a.confidentialAddress - ) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef( - { - 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({ + if (!a.address && + !a.hash && + !a.output && + !a.redeem && + !a.input && + !a.confidentialAddress) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef({ network: typef.maybe(typef.Object), - output: typef.maybe(typef.Buffer), + 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)), + }), input: typef.maybe(typef.Buffer), witness: typef.maybe(typef.arrayOf(typef.Buffer)), - }), - input: typef.maybe(typef.Buffer), - witness: typef.maybe(typef.arrayOf(typef.Buffer)), - blindkey: typef.maybe(ecc.isPoint), - confidentialAddress: typef.maybe(typef.String), - }, - a, - ); - let network = a.network; - if (!network) { - network = (a.redeem && a.redeem.network) || networks_1.liquid; - } - const o = { network }; - const _address = lazy.value(() => { - const payload = bs58check.decode(a.address); - const version = payload.readUInt8(0); - const hash = payload.slice(1); - return { version, hash }; - }); - const _chunks = lazy.value(() => { - return bscript.decompile(a.input); - }); - const _redeem = lazy.value(() => { - const chunks = _chunks(); - return { - network, - output: chunks[chunks.length - 1], - input: bscript.compile(chunks.slice(0, -1)), - witness: a.witness || [], - }; - }); - const _confidentialAddress = lazy.value(() => { - const payload = bs58check.decode(a.confidentialAddress); - const blindkey = payload.slice(2, 35); - const unconfidentialAddressBuffer = Buffer.concat([ - Buffer.from([payload.readUInt8(1)]), - payload.slice(35), - ]); - const unconfidentialAddress = bs58check.encode(unconfidentialAddressBuffer); - return { blindkey, unconfidentialAddress }; - }); - // output dependents - lazy.prop(o, 'address', () => { - if (!o.hash) return; - const payload = Buffer.allocUnsafe(21); - payload.writeUInt8(o.network.scriptHash, 0); - o.hash.copy(payload, 1); - return bs58check.encode(payload); - }); - lazy.prop(o, 'hash', () => { - // in order of least effort - if (a.output) return a.output.slice(2, 22); - if (a.address) return _address().hash; - if (o.redeem && o.redeem.output) return bcrypto.hash160(o.redeem.output); - if (a.confidentialAddress) { - const address = _confidentialAddress().unconfidentialAddress; - return bs58check.decode(address).slice(1); + blindkey: typef.maybe(ecc.isPoint), + confidentialAddress: typef.maybe(typef.String), + }, a); + let network = a.network; + if (!network) { + network = (a.redeem && a.redeem.network) || networks_1.liquid; } - }); - lazy.prop(o, 'output', () => { - if (!o.hash) return; - return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]); - }); - // input dependents - lazy.prop(o, 'redeem', () => { - if (!a.input) return; - return _redeem(); - }); - lazy.prop(o, 'input', () => { - if (!a.redeem || !a.redeem.input || !a.redeem.output) return; - return bscript.compile( - [].concat(bscript.decompile(a.redeem.input), a.redeem.output), - ); - }); - lazy.prop(o, 'witness', () => { - if (o.redeem && o.redeem.witness) return o.redeem.witness; - if (o.input) return []; - }); - lazy.prop(o, 'name', () => { - const nameParts = ['p2sh']; - if (o.redeem !== undefined) nameParts.push(o.redeem.name); - return nameParts.join('-'); - }); - lazy.prop(o, 'blindkey', () => { - if (a.confidentialAddress) return _confidentialAddress().blindkey; - if (a.blindkey) return a.blindkey; - }); - lazy.prop(o, 'confidentialAddress', () => { - if (!o.address) return; - if (!o.blindkey) return; - const payload = bs58check.decode(o.address); - const confidentialAddress = Buffer.concat([ - Buffer.from([network.confidentialPrefix, payload.readUInt8(0)]), - o.blindkey, - Buffer.from(payload.slice(1)), - ]); - return bs58check.encode(confidentialAddress); - }); - if (opts.validate) { - let hash = Buffer.from([]); - let blindkey = Buffer.from([]); - if (a.address) { - if (_address().version !== network.scriptHash) - throw new TypeError('Invalid version or Network mismatch'); - if (_address().hash.length !== 20) throw new TypeError('Invalid address'); - hash = _address().hash; - } - if (a.hash) { - if (hash.length > 0 && !hash.equals(a.hash)) - throw new TypeError('Hash mismatch'); - else hash = a.hash; - } - if (a.output) { - if ( - a.output.length !== 23 || - a.output[0] !== OPS.OP_HASH160 || - a.output[1] !== 0x14 || - a.output[22] !== OPS.OP_EQUAL - ) - throw new TypeError('Output is invalid'); - const hash2 = a.output.slice(2, 22); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else hash = hash2; - } - // inlined to prevent 'no-inner-declarations' failing - const checkRedeem = redeem => { - // is the redeem output empty/invalid? - if (redeem.output) { - const decompile = bscript.decompile(redeem.output); - if (!decompile || decompile.length < 1) - throw new TypeError('Redeem.output too short'); - // match hash against other sources - const hash2 = bcrypto.hash160(redeem.output); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else hash = hash2; - } - if (redeem.input) { - const hasInput = redeem.input.length > 0; - const hasWitness = redeem.witness && redeem.witness.length > 0; - if (!hasInput && !hasWitness) throw new TypeError('Empty input'); - if (hasInput && hasWitness) - throw new TypeError('Input and witness provided'); - if (hasInput) { - const richunks = bscript.decompile(redeem.input); - if (!bscript.isPushOnly(richunks)) - throw new TypeError('Non push-only scriptSig'); + const o = { network }; + const _address = lazy.value(() => { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input); + }); + const _redeem = lazy.value(() => { + const chunks = _chunks(); + return { + network, + output: chunks[chunks.length - 1], + input: bscript.compile(chunks.slice(0, -1)), + witness: a.witness || [], + }; + }); + const _confidentialAddress = lazy.value(() => { + const payload = bs58check.decode(a.confidentialAddress); + const blindkey = payload.slice(2, 35); + const unconfidentialAddressBuffer = Buffer.concat([ + Buffer.from([payload.readUInt8(1)]), + payload.slice(35), + ]); + const unconfidentialAddress = bs58check.encode(unconfidentialAddressBuffer); + return { blindkey, unconfidentialAddress }; + }); + // output dependents + lazy.prop(o, 'address', () => { + if (!o.hash) + return; + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(o.network.scriptHash, 0); + o.hash.copy(payload, 1); + return bs58check.encode(payload); + }); + lazy.prop(o, 'hash', () => { + // in order of least effort + if (a.output) + return a.output.slice(2, 22); + if (a.address) + return _address().hash; + if (o.redeem && o.redeem.output) + return bcrypto.hash160(o.redeem.output); + if (a.confidentialAddress) { + const address = _confidentialAddress().unconfidentialAddress; + return bs58check.decode(address).slice(1); + } + }); + lazy.prop(o, 'output', () => { + if (!o.hash) + return; + return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]); + }); + // input dependents + lazy.prop(o, 'redeem', () => { + if (!a.input) + return; + return _redeem(); + }); + lazy.prop(o, 'input', () => { + if (!a.redeem || !a.redeem.input || !a.redeem.output) + return; + return bscript.compile([].concat(bscript.decompile(a.redeem.input), a.redeem.output)); + }); + lazy.prop(o, 'witness', () => { + if (o.redeem && o.redeem.witness) + return o.redeem.witness; + if (o.input) + return []; + }); + lazy.prop(o, 'name', () => { + const nameParts = ['p2sh']; + if (o.redeem !== undefined) + nameParts.push(o.redeem.name); + return nameParts.join('-'); + }); + lazy.prop(o, 'blindkey', () => { + if (a.confidentialAddress) + return _confidentialAddress().blindkey; + if (a.blindkey) + return a.blindkey; + }); + lazy.prop(o, 'confidentialAddress', () => { + if (!o.address) + return; + if (!o.blindkey) + return; + const payload = bs58check.decode(o.address); + const confidentialAddress = Buffer.concat([ + Buffer.from([network.confidentialPrefix, payload.readUInt8(0)]), + o.blindkey, + Buffer.from(payload.slice(1)), + ]); + return bs58check.encode(confidentialAddress); + }); + if (opts.validate) { + let hash = Buffer.from([]); + let blindkey = Buffer.from([]); + if (a.address) { + if (_address().version !== network.scriptHash) + throw new TypeError('Invalid version or Network mismatch'); + if (_address().hash.length !== 20) + throw new TypeError('Invalid address'); + hash = _address().hash; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else + hash = a.hash; + } + if (a.output) { + if (a.output.length !== 23 || + a.output[0] !== OPS.OP_HASH160 || + a.output[1] !== 0x14 || + a.output[22] !== OPS.OP_EQUAL) + throw new TypeError('Output is invalid'); + const hash2 = a.output.slice(2, 22); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else + hash = hash2; + } + // inlined to prevent 'no-inner-declarations' failing + const checkRedeem = (redeem) => { + // is the redeem output empty/invalid? + if (redeem.output) { + const decompile = bscript.decompile(redeem.output); + if (!decompile || decompile.length < 1) + throw new TypeError('Redeem.output too short'); + // match hash against other sources + const hash2 = bcrypto.hash160(redeem.output); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else + hash = hash2; + } + if (redeem.input) { + const hasInput = redeem.input.length > 0; + const hasWitness = redeem.witness && redeem.witness.length > 0; + if (!hasInput && !hasWitness) + throw new TypeError('Empty input'); + if (hasInput && hasWitness) + throw new TypeError('Input and witness provided'); + if (hasInput) { + const richunks = bscript.decompile(redeem.input); + if (!bscript.isPushOnly(richunks)) + throw new TypeError('Non push-only scriptSig'); + } + } + }; + if (a.input) { + const chunks = _chunks(); + if (!chunks || chunks.length < 1) + throw new TypeError('Input too short'); + if (!Buffer.isBuffer(_redeem().output)) + throw new TypeError('Input is invalid'); + checkRedeem(_redeem()); + } + if (a.redeem) { + if (a.redeem.network && a.redeem.network !== network) + throw new TypeError('Network mismatch'); + if (a.input) { + const redeem = _redeem(); + if (a.redeem.output && !a.redeem.output.equals(redeem.output)) + throw new TypeError('Redeem.output mismatch'); + if (a.redeem.input && !a.redeem.input.equals(redeem.input)) + throw new TypeError('Redeem.input mismatch'); + } + checkRedeem(a.redeem); + } + if (a.witness) { + if (a.redeem && + a.redeem.witness && + !stacksEqual(a.redeem.witness, a.witness)) + throw new TypeError('Witness and redeem.witness mismatch'); + } + if (a.confidentialAddress) { + if (a.address && + a.address !== _confidentialAddress().unconfidentialAddress) + throw new TypeError('Address mismatch'); + if (blindkey.length > 0 && + !blindkey.equals(_confidentialAddress().blindkey)) + throw new TypeError('Blindkey mismatch'); + else + blindkey = _confidentialAddress().blindkey; + } + if (a.blindkey) { + if (!ecc.isPoint(a.blindkey)) + throw new TypeError('Blindkey is invalid'); + if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) + throw new TypeError('Blindkey mismatch'); + else + blindkey = a.blindkey; } - } - }; - if (a.input) { - const chunks = _chunks(); - if (!chunks || chunks.length < 1) throw new TypeError('Input too short'); - if (!Buffer.isBuffer(_redeem().output)) - throw new TypeError('Input is invalid'); - checkRedeem(_redeem()); - } - if (a.redeem) { - if (a.redeem.network && a.redeem.network !== network) - throw new TypeError('Network mismatch'); - if (a.input) { - const redeem = _redeem(); - if (a.redeem.output && !a.redeem.output.equals(redeem.output)) - throw new TypeError('Redeem.output mismatch'); - if (a.redeem.input && !a.redeem.input.equals(redeem.input)) - throw new TypeError('Redeem.input mismatch'); - } - checkRedeem(a.redeem); - } - if (a.witness) { - if ( - a.redeem && - a.redeem.witness && - !stacksEqual(a.redeem.witness, a.witness) - ) - throw new TypeError('Witness and redeem.witness mismatch'); - } - if (a.confidentialAddress) { - if ( - a.address && - a.address !== _confidentialAddress().unconfidentialAddress - ) - throw new TypeError('Address mismatch'); - if ( - blindkey.length > 0 && - !blindkey.equals(_confidentialAddress().blindkey) - ) - throw new TypeError('Blindkey mismatch'); - else blindkey = _confidentialAddress().blindkey; - } - if (a.blindkey) { - if (!ecc.isPoint(a.blindkey)) throw new TypeError('Blindkey is invalid'); - if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) - throw new TypeError('Blindkey mismatch'); - else blindkey = a.blindkey; } - } - return Object.assign(o, a); + return Object.assign(o, a); } exports.p2sh = p2sh; diff --git a/src/payments/p2wpkh.js b/src/payments/p2wpkh.js index 4f9956aad..21b2b7a46 100644 --- a/src/payments/p2wpkh.js +++ b/src/payments/p2wpkh.js @@ -1,21 +1,17 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const baddress = __importStar(require('../address')); -const bcrypto = __importStar(require('../crypto')); -const networks_1 = require('../networks'); -const bscript = __importStar(require('../script')); -const lazy = __importStar(require('./lazy')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const baddress = __importStar(require("../address")); +const bcrypto = __importStar(require("../crypto")); +const networks_1 = require("../networks"); +const bscript = __importStar(require("../script")); +const lazy = __importStar(require("./lazy")); const typef = require('typeforce'); const OPS = bscript.OPS; const ecc = require('tiny-secp256k1'); @@ -25,172 +21,179 @@ const EMPTY_BUFFER = Buffer.alloc(0); // input: <> // output: OP_0 {pubKeyHash} function p2wpkh(a, opts) { - if ( - !a.address && - !a.hash && - !a.output && - !a.pubkey && - !a.witness && - !a.confidentialAddress - ) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef( - { - 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)), - }, - a, - ); - const network = a.network || networks_1.liquid; - const _address = lazy.value(() => { - const result = bech32.decode(a.address); - const version = result.words.shift(); - const data = bech32.fromWords(result.words); - return { - version, - prefix: result.prefix, - data: Buffer.from(data), - }; - }); - const _confidentialAddress = lazy.value(() => { - const result = baddress.fromBlech32(a.confidentialAddress); - return { - blindingKey: result.pubkey, - unconfidentialAddress: baddress.toBech32( - result.data.slice(2), - result.version, - network.bech32, - ), - }; - }); - const o = { name: 'p2wpkh', network }; - lazy.prop(o, 'address', () => { - if (!o.hash) return; - const words = bech32.toWords(o.hash); - words.unshift(0x00); - return bech32.encode(network.bech32, words); - }); - lazy.prop(o, 'hash', () => { - if (a.output) return a.output.slice(2, 22); - if (a.address) return _address().data; - if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey); - if (a.confidentialAddress) { - const addr = _confidentialAddress().unconfidentialAddress; - return baddress.fromBech32(addr).data; + if (!a.address && + !a.hash && + !a.output && + !a.pubkey && + !a.witness && + !a.confidentialAddress) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef({ + 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)), + }, a); + const network = a.network || networks_1.liquid; + const _address = lazy.value(() => { + const result = bech32.decode(a.address); + const version = result.words.shift(); + const data = bech32.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: Buffer.from(data), + }; + }); + const _confidentialAddress = lazy.value(() => { + const result = baddress.fromBlech32(a.confidentialAddress); + return { + blindingKey: result.pubkey, + unconfidentialAddress: baddress.toBech32(result.data.slice(2), result.version, network.bech32), + }; + }); + const o = { name: 'p2wpkh', network }; + lazy.prop(o, 'address', () => { + if (!o.hash) + return; + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', () => { + if (a.output) + return a.output.slice(2, 22); + if (a.address) + return _address().data; + if (a.pubkey || o.pubkey) + return bcrypto.hash160(a.pubkey || o.pubkey); + if (a.confidentialAddress) { + const addr = _confidentialAddress().unconfidentialAddress; + return baddress.fromBech32(addr).data; + } + }); + lazy.prop(o, 'output', () => { + if (!o.hash) + return; + return bscript.compile([OPS.OP_0, o.hash]); + }); + lazy.prop(o, 'pubkey', () => { + if (a.pubkey) + return a.pubkey; + if (!a.witness) + return; + return a.witness[1]; + }); + lazy.prop(o, 'signature', () => { + if (!a.witness) + return; + return a.witness[0]; + }); + lazy.prop(o, 'input', () => { + if (!o.witness) + return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', () => { + if (!a.pubkey) + return; + if (!a.signature) + return; + return [a.signature, a.pubkey]; + }); + lazy.prop(o, 'blindkey', () => { + if (a.confidentialAddress) + return _confidentialAddress().blindingKey; + if (a.blindkey) + return a.blindkey; + }); + lazy.prop(o, 'confidentialAddress', () => { + if (!o.address) + return; + if (!o.blindkey) + return; + const res = baddress.fromBech32(o.address); + const data = Buffer.concat([ + Buffer.from([res.version, res.data.length]), + res.data, + ]); + return baddress.toBlech32(data, o.blindkey, o.network.blech32); + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + let blindkey = Buffer.from([]); + if (a.address) { + if (network && network.bech32 !== _address().prefix) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== 0x00) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 20) + throw new TypeError('Invalid address data'); + hash = _address().data; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else + hash = a.hash; + } + if (a.output) { + if (a.output.length !== 22 || + a.output[0] !== OPS.OP_0 || + a.output[1] !== 0x14) + throw new TypeError('Output is invalid'); + if (hash.length > 0 && !hash.equals(a.output.slice(2))) + throw new TypeError('Hash mismatch'); + else + hash = a.output.slice(2); + } + if (a.pubkey) { + const pkh = bcrypto.hash160(a.pubkey); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + else + hash = pkh; + } + 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])) + throw new TypeError('Witness has invalid pubkey'); + if (a.signature && !a.signature.equals(a.witness[0])) + throw new TypeError('Signature mismatch'); + if (a.pubkey && !a.pubkey.equals(a.witness[1])) + throw new TypeError('Pubkey mismatch'); + const pkh = bcrypto.hash160(a.witness[1]); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + } + if (a.confidentialAddress) { + if (a.address && + a.address !== _confidentialAddress().unconfidentialAddress) + throw new TypeError('Address mismatch'); + if (blindkey.length > 0 && + !blindkey.equals(_confidentialAddress().blindingKey)) + throw new TypeError('Blindkey mismatch'); + else + blindkey = _confidentialAddress().blindingKey; + } + if (a.blindkey) { + if (!ecc.isPoint(a.blindkey)) + throw new TypeError('Blindkey is invalid'); + if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) + throw new TypeError('Blindkey mismatch'); + else + blindkey = a.blindkey; + } } - }); - lazy.prop(o, 'output', () => { - if (!o.hash) return; - return bscript.compile([OPS.OP_0, o.hash]); - }); - lazy.prop(o, 'pubkey', () => { - if (a.pubkey) return a.pubkey; - if (!a.witness) return; - return a.witness[1]; - }); - lazy.prop(o, 'signature', () => { - if (!a.witness) return; - return a.witness[0]; - }); - lazy.prop(o, 'input', () => { - if (!o.witness) return; - return EMPTY_BUFFER; - }); - lazy.prop(o, 'witness', () => { - if (!a.pubkey) return; - if (!a.signature) return; - return [a.signature, a.pubkey]; - }); - lazy.prop(o, 'blindkey', () => { - if (a.confidentialAddress) return _confidentialAddress().blindingKey; - if (a.blindkey) return a.blindkey; - }); - lazy.prop(o, 'confidentialAddress', () => { - if (!o.address) return; - if (!o.blindkey) return; - const res = baddress.fromBech32(o.address); - const data = Buffer.concat([ - Buffer.from([res.version, res.data.length]), - res.data, - ]); - return baddress.toBlech32(data, o.blindkey, o.network.blech32); - }); - // extended validation - if (opts.validate) { - let hash = Buffer.from([]); - let blindkey = Buffer.from([]); - if (a.address) { - if (network && network.bech32 !== _address().prefix) - throw new TypeError('Invalid prefix or Network mismatch'); - if (_address().version !== 0x00) - throw new TypeError('Invalid address version'); - if (_address().data.length !== 20) - throw new TypeError('Invalid address data'); - hash = _address().data; - } - if (a.hash) { - if (hash.length > 0 && !hash.equals(a.hash)) - throw new TypeError('Hash mismatch'); - else hash = a.hash; - } - if (a.output) { - if ( - a.output.length !== 22 || - a.output[0] !== OPS.OP_0 || - a.output[1] !== 0x14 - ) - throw new TypeError('Output is invalid'); - if (hash.length > 0 && !hash.equals(a.output.slice(2))) - throw new TypeError('Hash mismatch'); - else hash = a.output.slice(2); - } - if (a.pubkey) { - const pkh = bcrypto.hash160(a.pubkey); - if (hash.length > 0 && !hash.equals(pkh)) - throw new TypeError('Hash mismatch'); - else hash = pkh; - } - 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])) - throw new TypeError('Witness has invalid pubkey'); - if (a.signature && !a.signature.equals(a.witness[0])) - throw new TypeError('Signature mismatch'); - if (a.pubkey && !a.pubkey.equals(a.witness[1])) - throw new TypeError('Pubkey mismatch'); - const pkh = bcrypto.hash160(a.witness[1]); - if (hash.length > 0 && !hash.equals(pkh)) - throw new TypeError('Hash mismatch'); - } - if (a.confidentialAddress) { - if ( - a.address && - a.address !== _confidentialAddress().unconfidentialAddress - ) - throw new TypeError('Address mismatch'); - if ( - blindkey.length > 0 && - !blindkey.equals(_confidentialAddress().blindingKey) - ) - throw new TypeError('Blindkey mismatch'); - else blindkey = _confidentialAddress().blindingKey; - } - if (a.blindkey) { - if (!ecc.isPoint(a.blindkey)) throw new TypeError('Blindkey is invalid'); - if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) - throw new TypeError('Blindkey mismatch'); - else blindkey = a.blindkey; - } - } - return Object.assign(o, a); + return Object.assign(o, a); } exports.p2wpkh = p2wpkh; diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js index db2211941..a4d53ba9f 100644 --- a/src/payments/p2wsh.js +++ b/src/payments/p2wsh.js @@ -1,252 +1,247 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const baddress = __importStar(require('../address')); -const bcrypto = __importStar(require('../crypto')); -const networks_1 = require('../networks'); -const bscript = __importStar(require('../script')); -const lazy = __importStar(require('./lazy')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const baddress = __importStar(require("../address")); +const bcrypto = __importStar(require("../crypto")); +const networks_1 = require("../networks"); +const bscript = __importStar(require("../script")); +const lazy = __importStar(require("./lazy")); const typef = require('typeforce'); 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; - return a.every((x, i) => { - return x.equals(b[i]); - }); + if (a.length !== b.length) + return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); } // input: <> // witness: [redeemScriptSig ...] {redeemScript} // output: OP_0 {sha256(redeemScript)} function p2wsh(a, opts) { - if ( - !a.address && - !a.hash && - !a.output && - !a.redeem && - !a.witness && - !a.confidentialAddress - ) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef( - { - 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), + if (!a.address && + !a.hash && + !a.output && + !a.redeem && + !a.witness && + !a.confidentialAddress) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef({ network: typef.maybe(typef.Object), - output: typef.maybe(typef.Buffer), + 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)), + }), + input: typef.maybe(typef.BufferN(0)), witness: typef.maybe(typef.arrayOf(typef.Buffer)), - }), - input: typef.maybe(typef.BufferN(0)), - witness: typef.maybe(typef.arrayOf(typef.Buffer)), - blindkey: typef.maybe(ecc.isPoint), - confidentialAddress: typef.maybe(typef.String), - }, - a, - ); - let network = a.network; - if (!network) { - network = (a.redeem && a.redeem.network) || networks_1.liquid; - } - const _address = lazy.value(() => { - const result = bech32.decode(a.address); - const version = result.words.shift(); - const data = bech32.fromWords(result.words); - return { - version, - prefix: result.prefix, - data: Buffer.from(data), - }; - }); - const _rchunks = lazy.value(() => { - return bscript.decompile(a.redeem.input); - }); - const _confidentialAddress = lazy.value(() => { - const result = baddress.fromBlech32(a.confidentialAddress); - return { - blindingKey: result.pubkey, - unconfidentialAddress: baddress.toBech32( - result.data.slice(2), - result.version, - network.bech32, - ), - }; - }); - const o = { network }; - lazy.prop(o, 'address', () => { - if (!o.hash) return; - const words = bech32.toWords(o.hash); - words.unshift(0x00); - return bech32.encode(network.bech32, words); - }); - lazy.prop(o, 'hash', () => { - if (a.output) return a.output.slice(2); - if (a.address) return _address().data; - if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output); - if (a.confidentialAddress) { - const addr = _confidentialAddress().unconfidentialAddress; - return baddress.fromBech32(addr).data; + blindkey: typef.maybe(ecc.isPoint), + confidentialAddress: typef.maybe(typef.String), + }, a); + let network = a.network; + if (!network) { + network = (a.redeem && a.redeem.network) || networks_1.liquid; } - }); - lazy.prop(o, 'output', () => { - if (!o.hash) return; - return bscript.compile([OPS.OP_0, o.hash]); - }); - lazy.prop(o, 'redeem', () => { - if (!a.witness) return; - return { - output: a.witness[a.witness.length - 1], - input: EMPTY_BUFFER, - witness: a.witness.slice(0, -1), - }; - }); - lazy.prop(o, 'input', () => { - if (!o.witness) return; - return EMPTY_BUFFER; - }); - lazy.prop(o, 'witness', () => { - // transform redeem input to witness stack? - if ( - a.redeem && - a.redeem.input && - a.redeem.input.length > 0 && - a.redeem.output && - a.redeem.output.length > 0 - ) { - const stack = bscript.toStack(_rchunks()); - // assign, and blank the existing input - o.redeem = Object.assign({ witness: stack }, a.redeem); - o.redeem.input = EMPTY_BUFFER; - return [].concat(stack, a.redeem.output); + const _address = lazy.value(() => { + const result = bech32.decode(a.address); + const version = result.words.shift(); + const data = bech32.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: Buffer.from(data), + }; + }); + const _rchunks = lazy.value(() => { + return bscript.decompile(a.redeem.input); + }); + const _confidentialAddress = lazy.value(() => { + const result = baddress.fromBlech32(a.confidentialAddress); + return { + blindingKey: result.pubkey, + unconfidentialAddress: baddress.toBech32(result.data.slice(2), result.version, network.bech32), + }; + }); + const o = { network }; + lazy.prop(o, 'address', () => { + if (!o.hash) + return; + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', () => { + if (a.output) + return a.output.slice(2); + if (a.address) + return _address().data; + if (o.redeem && o.redeem.output) + return bcrypto.sha256(o.redeem.output); + if (a.confidentialAddress) { + const addr = _confidentialAddress().unconfidentialAddress; + return baddress.fromBech32(addr).data; + } + }); + lazy.prop(o, 'output', () => { + if (!o.hash) + return; + return bscript.compile([OPS.OP_0, o.hash]); + }); + lazy.prop(o, 'redeem', () => { + if (!a.witness) + return; + return { + output: a.witness[a.witness.length - 1], + input: EMPTY_BUFFER, + witness: a.witness.slice(0, -1), + }; + }); + lazy.prop(o, 'input', () => { + if (!o.witness) + return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', () => { + // transform redeem input to witness stack? + if (a.redeem && + a.redeem.input && + a.redeem.input.length > 0 && + a.redeem.output && + a.redeem.output.length > 0) { + const stack = bscript.toStack(_rchunks()); + // assign, and blank the existing input + o.redeem = Object.assign({ witness: stack }, a.redeem); + o.redeem.input = EMPTY_BUFFER; + return [].concat(stack, a.redeem.output); + } + if (!a.redeem) + return; + if (!a.redeem.output) + return; + if (!a.redeem.witness) + return; + return [].concat(a.redeem.witness, a.redeem.output); + }); + lazy.prop(o, 'name', () => { + const nameParts = ['p2wsh']; + if (o.redeem !== undefined) + nameParts.push(o.redeem.name); + return nameParts.join('-'); + }); + lazy.prop(o, 'blindkey', () => { + if (a.confidentialAddress) + return _confidentialAddress().blindingKey; + if (a.blindkey) + return a.blindkey; + }); + lazy.prop(o, 'confidentialAddress', () => { + if (!o.address) + return; + if (!o.blindkey) + return; + const res = baddress.fromBech32(o.address); + const data = Buffer.concat([ + Buffer.from([res.version, res.data.length]), + res.data, + ]); + return baddress.toBlech32(data, o.blindkey, o.network.blech32); + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + let blindkey = Buffer.from([]); + if (a.address) { + if (_address().prefix !== network.bech32) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== 0x00) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 32) + throw new TypeError('Invalid address data'); + hash = _address().data; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else + hash = a.hash; + } + if (a.output) { + if (a.output.length !== 34 || + a.output[0] !== OPS.OP_0 || + a.output[1] !== 0x20) + throw new TypeError('Output is invalid'); + const hash2 = a.output.slice(2); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else + hash = hash2; + } + if (a.redeem) { + if (a.redeem.network && a.redeem.network !== network) + throw new TypeError('Network mismatch'); + // is there two redeem sources? + if (a.redeem.input && + a.redeem.input.length > 0 && + a.redeem.witness && + a.redeem.witness.length > 0) + throw new TypeError('Ambiguous witness source'); + // is the redeem output non-empty? + if (a.redeem.output) { + if (bscript.decompile(a.redeem.output).length === 0) + throw new TypeError('Redeem.output is invalid'); + // match hash against other sources + const hash2 = bcrypto.sha256(a.redeem.output); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else + hash = hash2; + } + if (a.redeem.input && !bscript.isPushOnly(_rchunks())) + throw new TypeError('Non push-only scriptSig'); + if (a.witness && + a.redeem.witness && + !stacksEqual(a.witness, a.redeem.witness)) + throw new TypeError('Witness and redeem.witness mismatch'); + } + if (a.witness) { + if (a.redeem && + a.redeem.output && + !a.redeem.output.equals(a.witness[a.witness.length - 1])) + throw new TypeError('Witness and redeem.output mismatch'); + } + if (a.confidentialAddress) { + if (a.address && + a.address !== _confidentialAddress().unconfidentialAddress) + throw new TypeError('Address mismatch'); + if (blindkey.length > 0 && + !blindkey.equals(_confidentialAddress().blindingKey)) + throw new TypeError('Blindkey mismatch'); + else + blindkey = _confidentialAddress().blindingKey; + } + if (a.blindkey) { + if (!ecc.isPoint(a.blindkey)) + throw new TypeError('Blindkey is invalid'); + if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) + throw new TypeError('Blindkey mismatch'); + else + blindkey = a.blindkey; + } } - if (!a.redeem) return; - if (!a.redeem.output) return; - if (!a.redeem.witness) return; - return [].concat(a.redeem.witness, a.redeem.output); - }); - lazy.prop(o, 'name', () => { - const nameParts = ['p2wsh']; - if (o.redeem !== undefined) nameParts.push(o.redeem.name); - return nameParts.join('-'); - }); - lazy.prop(o, 'blindkey', () => { - if (a.confidentialAddress) return _confidentialAddress().blindingKey; - if (a.blindkey) return a.blindkey; - }); - lazy.prop(o, 'confidentialAddress', () => { - if (!o.address) return; - if (!o.blindkey) return; - const res = baddress.fromBech32(o.address); - const data = Buffer.concat([ - Buffer.from([res.version, res.data.length]), - res.data, - ]); - return baddress.toBlech32(data, o.blindkey, o.network.blech32); - }); - // extended validation - if (opts.validate) { - let hash = Buffer.from([]); - let blindkey = Buffer.from([]); - if (a.address) { - if (_address().prefix !== network.bech32) - throw new TypeError('Invalid prefix or Network mismatch'); - if (_address().version !== 0x00) - throw new TypeError('Invalid address version'); - if (_address().data.length !== 32) - throw new TypeError('Invalid address data'); - hash = _address().data; - } - if (a.hash) { - if (hash.length > 0 && !hash.equals(a.hash)) - throw new TypeError('Hash mismatch'); - else hash = a.hash; - } - if (a.output) { - if ( - a.output.length !== 34 || - a.output[0] !== OPS.OP_0 || - a.output[1] !== 0x20 - ) - throw new TypeError('Output is invalid'); - const hash2 = a.output.slice(2); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else hash = hash2; - } - if (a.redeem) { - if (a.redeem.network && a.redeem.network !== network) - throw new TypeError('Network mismatch'); - // is there two redeem sources? - if ( - a.redeem.input && - a.redeem.input.length > 0 && - a.redeem.witness && - a.redeem.witness.length > 0 - ) - throw new TypeError('Ambiguous witness source'); - // is the redeem output non-empty? - if (a.redeem.output) { - if (bscript.decompile(a.redeem.output).length === 0) - throw new TypeError('Redeem.output is invalid'); - // match hash against other sources - const hash2 = bcrypto.sha256(a.redeem.output); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else hash = hash2; - } - if (a.redeem.input && !bscript.isPushOnly(_rchunks())) - throw new TypeError('Non push-only scriptSig'); - if ( - a.witness && - a.redeem.witness && - !stacksEqual(a.witness, a.redeem.witness) - ) - throw new TypeError('Witness and redeem.witness mismatch'); - } - if (a.witness) { - if ( - a.redeem && - a.redeem.output && - !a.redeem.output.equals(a.witness[a.witness.length - 1]) - ) - throw new TypeError('Witness and redeem.output mismatch'); - } - if (a.confidentialAddress) { - if ( - a.address && - a.address !== _confidentialAddress().unconfidentialAddress - ) - throw new TypeError('Address mismatch'); - if ( - blindkey.length > 0 && - !blindkey.equals(_confidentialAddress().blindingKey) - ) - throw new TypeError('Blindkey mismatch'); - else blindkey = _confidentialAddress().blindingKey; - } - if (a.blindkey) { - if (!ecc.isPoint(a.blindkey)) throw new TypeError('Blindkey is invalid'); - if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) - throw new TypeError('Blindkey mismatch'); - else blindkey = a.blindkey; - } - } - return Object.assign(o, a); + return Object.assign(o, a); } exports.p2wsh = p2wsh; diff --git a/src/psbt.js b/src/psbt.js index 814654c28..5382d5f75 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1,73 +1,49 @@ -'use strict'; -var __awaiter = - (this && this.__awaiter) || - function(thisArg, _arguments, P, generator) { - return new (P || (P = Promise))(function(resolve, reject) { - function fulfilled(value) { - try { - step(generator.next(value)); - } catch (e) { - reject(e); - } - } - function rejected(value) { - try { - step(generator['throw'](value)); - } catch (e) { - reject(e); - } - } - function step(result) { - result.done - ? resolve(result.value) - : new P(function(resolve) { - resolve(result.value); - }).then(fulfilled, rejected); - } - step((generator = generator.apply(thisArg, _arguments || [])).next()); +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); }); - }; -var __importStar = - (this && this.__importStar) || - function(mod) { +}; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const confidential = __importStar(require('./confidential')); -const varuint = __importStar(require('bip174-liquid/src/lib/converter/varint')); -const address_1 = require('./address'); -const bufferutils_1 = require('./bufferutils'); -const crypto_1 = require('./crypto'); -const networks_1 = require('./networks'); -const transaction_1 = require('./transaction'); -const ecpair_1 = require('./ecpair'); -const issuance_1 = require('./issuance'); -const payments = __importStar(require('./payments')); -const bscript = __importStar(require('./script')); -const bip174_liquid_1 = require('bip174-liquid'); -const utils_1 = require('bip174-liquid/src/lib/utils'); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const confidential = __importStar(require("./confidential")); +const varuint = __importStar(require("bip174-liquid/src/lib/converter/varint")); +const address_1 = require("./address"); +const bufferutils_1 = require("./bufferutils"); +const crypto_1 = require("./crypto"); +const networks_1 = require("./networks"); +const transaction_1 = require("./transaction"); +const ecpair_1 = require("./ecpair"); +const issuance_1 = require("./issuance"); +const payments = __importStar(require("./payments")); +const bscript = __importStar(require("./script")); +const bip174_liquid_1 = require("bip174-liquid"); +const utils_1 = require("bip174-liquid/src/lib/utils"); const _randomBytes = require('randombytes'); /** * These are the default arguments for a Psbt instance. */ const DEFAULT_OPTS = { - /** - * A bitcoinjs Network object. This is only used if you pass an `address` - * parameter to addOutput. Otherwise it is not needed and can be left default. - */ - network: networks_1.liquid, - /** - * When extractTransaction is called, the fee rate is checked. - * 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, + /** + * A bitcoinjs Network object. This is only used if you pass an `address` + * parameter to addOutput. Otherwise it is not needed and can be left default. + */ + network: networks_1.liquid, + /** + * When extractTransaction is called, the fee rate is checked. + * 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, }; /** * Psbt class can parse and generate a PSBT binary based off of the BIP174. @@ -102,902 +78,672 @@ const DEFAULT_OPTS = { * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. */ class Psbt { - constructor( - opts = {}, - data = new bip174_liquid_1.Psbt(new PsbtTransaction()), - ) { - this.data = data; - // set defaults - this.opts = Object.assign({}, DEFAULT_OPTS, opts); - this.__CACHE = { - __NON_WITNESS_UTXO_TX_CACHE: [], - __NON_WITNESS_UTXO_BUF_CACHE: [], - __TX_IN_CACHE: {}, - __TX: this.data.globalMap.unsignedTx.tx, - }; - if (this.data.inputs.length === 0) this.setVersion(2); - // Make data hidden when enumerating - const dpew = (obj, attr, enumerable, writable) => - Object.defineProperty(obj, attr, { - enumerable, - writable, - }); - 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_liquid_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; - } - combine(...those) { - this.data.combine(...those.map(o => o.data)); - return this; - } - clone() { - // TODO: more efficient cloning - const res = Psbt.fromBuffer(this.data.toBuffer()); - res.opts = JSON.parse(JSON.stringify(this.opts)); - return res; - } - setMaximumFeeRate(satoshiPerByte) { - check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw - this.opts.maximumFeeRate = satoshiPerByte; - } - setVersion(version) { - check32Bit(version); - checkInputsForPartialSig(this.data.inputs, 'setVersion'); - const c = this.__CACHE; - c.__TX.version = version; - c.__EXTRACTED_TX = undefined; - return this; - } - setLocktime(locktime) { - check32Bit(locktime); - checkInputsForPartialSig(this.data.inputs, 'setLocktime'); - const c = this.__CACHE; - c.__TX.locktime = locktime; - c.__EXTRACTED_TX = undefined; - return this; - } - setInputSequence(inputIndex, sequence) { - check32Bit(sequence); - checkInputsForPartialSig(this.data.inputs, 'setInputSequence'); - const c = this.__CACHE; - if (c.__TX.ins.length <= inputIndex) { - throw new Error('Input index too high'); - } - c.__TX.ins[inputIndex].sequence = sequence; - c.__EXTRACTED_TX = undefined; - return this; - } - addInputs(inputDatas) { - inputDatas.forEach(inputData => this.addInput(inputData)); - return this; - } - addInput(inputData) { - if ( - arguments.length > 1 || - !inputData || - inputData.hash === undefined || - inputData.index === undefined - ) { - throw new Error( - `Invalid arguments for Psbt.addInput. ` + - `Requires single object with at least [hash] and [index]`, - ); - } - checkInputsForPartialSig(this.data.inputs, 'addInput'); - const c = this.__CACHE; - this.data.addInput(inputData); - const txIn = c.__TX.ins[c.__TX.ins.length - 1]; - checkTxInputCache(c, txIn); - const inputIndex = this.data.inputs.length - 1; - const input = this.data.inputs[inputIndex]; - if (input.nonWitnessUtxo) { - addNonWitnessTxCache(this.__CACHE, input, inputIndex); - } - c.__FEE = undefined; - c.__FEE_RATE = undefined; - c.__EXTRACTED_TX = undefined; - return this; - } - addIssuance(args, inputIndex) { - issuance_1.validateAddIssuanceArgs(args); // throw an error if args are invalid - if (inputIndex && !this.data.inputs[inputIndex]) { - throw new Error(`The input ${inputIndex} does not exist.`); - // check if the input is available for issuance. - } else { - // verify if there is at least one input available. - if (this.__CACHE.__TX.ins.filter(i => !i.issuance).length === 0) - throw new Error( - 'transaction needs at least one input without issuance data.', - ); - // search and extract the input index. - inputIndex = this.__CACHE.__TX.ins.findIndex(i => !i.issuance); - } - if (this.__CACHE.__TX.ins[inputIndex].issuance) - throw new Error(`The input ${inputIndex} already has issuance data.`); - const { hash, index } = this.__CACHE.__TX.ins[inputIndex]; - // create an issuance object using the vout and the args - const issuance = issuance_1.newIssuance( - args.assetAmount, - args.tokenAmount, - args.precision, - args.contract, - ); - // generate the entropy - const entropy = issuance_1.generateEntropy( - { txHash: hash, vout: index }, - issuance.assetEntropy, - ); - // add the issuance to the input. - this.__CACHE.__TX.ins[inputIndex].issuance = issuance; - const asset = Buffer.concat([ - Buffer.of(args.confidential ? 0x0a : 0x01), - issuance_1.calculateAsset(entropy), - ]); - const assetScript = address_1.toOutputScript(args.assetAddress); - // send the asset amount to the asset address. - this.addOutput({ - value: issuance.assetAmount, - script: assetScript, - asset, - nonce: Buffer.from('00', 'hex'), - }); - // check if the token amount is not 0 - if (args.tokenAmount !== 0) { - if (!args.tokenAddress) - throw new Error("tokenAddress can't be undefined if tokenAmount > 0"); - const token = issuance_1.calculateReissuanceToken( - entropy, - args.confidential, - ); - const tokenScript = address_1.toOutputScript(args.tokenAddress); - // send the token amount to the token address. - this.addOutput({ - script: tokenScript, - value: issuance.tokenAmount, - asset: Buffer.concat([Buffer.of(0x01), token]), - nonce: Buffer.from('00', 'hex'), - }); - } - return this; - } - addOutputs(outputDatas) { - outputDatas.forEach(outputData => this.addOutput(outputData)); - return this; - } - addOutput(outputData) { - if ( - arguments.length > 1 || - !outputData || - outputData.value === undefined || - (outputData.address === undefined && outputData.script === undefined) - ) { - throw new Error( - `Invalid arguments for Psbt.addOutput. ` + - `Requires single object with at least [script or address] and [value]`, - ); - } - checkInputsForPartialSig(this.data.inputs, 'addOutput'); - const { address } = outputData; - if (typeof address === 'string') { - const { network } = this.opts; - const script = address_1.toOutputScript(address, network); - outputData = Object.assign(outputData, { script }); - } - const c = this.__CACHE; - this.data.addOutput(outputData); - c.__FEE = undefined; - c.__FEE_RATE = undefined; - c.__EXTRACTED_TX = undefined; - return this; - } - extractTransaction(disableFeeCheck) { - if (!this.data.inputs.every(isFinalized)) throw new Error('Not finalized'); - const c = this.__CACHE; - if (!disableFeeCheck) { - checkFees(this, c, this.opts); - } - if (c.__EXTRACTED_TX) return c.__EXTRACTED_TX; - const tx = c.__TX.clone(); - inputFinalizeGetAmts(this.data.inputs, tx, c, true); - return tx; - } - getFeeRate() { - return getTxCacheValue( - '__FEE_RATE', - 'fee rate', - this.data.inputs, - this.__CACHE, - ); - } - getFee() { - return getTxCacheValue('__FEE', 'fee', this.data.inputs, this.__CACHE); - } - finalizeAllInputs() { - 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) { - const input = utils_1.checkForInput(this.data.inputs, inputIndex); - const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput( - inputIndex, - input, - this.__CACHE, - ); - if (!script) throw new Error(`No script found for input #${inputIndex}`); - const scriptType = classifyScript(script); - if (!canFinalize(input, script, scriptType)) - throw new Error(`Can not finalize input #${inputIndex}`); - checkPartialSigSighashes(input); - const { finalScriptSig, finalScriptWitness } = getFinalScripts( - script, - scriptType, - input.partialSig, - isSegwit, - isP2SH, - isP2WSH, - ); - if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); - if (finalScriptWitness) - this.data.updateInput(inputIndex, { finalScriptWitness }); - if (!finalScriptSig && !finalScriptWitness) - throw new Error(`Unknown error finalizing input #${inputIndex}`); - this.data.clearFinalizedInput(inputIndex); - return this; - } - validateSignaturesOfAllInputs() { - 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), - ); - return results.reduce((final, res) => res === true && final, true); - } - validateSignaturesOfInput(inputIndex, pubkey) { - const input = this.data.inputs[inputIndex]; - const partialSig = (input || {}).partialSig; - if (!input || !partialSig || partialSig.length < 1) - throw new Error('No signatures to validate'); - const mySigs = pubkey - ? partialSig.filter(sig => sig.pubkey.equals(pubkey)) - : partialSig; - if (mySigs.length < 1) throw new Error('No signatures for this pubkey'); - const results = []; - let hashCache; - let scriptCache; - let sighashCache; - for (const pSig of mySigs) { - const sig = bscript.signature.decode(pSig.signature); - const { hash, script } = - sighashCache !== sig.hashType - ? getHashForSig( - inputIndex, - Object.assign({}, input, { sighashType: sig.hashType }), - this.__CACHE, - ) - : { hash: hashCache, script: scriptCache }; - sighashCache = sig.hashType; - hashCache = hash; - scriptCache = script; - checkScriptForPubkey(pSig.pubkey, script, 'verify'); - const keypair = ecpair_1.fromPublicKey(pSig.pubkey); - results.push(keypair.verify(hash, sig.signature)); - } - return results.every(res => res === true); - } - signAllInputsHD( - hdKeyPair, - sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], - ) { - if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { - throw new Error('Need HDSigner to sign input'); - } - const results = []; - for (const i of range(this.data.inputs.length)) { - try { - this.signInputHD(i, hdKeyPair, sighashTypes); - results.push(true); - } catch (err) { - results.push(false); - } - } - if (results.every(v => v === false)) { - throw new Error('No inputs were signed'); - } - return this; - } - signAllInputsHDAsync( - hdKeyPair, - sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], - ) { - return new Promise((resolve, reject) => { - if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { - return reject(new Error('Need HDSigner to sign input')); - } - const results = []; - const promises = []; - for (const i of range(this.data.inputs.length)) { - promises.push( - this.signInputHDAsync(i, hdKeyPair, sighashTypes).then( - () => { - results.push(true); - }, - () => { - results.push(false); - }, - ), - ); - } - return Promise.all(promises).then(() => { + constructor(opts = {}, data = new bip174_liquid_1.Psbt(new PsbtTransaction())) { + this.data = data; + // set defaults + this.opts = Object.assign({}, DEFAULT_OPTS, opts); + this.__CACHE = { + __NON_WITNESS_UTXO_TX_CACHE: [], + __NON_WITNESS_UTXO_BUF_CACHE: [], + __TX_IN_CACHE: {}, + __TX: this.data.globalMap.unsignedTx.tx, + }; + if (this.data.inputs.length === 0) + this.setVersion(2); + // Make data hidden when enumerating + const dpew = (obj, attr, enumerable, writable) => Object.defineProperty(obj, attr, { + enumerable, + writable, + }); + 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_liquid_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; + } + combine(...those) { + this.data.combine(...those.map(o => o.data)); + return this; + } + clone() { + // TODO: more efficient cloning + const res = Psbt.fromBuffer(this.data.toBuffer()); + res.opts = JSON.parse(JSON.stringify(this.opts)); + return res; + } + setMaximumFeeRate(satoshiPerByte) { + check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw + this.opts.maximumFeeRate = satoshiPerByte; + } + setVersion(version) { + check32Bit(version); + checkInputsForPartialSig(this.data.inputs, 'setVersion'); + const c = this.__CACHE; + c.__TX.version = version; + c.__EXTRACTED_TX = undefined; + return this; + } + setLocktime(locktime) { + check32Bit(locktime); + checkInputsForPartialSig(this.data.inputs, 'setLocktime'); + const c = this.__CACHE; + c.__TX.locktime = locktime; + c.__EXTRACTED_TX = undefined; + return this; + } + setInputSequence(inputIndex, sequence) { + check32Bit(sequence); + checkInputsForPartialSig(this.data.inputs, 'setInputSequence'); + const c = this.__CACHE; + if (c.__TX.ins.length <= inputIndex) { + throw new Error('Input index too high'); + } + c.__TX.ins[inputIndex].sequence = sequence; + c.__EXTRACTED_TX = undefined; + return this; + } + addInputs(inputDatas) { + inputDatas.forEach(inputData => this.addInput(inputData)); + return this; + } + addInput(inputData) { + if (arguments.length > 1 || + !inputData || + inputData.hash === undefined || + inputData.index === undefined) { + throw new Error(`Invalid arguments for Psbt.addInput. ` + + `Requires single object with at least [hash] and [index]`); + } + checkInputsForPartialSig(this.data.inputs, 'addInput'); + const c = this.__CACHE; + this.data.addInput(inputData); + const txIn = c.__TX.ins[c.__TX.ins.length - 1]; + checkTxInputCache(c, txIn); + const inputIndex = this.data.inputs.length - 1; + const input = this.data.inputs[inputIndex]; + if (input.nonWitnessUtxo) { + addNonWitnessTxCache(this.__CACHE, input, inputIndex); + } + c.__FEE = undefined; + c.__FEE_RATE = undefined; + c.__EXTRACTED_TX = undefined; + return this; + } + addIssuance(args, inputIndex) { + issuance_1.validateAddIssuanceArgs(args); // throw an error if args are invalid + if (inputIndex && !this.data.inputs[inputIndex]) { + throw new Error(`The input ${inputIndex} does not exist.`); + // check if the input is available for issuance. + } + else { + // verify if there is at least one input available. + if (this.__CACHE.__TX.ins.filter(i => !i.issuance).length === 0) + throw new Error('transaction needs at least one input without issuance data.'); + // search and extract the input index. + inputIndex = this.__CACHE.__TX.ins.findIndex(i => !i.issuance); + } + if (this.__CACHE.__TX.ins[inputIndex].issuance) + throw new Error(`The input ${inputIndex} already has issuance data.`); + const { hash, index } = this.__CACHE.__TX.ins[inputIndex]; + // create an issuance object using the vout and the args + const issuance = issuance_1.newIssuance(args.assetAmount, args.tokenAmount, args.precision, args.contract); + // generate the entropy + const entropy = issuance_1.generateEntropy({ txHash: hash, vout: index }, issuance.assetEntropy); + // add the issuance to the input. + this.__CACHE.__TX.ins[inputIndex].issuance = issuance; + const asset = Buffer.concat([ + Buffer.of(args.confidential ? 0x0a : 0x01), + issuance_1.calculateAsset(entropy), + ]); + const assetScript = address_1.toOutputScript(args.assetAddress); + // send the asset amount to the asset address. + this.addOutput({ + value: issuance.assetAmount, + script: assetScript, + asset, + nonce: Buffer.from('00', 'hex'), + }); + // check if the token amount is not 0 + if (args.tokenAmount !== 0) { + if (!args.tokenAddress) + throw new Error("tokenAddress can't be undefined if tokenAmount > 0"); + const token = issuance_1.calculateReissuanceToken(entropy, args.confidential); + const tokenScript = address_1.toOutputScript(args.tokenAddress); + // send the token amount to the token address. + this.addOutput({ + script: tokenScript, + value: issuance.tokenAmount, + asset: Buffer.concat([Buffer.of(0x01), token]), + nonce: Buffer.from('00', 'hex'), + }); + } + return this; + } + addOutputs(outputDatas) { + outputDatas.forEach(outputData => this.addOutput(outputData)); + return this; + } + addOutput(outputData) { + if (arguments.length > 1 || + !outputData || + outputData.value === undefined || + (outputData.address === undefined && + outputData.script === undefined)) { + throw new Error(`Invalid arguments for Psbt.addOutput. ` + + `Requires single object with at least [script or address] and [value]`); + } + checkInputsForPartialSig(this.data.inputs, 'addOutput'); + const { address } = outputData; + if (typeof address === 'string') { + const { network } = this.opts; + const script = address_1.toOutputScript(address, network); + outputData = Object.assign(outputData, { script }); + } + const c = this.__CACHE; + this.data.addOutput(outputData); + c.__FEE = undefined; + c.__FEE_RATE = undefined; + c.__EXTRACTED_TX = undefined; + return this; + } + extractTransaction(disableFeeCheck) { + if (!this.data.inputs.every(isFinalized)) + throw new Error('Not finalized'); + const c = this.__CACHE; + if (!disableFeeCheck) { + checkFees(this, c, this.opts); + } + if (c.__EXTRACTED_TX) + return c.__EXTRACTED_TX; + const tx = c.__TX.clone(); + inputFinalizeGetAmts(this.data.inputs, tx, c, true); + return tx; + } + getFeeRate() { + return getTxCacheValue('__FEE_RATE', 'fee rate', this.data.inputs, this.__CACHE); + } + getFee() { + return getTxCacheValue('__FEE', 'fee', this.data.inputs, this.__CACHE); + } + finalizeAllInputs() { + 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) { + const input = utils_1.checkForInput(this.data.inputs, inputIndex); + const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(inputIndex, input, this.__CACHE); + if (!script) + throw new Error(`No script found for input #${inputIndex}`); + const scriptType = classifyScript(script); + if (!canFinalize(input, script, scriptType)) + throw new Error(`Can not finalize input #${inputIndex}`); + checkPartialSigSighashes(input); + const { finalScriptSig, finalScriptWitness } = getFinalScripts(script, scriptType, input.partialSig, isSegwit, isP2SH, isP2WSH); + if (finalScriptSig) + this.data.updateInput(inputIndex, { finalScriptSig }); + if (finalScriptWitness) + this.data.updateInput(inputIndex, { finalScriptWitness }); + if (!finalScriptSig && !finalScriptWitness) + throw new Error(`Unknown error finalizing input #${inputIndex}`); + this.data.clearFinalizedInput(inputIndex); + return this; + } + validateSignaturesOfAllInputs() { + 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)); + return results.reduce((final, res) => res === true && final, true); + } + validateSignaturesOfInput(inputIndex, pubkey) { + const input = this.data.inputs[inputIndex]; + const partialSig = (input || {}).partialSig; + if (!input || !partialSig || partialSig.length < 1) + throw new Error('No signatures to validate'); + const mySigs = pubkey + ? partialSig.filter(sig => sig.pubkey.equals(pubkey)) + : partialSig; + if (mySigs.length < 1) + throw new Error('No signatures for this pubkey'); + const results = []; + let hashCache; + let scriptCache; + let sighashCache; + for (const pSig of mySigs) { + const sig = bscript.signature.decode(pSig.signature); + const { hash, script } = sighashCache !== sig.hashType + ? getHashForSig(inputIndex, Object.assign({}, input, { sighashType: sig.hashType }), this.__CACHE) + : { hash: hashCache, script: scriptCache }; + sighashCache = sig.hashType; + hashCache = hash; + scriptCache = script; + checkScriptForPubkey(pSig.pubkey, script, 'verify'); + const keypair = ecpair_1.fromPublicKey(pSig.pubkey); + results.push(keypair.verify(hash, sig.signature)); + } + return results.every(res => res === true); + } + signAllInputsHD(hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + throw new Error('Need HDSigner to sign input'); + } + const results = []; + for (const i of range(this.data.inputs.length)) { + try { + this.signInputHD(i, hdKeyPair, sighashTypes); + results.push(true); + } + catch (err) { + results.push(false); + } + } if (results.every(v => v === false)) { - return reject(new Error('No inputs were signed')); + throw new Error('No inputs were signed'); + } + return this; + } + signAllInputsHDAsync(hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { + return new Promise((resolve, reject) => { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + return reject(new Error('Need HDSigner to sign input')); + } + const results = []; + const promises = []; + for (const i of range(this.data.inputs.length)) { + promises.push(this.signInputHDAsync(i, hdKeyPair, sighashTypes).then(() => { + results.push(true); + }, () => { + results.push(false); + })); + } + return Promise.all(promises).then(() => { + if (results.every(v => v === false)) { + return reject(new Error('No inputs were signed')); + } + resolve(); + }); + }); + } + signInputHD(inputIndex, hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + throw new Error('Need HDSigner to sign input'); + } + const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair); + signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes)); + return this; + } + signInputHDAsync(inputIndex, hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { + return new Promise((resolve, reject) => { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + return reject(new Error('Need HDSigner to sign input')); + } + const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair); + const promises = signers.map(signer => this.signInputAsync(inputIndex, signer, sighashTypes)); + return Promise.all(promises) + .then(() => { + resolve(); + }) + .catch(reject); + }); + } + signAllInputs(keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + // TODO: Add a pubkey/pubkeyhash cache to each input + // as input information is added, then eventually + // optimize this method. + const results = []; + for (const i of range(this.data.inputs.length)) { + try { + this.signInput(i, keyPair, sighashTypes); + results.push(true); + } + catch (err) { + results.push(false); + } } - resolve(); - }); - }); - } - signInputHD( - inputIndex, - hdKeyPair, - sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], - ) { - if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { - throw new Error('Need HDSigner to sign input'); - } - const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair); - signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes)); - return this; - } - signInputHDAsync( - inputIndex, - hdKeyPair, - sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], - ) { - return new Promise((resolve, reject) => { - if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { - return reject(new Error('Need HDSigner to sign input')); - } - const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair); - const promises = signers.map(signer => - this.signInputAsync(inputIndex, signer, sighashTypes), - ); - return Promise.all(promises) - .then(() => { - resolve(); - }) - .catch(reject); - }); - } - signAllInputs( - keyPair, - sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], - ) { - if (!keyPair || !keyPair.publicKey) - throw new Error('Need Signer to sign input'); - // TODO: Add a pubkey/pubkeyhash cache to each input - // as input information is added, then eventually - // optimize this method. - const results = []; - for (const i of range(this.data.inputs.length)) { - try { - this.signInput(i, keyPair, sighashTypes); - results.push(true); - } catch (err) { - results.push(false); - } - } - if (results.every(v => v === false)) { - throw new Error('No inputs were signed'); - } - return this; - } - signAllInputsAsync( - keyPair, - sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], - ) { - return new Promise((resolve, reject) => { - if (!keyPair || !keyPair.publicKey) - return reject(new Error('Need Signer to sign input')); - // TODO: Add a pubkey/pubkeyhash cache to each input - // as input information is added, then eventually - // optimize this method. - const results = []; - const promises = []; - for (const [i] of this.data.inputs.entries()) { - promises.push( - this.signInputAsync(i, keyPair, sighashTypes).then( - () => { - results.push(true); - }, - () => { - results.push(false); - }, - ), - ); - } - return Promise.all(promises).then(() => { if (results.every(v => v === false)) { - return reject(new Error('No inputs were signed')); + throw new Error('No inputs were signed'); } - resolve(); - }); - }); - } - signInput( - inputIndex, - keyPair, - sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], - ) { - if (!keyPair || !keyPair.publicKey) - throw new Error('Need Signer to sign input'); - const { hash, sighashType } = getHashAndSighashType( - this.data.inputs, - inputIndex, - keyPair.publicKey, - this.__CACHE, - sighashTypes, - ); - const partialSig = [ - { - pubkey: keyPair.publicKey, - signature: bscript.signature.encode(keyPair.sign(hash), sighashType), - }, - ]; - this.data.updateInput(inputIndex, { partialSig }); - return this; - } - signInputAsync( - inputIndex, - keyPair, - sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], - ) { - return new Promise((resolve, reject) => { - if (!keyPair || !keyPair.publicKey) - return reject(new Error('Need Signer to sign input')); - const { hash, sighashType } = getHashAndSighashType( - this.data.inputs, - inputIndex, - keyPair.publicKey, - this.__CACHE, - sighashTypes, - ); - Promise.resolve(keyPair.sign(hash)).then(signature => { + return this; + } + signAllInputsAsync(keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { + return new Promise((resolve, reject) => { + if (!keyPair || !keyPair.publicKey) + return reject(new Error('Need Signer to sign input')); + // TODO: Add a pubkey/pubkeyhash cache to each input + // as input information is added, then eventually + // optimize this method. + const results = []; + const promises = []; + for (const [i] of this.data.inputs.entries()) { + promises.push(this.signInputAsync(i, keyPair, sighashTypes).then(() => { + results.push(true); + }, () => { + results.push(false); + })); + } + return Promise.all(promises).then(() => { + if (results.every(v => v === false)) { + return reject(new Error('No inputs were signed')); + } + resolve(); + }); + }); + } + signInput(inputIndex, keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + const { hash, sighashType } = getHashAndSighashType(this.data.inputs, inputIndex, keyPair.publicKey, this.__CACHE, sighashTypes); const partialSig = [ - { - pubkey: keyPair.publicKey, - signature: bscript.signature.encode(signature, sighashType), - }, + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(keyPair.sign(hash), sighashType), + }, ]; this.data.updateInput(inputIndex, { partialSig }); - resolve(); - }); - }); - } - toBuffer() { - return this.data.toBuffer(); - } - toHex() { - return this.data.toHex(); - } - toBase64() { - return this.data.toBase64(); - } - updateGlobal(updateData) { - this.data.updateGlobal(updateData); - return this; - } - updateInput(inputIndex, updateData) { - if (updateData.witnessUtxo) { - const { witnessUtxo } = updateData; - const script = Buffer.isBuffer(witnessUtxo.script) - ? witnessUtxo.script - : Buffer.from(witnessUtxo.script, 'hex'); - const value = Buffer.isBuffer(witnessUtxo.value) - ? witnessUtxo.value - : typeof witnessUtxo.value === 'string' - ? Buffer.from(witnessUtxo.value, 'hex') - : confidential.satoshiToConfidentialValue(witnessUtxo.value); - // if the asset is a string, by checking the first byte we can determine if - // it's an asset commitment, in this case we decode the hex string as buffer, - // or if it's an asset hash, in this case we put the unconf prefix in front of the reversed the buffer - const asset = Buffer.isBuffer(witnessUtxo.asset) - ? witnessUtxo.asset - : witnessUtxo.asset.startsWith('0a') || - witnessUtxo.asset.startsWith('0b') - ? Buffer.from(witnessUtxo.asset, 'hex') - : Buffer.concat([ - Buffer.alloc(1, 1), - bufferutils_1.reverseBuffer(Buffer.from(witnessUtxo.asset, 'hex')), - ]); - const nonce = witnessUtxo.nonce - ? Buffer.isBuffer(witnessUtxo.nonce) - ? witnessUtxo.nonce - : Buffer.from(witnessUtxo.nonce, 'hex') - : Buffer.alloc(1, 0); - const rangeProof = witnessUtxo.rangeProof - ? Buffer.isBuffer(witnessUtxo.rangeProof) - ? witnessUtxo.rangeProof - : Buffer.from(witnessUtxo.rangeProof, 'hex') - : undefined; - const surjectionProof = witnessUtxo.surjectionProof - ? Buffer.isBuffer(witnessUtxo.surjectionProof) - ? witnessUtxo.surjectionProof - : Buffer.from(witnessUtxo.surjectionProof, 'hex') - : undefined; - updateData = Object.assign(updateData, { - witnessUtxo: { - script, - value, - asset, - nonce, - rangeProof, - surjectionProof, - }, - }); - } - this.data.updateInput(inputIndex, updateData); - if (updateData.nonWitnessUtxo) { - addNonWitnessTxCache( - this.__CACHE, - this.data.inputs[inputIndex], - inputIndex, - ); - } - return this; - } - updateOutput(outputIndex, updateData) { - this.data.updateOutput(outputIndex, updateData); - return this; - } - blindOutputs(blindingDataLike, blindingPubkeys, opts) { - return this.rawBlindOutputs( - blindingDataLike, - blindingPubkeys, - undefined, - undefined, - opts, - ); - } - blindOutputsByIndex( - inputsBlindingData, - outputsBlindingPubKeys, - issuancesBlindingKeys, - opts, - ) { - const blindingPrivKeysArgs = range(this.__CACHE.__TX.ins.length).map( - inputIndex => inputsBlindingData.get(inputIndex), - ); - const blindingPrivKeysIssuancesArgs = issuancesBlindingKeys - ? range(this.__CACHE.__TX.ins.length).map(inputIndex => - issuancesBlindingKeys.get(inputIndex), - ) - : []; - const outputIndexes = []; - const blindingPublicKey = []; - for (const [outputIndex, pubBlindingKey] of outputsBlindingPubKeys) { - outputIndexes.push(outputIndex); - blindingPublicKey.push(pubBlindingKey); - } - return this.rawBlindOutputs( - blindingPrivKeysArgs, - blindingPublicKey, - blindingPrivKeysIssuancesArgs, - outputIndexes, - opts, - ); - } - addUnknownKeyValToGlobal(keyVal) { - this.data.addUnknownKeyValToGlobal(keyVal); - return this; - } - addUnknownKeyValToInput(inputIndex, keyVal) { - this.data.addUnknownKeyValToInput(inputIndex, keyVal); - return this; - } - addUnknownKeyValToOutput(outputIndex, keyVal) { - this.data.addUnknownKeyValToOutput(outputIndex, keyVal); - return this; - } - clearFinalizedInput(inputIndex) { - this.data.clearFinalizedInput(inputIndex); - return this; - } - unblindInputsToIssuanceBlindingData(issuanceBlindingPrivKeys = []) { - const pseudoBlindingDataFromIssuances = []; - let inputIndex = 0; - for (const input of this.__CACHE.__TX.ins) { - if (input.issuance) { - const isConfidentialIssuance = - issuanceBlindingPrivKeys && issuanceBlindingPrivKeys[inputIndex] - ? true - : false; - const entropy = issuance_1.generateEntropy( - { txHash: input.hash, vout: input.index }, - input.issuance.assetEntropy, - ); - const asset = issuance_1.calculateAsset(entropy); - const value = confidential - .confidentialValueToSatoshi(input.issuance.assetAmount) - .toString(10); - const assetBlindingData = { - value, - asset, - assetBlindingFactor: transaction_1.ZERO, - valueBlindingFactor: isConfidentialIssuance - ? randomBytes() - : transaction_1.ZERO, - }; - pseudoBlindingDataFromIssuances.push(assetBlindingData); - if (issuance_1.hasTokenAmount(input.issuance)) { - const token = issuance_1.calculateReissuanceToken( - entropy, - isConfidentialIssuance, - ); - const tokenValue = confidential - .confidentialValueToSatoshi(input.issuance.tokenAmount) - .toString(10); - const tokenBlindingData = { - value: tokenValue, - asset: token, - assetBlindingFactor: transaction_1.ZERO, - valueBlindingFactor: isConfidentialIssuance - ? randomBytes() - : transaction_1.ZERO, - }; - pseudoBlindingDataFromIssuances.push(tokenBlindingData); + return this; + } + signInputAsync(inputIndex, keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { + return new Promise((resolve, reject) => { + if (!keyPair || !keyPair.publicKey) + return reject(new Error('Need Signer to sign input')); + const { hash, sighashType } = getHashAndSighashType(this.data.inputs, inputIndex, keyPair.publicKey, this.__CACHE, sighashTypes); + Promise.resolve(keyPair.sign(hash)).then(signature => { + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(signature, sighashType), + }, + ]; + this.data.updateInput(inputIndex, { partialSig }); + resolve(); + }); + }); + } + toBuffer() { + return this.data.toBuffer(); + } + toHex() { + return this.data.toHex(); + } + toBase64() { + return this.data.toBase64(); + } + updateGlobal(updateData) { + this.data.updateGlobal(updateData); + return this; + } + updateInput(inputIndex, updateData) { + if (updateData.witnessUtxo) { + const { witnessUtxo } = updateData; + const script = Buffer.isBuffer(witnessUtxo.script) + ? witnessUtxo.script + : Buffer.from(witnessUtxo.script, 'hex'); + const value = Buffer.isBuffer(witnessUtxo.value) + ? witnessUtxo.value + : typeof witnessUtxo.value === 'string' + ? Buffer.from(witnessUtxo.value, 'hex') + : confidential.satoshiToConfidentialValue(witnessUtxo.value); + // if the asset is a string, by checking the first byte we can determine if + // it's an asset commitment, in this case we decode the hex string as buffer, + // or if it's an asset hash, in this case we put the unconf prefix in front of the reversed the buffer + const asset = Buffer.isBuffer(witnessUtxo.asset) + ? witnessUtxo.asset + : witnessUtxo.asset.startsWith('0a') || + witnessUtxo.asset.startsWith('0b') + ? Buffer.from(witnessUtxo.asset, 'hex') + : Buffer.concat([ + Buffer.alloc(1, 1), + bufferutils_1.reverseBuffer(Buffer.from(witnessUtxo.asset, 'hex')), + ]); + const nonce = witnessUtxo.nonce + ? Buffer.isBuffer(witnessUtxo.nonce) + ? witnessUtxo.nonce + : Buffer.from(witnessUtxo.nonce, 'hex') + : Buffer.alloc(1, 0); + const rangeProof = witnessUtxo.rangeProof + ? Buffer.isBuffer(witnessUtxo.rangeProof) + ? witnessUtxo.rangeProof + : Buffer.from(witnessUtxo.rangeProof, 'hex') + : undefined; + const surjectionProof = witnessUtxo.surjectionProof + ? Buffer.isBuffer(witnessUtxo.surjectionProof) + ? witnessUtxo.surjectionProof + : Buffer.from(witnessUtxo.surjectionProof, 'hex') + : undefined; + updateData = Object.assign(updateData, { + witnessUtxo: { + script, + value, + asset, + nonce, + rangeProof, + surjectionProof, + }, + }); + } + this.data.updateInput(inputIndex, updateData); + if (updateData.nonWitnessUtxo) { + addNonWitnessTxCache(this.__CACHE, this.data.inputs[inputIndex], inputIndex); } - } - inputIndex++; - } - return pseudoBlindingDataFromIssuances; - } - blindInputs(blindingData, issuanceBlindingPrivKeys = []) { - return __awaiter(this, void 0, void 0, function*() { - if (!issuanceBlindingPrivKeys || issuanceBlindingPrivKeys.length === 0) - return this; // skip if no issuance blind keys - function getBlindingFactors(asset) { - for (const blindData of blindingData) { - if (asset.equals(blindData.asset)) { - return blindData; - } + return this; + } + updateOutput(outputIndex, updateData) { + this.data.updateOutput(outputIndex, updateData); + return this; + } + blindOutputs(blindingDataLike, blindingPubkeys, opts) { + return this.rawBlindOutputs(blindingDataLike, blindingPubkeys, undefined, undefined, opts); + } + blindOutputsByIndex(inputsBlindingData, outputsBlindingPubKeys, issuancesBlindingKeys, opts) { + const blindingPrivKeysArgs = range(this.__CACHE.__TX.ins.length).map((inputIndex) => inputsBlindingData.get(inputIndex)); + const blindingPrivKeysIssuancesArgs = issuancesBlindingKeys + ? range(this.__CACHE.__TX.ins.length).map((inputIndex) => issuancesBlindingKeys.get(inputIndex)) + : []; + const outputIndexes = []; + const blindingPublicKey = []; + for (const [outputIndex, pubBlindingKey] of outputsBlindingPubKeys) { + outputIndexes.push(outputIndex); + blindingPublicKey.push(pubBlindingKey); } - throw new Error( - 'no blinding factors generated for pseudo issuance inputs', - ); - } - // loop over inputs and create blindingData object in case of issuance - let inputIndex = 0; - for (const input of this.__CACHE.__TX.ins) { - if (input.issuance) { - if (!issuanceBlindingPrivKeys[inputIndex]) { - // check if the user has provided blinding key + return this.rawBlindOutputs(blindingPrivKeysArgs, blindingPublicKey, blindingPrivKeysIssuancesArgs, outputIndexes, opts); + } + addUnknownKeyValToGlobal(keyVal) { + this.data.addUnknownKeyValToGlobal(keyVal); + return this; + } + addUnknownKeyValToInput(inputIndex, keyVal) { + this.data.addUnknownKeyValToInput(inputIndex, keyVal); + return this; + } + addUnknownKeyValToOutput(outputIndex, keyVal) { + this.data.addUnknownKeyValToOutput(outputIndex, keyVal); + return this; + } + clearFinalizedInput(inputIndex) { + this.data.clearFinalizedInput(inputIndex); + return this; + } + unblindInputsToIssuanceBlindingData(issuanceBlindingPrivKeys = []) { + const pseudoBlindingDataFromIssuances = []; + let inputIndex = 0; + for (const input of this.__CACHE.__TX.ins) { + if (input.issuance) { + const isConfidentialIssuance = issuanceBlindingPrivKeys && issuanceBlindingPrivKeys[inputIndex] + ? true + : false; + const entropy = issuance_1.generateEntropy({ txHash: input.hash, vout: input.index }, input.issuance.assetEntropy); + const asset = issuance_1.calculateAsset(entropy); + const value = confidential + .confidentialValueToSatoshi(input.issuance.assetAmount) + .toString(10); + const assetBlindingData = { + value, + asset, + assetBlindingFactor: transaction_1.ZERO, + valueBlindingFactor: isConfidentialIssuance ? randomBytes() : transaction_1.ZERO, + }; + pseudoBlindingDataFromIssuances.push(assetBlindingData); + if (issuance_1.hasTokenAmount(input.issuance)) { + const token = issuance_1.calculateReissuanceToken(entropy, isConfidentialIssuance); + const tokenValue = confidential + .confidentialValueToSatoshi(input.issuance.tokenAmount) + .toString(10); + const tokenBlindingData = { + value: tokenValue, + asset: token, + assetBlindingFactor: transaction_1.ZERO, + valueBlindingFactor: isConfidentialIssuance ? randomBytes() : transaction_1.ZERO, + }; + pseudoBlindingDataFromIssuances.push(tokenBlindingData); + } + } inputIndex++; - continue; - } - const entropy = issuance_1.generateEntropy( - { txHash: input.hash, vout: input.index }, - input.issuance.assetEntropy, - ); - const issuedAsset = issuance_1.calculateAsset(entropy); - const blindingFactorsAsset = getBlindingFactors(issuedAsset); - const assetCommitment = yield confidential.assetCommitment( - blindingFactorsAsset.asset, - blindingFactorsAsset.assetBlindingFactor, - ); - const valueCommitment = yield confidential.valueCommitment( - blindingFactorsAsset.value, - assetCommitment, - blindingFactorsAsset.valueBlindingFactor, - ); - const assetBlindingPrivateKey = issuanceBlindingPrivKeys[inputIndex] - ? issuanceBlindingPrivKeys[inputIndex].assetKey - : undefined; - if (!assetBlindingPrivateKey) { - throw new Error( - `missing asset blinding private key for issuance #${inputIndex}`, - ); - } - const issuanceRangeProof = yield confidential.rangeProof( - blindingFactorsAsset.value, - assetBlindingPrivateKey, - blindingFactorsAsset.asset, - blindingFactorsAsset.assetBlindingFactor, - blindingFactorsAsset.valueBlindingFactor, - valueCommitment, - Buffer.alloc(0), - '1', - 0, - 52, - ); - this.__CACHE.__TX.ins[ - inputIndex - ].issuanceRangeProof = issuanceRangeProof; - this.__CACHE.__TX.ins[ - inputIndex - ].issuance.assetAmount = valueCommitment; - if (issuance_1.hasTokenAmount(input.issuance)) { - const token = issuance_1.calculateReissuanceToken(entropy, true); - const blindingFactorsToken = getBlindingFactors(token); - const issuedTokenCommitment = yield confidential.assetCommitment( - token, - blindingFactorsToken.assetBlindingFactor, - ); - const tokenValueCommitment = yield confidential.valueCommitment( - blindingFactorsToken.value, - issuedTokenCommitment, - blindingFactorsToken.valueBlindingFactor, - ); - const inflationRangeProof = yield confidential.rangeProof( - blindingFactorsToken.value, - issuanceBlindingPrivKeys[inputIndex].tokenKey, - token, - blindingFactorsToken.assetBlindingFactor, - blindingFactorsToken.valueBlindingFactor, - tokenValueCommitment, - Buffer.alloc(0), - '1', - 0, - 52, - ); - this.__CACHE.__TX.ins[ - inputIndex - ].inflationRangeProof = inflationRangeProof; - this.__CACHE.__TX.ins[ - inputIndex - ].issuance.tokenAmount = tokenValueCommitment; - } } - inputIndex++; - } - return this; - }); - } - blindOutputsRaw(blindingData, blindingPubkeys, outputIndexes, opts) { - return __awaiter(this, void 0, void 0, function*() { - // get data (satoshis & asset) outputs to blind - const outputsData = outputIndexes.map(index => { - const output = this.__CACHE.__TX.outs[index]; - // prevent blinding the fee output - if (output.script.length === 0) - throw new Error("cant't blind the fee output"); - const value = confidential - .confidentialValueToSatoshi(output.value) - .toString(10); - return [value, output.asset.slice(1)]; - }); - // compute the outputs blinders - const outputsBlindingData = yield computeOutputsBlindingData( - blindingData, - outputsData, - ); - // use blinders to compute proofs & commitments - let indexInArray = 0; - for (const outputIndex of outputIndexes) { - const randomSeed = randomBytes(opts); - const ephemeralPrivKey = randomBytes(opts); - const outputNonce = ecpair_1.fromPrivateKey(ephemeralPrivKey).publicKey; - const outputBlindingData = outputsBlindingData[indexInArray]; - // commitments - const assetCommitment = yield confidential.assetCommitment( - outputBlindingData.asset, - outputBlindingData.assetBlindingFactor, - ); - const valueCommitment = yield confidential.valueCommitment( - outputBlindingData.value, - assetCommitment, - outputBlindingData.valueBlindingFactor, - ); - // proofs - const rangeProof = yield confidential.rangeProofWithNonceHash( - outputBlindingData.value, - blindingPubkeys[indexInArray], - ephemeralPrivKey, - outputBlindingData.asset, - outputBlindingData.assetBlindingFactor, - outputBlindingData.valueBlindingFactor, - valueCommitment, - this.__CACHE.__TX.outs[outputIndex].script, - ); - const surjectionProof = yield confidential.surjectionProof( - outputBlindingData.asset, - outputBlindingData.assetBlindingFactor, - blindingData.map(({ asset }) => asset), - blindingData.map(({ assetBlindingFactor }) => assetBlindingFactor), - randomSeed, - ); - // set commitments & proofs & nonce - this.__CACHE.__TX.outs[outputIndex].asset = assetCommitment; - this.__CACHE.__TX.outs[outputIndex].value = valueCommitment; - this.__CACHE.__TX.setOutputNonce(outputIndex, outputNonce); - this.__CACHE.__TX.setOutputRangeProof(outputIndex, rangeProof); - this.__CACHE.__TX.setOutputSurjectionProof( - outputIndex, - surjectionProof, - ); - indexInArray++; - } - return this; - }); - } - rawBlindOutputs( - blindingDataLike, - blindingPubkeys, - issuanceBlindingPrivKeys = [], - outputIndexes, - opts, - ) { - return __awaiter(this, void 0, void 0, function*() { - if (this.data.inputs.some(v => !v.nonWitnessUtxo && !v.witnessUtxo)) - throw new Error( - 'All inputs must contain a non witness utxo or a witness utxo', - ); - if (this.__CACHE.__TX.ins.length !== blindingDataLike.length) { - throw new Error( - 'blindingDataLike length does not match the number of inputs (undefined for unconfidential utxo)', - ); - } - if (!outputIndexes) { - outputIndexes = []; - // fill the outputIndexes array with all the output index (except the fee output) - this.__CACHE.__TX.outs.forEach((out, index) => { - if (out.script.length > 0) outputIndexes.push(index); + return pseudoBlindingDataFromIssuances; + } + blindInputs(blindingData, issuanceBlindingPrivKeys = []) { + return __awaiter(this, void 0, void 0, function* () { + if (!issuanceBlindingPrivKeys || issuanceBlindingPrivKeys.length === 0) + return this; // skip if no issuance blind keys + function getBlindingFactors(asset) { + for (const blindData of blindingData) { + if (asset.equals(blindData.asset)) { + return blindData; + } + } + throw new Error('no blinding factors generated for pseudo issuance inputs'); + } + // loop over inputs and create blindingData object in case of issuance + let inputIndex = 0; + for (const input of this.__CACHE.__TX.ins) { + if (input.issuance) { + if (!issuanceBlindingPrivKeys[inputIndex]) { + // check if the user has provided blinding key + inputIndex++; + continue; + } + const entropy = issuance_1.generateEntropy({ txHash: input.hash, vout: input.index }, input.issuance.assetEntropy); + const issuedAsset = issuance_1.calculateAsset(entropy); + const blindingFactorsAsset = getBlindingFactors(issuedAsset); + const assetCommitment = yield confidential.assetCommitment(blindingFactorsAsset.asset, blindingFactorsAsset.assetBlindingFactor); + const valueCommitment = yield confidential.valueCommitment(blindingFactorsAsset.value, assetCommitment, blindingFactorsAsset.valueBlindingFactor); + const assetBlindingPrivateKey = issuanceBlindingPrivKeys[inputIndex] + ? issuanceBlindingPrivKeys[inputIndex].assetKey + : undefined; + if (!assetBlindingPrivateKey) { + throw new Error(`missing asset blinding private key for issuance #${inputIndex}`); + } + const issuanceRangeProof = yield confidential.rangeProof(blindingFactorsAsset.value, assetBlindingPrivateKey, blindingFactorsAsset.asset, blindingFactorsAsset.assetBlindingFactor, blindingFactorsAsset.valueBlindingFactor, valueCommitment, Buffer.alloc(0), '1', 0, 52); + this.__CACHE.__TX.ins[inputIndex].issuanceRangeProof = issuanceRangeProof; + this.__CACHE.__TX.ins[inputIndex].issuance.assetAmount = valueCommitment; + if (issuance_1.hasTokenAmount(input.issuance)) { + const token = issuance_1.calculateReissuanceToken(entropy, true); + const blindingFactorsToken = getBlindingFactors(token); + const issuedTokenCommitment = yield confidential.assetCommitment(token, blindingFactorsToken.assetBlindingFactor); + const tokenValueCommitment = yield confidential.valueCommitment(blindingFactorsToken.value, issuedTokenCommitment, blindingFactorsToken.valueBlindingFactor); + const inflationRangeProof = yield confidential.rangeProof(blindingFactorsToken.value, issuanceBlindingPrivKeys[inputIndex].tokenKey, token, blindingFactorsToken.assetBlindingFactor, blindingFactorsToken.valueBlindingFactor, tokenValueCommitment, Buffer.alloc(0), '1', 0, 52); + this.__CACHE.__TX.ins[inputIndex].inflationRangeProof = inflationRangeProof; + this.__CACHE.__TX.ins[inputIndex].issuance.tokenAmount = tokenValueCommitment; + } + } + inputIndex++; + } + return this; }); - } - if (outputIndexes.length !== blindingPubkeys.length) - throw new Error( - 'not enough blinding public keys to blind the requested outputs', - ); - const witnesses = this.data.inputs.map((input, index) => { - if (input.nonWitnessUtxo) { - const prevTx = nonWitnessUtxoTxFromCache(this.__CACHE, input, index); - const prevoutIndex = this.__CACHE.__TX.ins[index].index; - return prevTx.outs[prevoutIndex]; - } - if (input.witnessUtxo) { - return input.witnessUtxo; - } - throw new Error('input data needs witness utxo or nonwitness utxo'); - }); - const inputsBlindingData = yield Promise.all( - blindingDataLike.map((data, i) => toBlindingData(data, witnesses[i])), - ); - const pseudoInputsBlindingData = this.unblindInputsToIssuanceBlindingData( - issuanceBlindingPrivKeys, - ); - const totalBlindingData = inputsBlindingData.concat( - pseudoInputsBlindingData, - ); - yield this.blindOutputsRaw( - totalBlindingData, - blindingPubkeys, - outputIndexes, - opts, - ); - yield this.blindInputs(totalBlindingData, issuanceBlindingPrivKeys); - this.__CACHE.__FEE = undefined; - this.__CACHE.__FEE_RATE = undefined; - this.__CACHE.__EXTRACTED_TX = undefined; - return this; - }); - } + } + blindOutputsRaw(blindingData, blindingPubkeys, outputIndexes, opts) { + return __awaiter(this, void 0, void 0, function* () { + // get data (satoshis & asset) outputs to blind + const outputsData = outputIndexes.map((index) => { + const output = this.__CACHE.__TX.outs[index]; + // prevent blinding the fee output + if (output.script.length === 0) + throw new Error("cant't blind the fee output"); + const value = confidential + .confidentialValueToSatoshi(output.value) + .toString(10); + return [value, output.asset.slice(1)]; + }); + // compute the outputs blinders + const outputsBlindingData = yield computeOutputsBlindingData(blindingData, outputsData); + // use blinders to compute proofs & commitments + let indexInArray = 0; + for (const outputIndex of outputIndexes) { + const randomSeed = randomBytes(opts); + const ephemeralPrivKey = randomBytes(opts); + const outputNonce = ecpair_1.fromPrivateKey(ephemeralPrivKey).publicKey; + const outputBlindingData = outputsBlindingData[indexInArray]; + // commitments + const assetCommitment = yield confidential.assetCommitment(outputBlindingData.asset, outputBlindingData.assetBlindingFactor); + const valueCommitment = yield confidential.valueCommitment(outputBlindingData.value, assetCommitment, outputBlindingData.valueBlindingFactor); + // proofs + const rangeProof = yield confidential.rangeProofWithNonceHash(outputBlindingData.value, blindingPubkeys[indexInArray], ephemeralPrivKey, outputBlindingData.asset, outputBlindingData.assetBlindingFactor, outputBlindingData.valueBlindingFactor, valueCommitment, this.__CACHE.__TX.outs[outputIndex].script); + const surjectionProof = yield confidential.surjectionProof(outputBlindingData.asset, outputBlindingData.assetBlindingFactor, blindingData.map(({ asset }) => asset), blindingData.map(({ assetBlindingFactor }) => assetBlindingFactor), randomSeed); + // set commitments & proofs & nonce + this.__CACHE.__TX.outs[outputIndex].asset = assetCommitment; + this.__CACHE.__TX.outs[outputIndex].value = valueCommitment; + this.__CACHE.__TX.setOutputNonce(outputIndex, outputNonce); + this.__CACHE.__TX.setOutputRangeProof(outputIndex, rangeProof); + this.__CACHE.__TX.setOutputSurjectionProof(outputIndex, surjectionProof); + indexInArray++; + } + return this; + }); + } + rawBlindOutputs(blindingDataLike, blindingPubkeys, issuanceBlindingPrivKeys = [], outputIndexes, opts) { + return __awaiter(this, void 0, void 0, function* () { + if (this.data.inputs.some((v) => !v.nonWitnessUtxo && !v.witnessUtxo)) + throw new Error('All inputs must contain a non witness utxo or a witness utxo'); + if (this.__CACHE.__TX.ins.length !== blindingDataLike.length) { + throw new Error('blindingDataLike length does not match the number of inputs (undefined for unconfidential utxo)'); + } + if (!outputIndexes) { + outputIndexes = []; + // fill the outputIndexes array with all the output index (except the fee output) + this.__CACHE.__TX.outs.forEach((out, index) => { + if (out.script.length > 0) + outputIndexes.push(index); + }); + } + if (outputIndexes.length !== blindingPubkeys.length) + throw new Error('not enough blinding public keys to blind the requested outputs'); + const witnesses = this.data.inputs.map((input, index) => { + if (input.nonWitnessUtxo) { + const prevTx = nonWitnessUtxoTxFromCache(this.__CACHE, input, index); + const prevoutIndex = this.__CACHE.__TX.ins[index].index; + return prevTx.outs[prevoutIndex]; + } + if (input.witnessUtxo) { + return input.witnessUtxo; + } + throw new Error('input data needs witness utxo or nonwitness utxo'); + }); + const inputsBlindingData = yield Promise.all(blindingDataLike.map((data, i) => toBlindingData(data, witnesses[i]))); + const pseudoInputsBlindingData = this.unblindInputsToIssuanceBlindingData(issuanceBlindingPrivKeys); + const totalBlindingData = inputsBlindingData.concat(pseudoInputsBlindingData); + yield this.blindOutputsRaw(totalBlindingData, blindingPubkeys, outputIndexes, opts); + yield this.blindInputs(totalBlindingData, issuanceBlindingPrivKeys); + this.__CACHE.__FEE = undefined; + this.__CACHE.__FEE_RATE = undefined; + this.__CACHE.__EXTRACTED_TX = undefined; + return this; + }); + } } exports.Psbt = Psbt; /** @@ -1005,113 +751,116 @@ exports.Psbt = Psbt; * It takes the "transaction buffer" portion of the psbt buffer and returns a * Transaction (From the bip174 library) interface. */ -const transactionFromBuffer = buffer => new PsbtTransaction(buffer); +const transactionFromBuffer = (buffer) => new PsbtTransaction(buffer); /** * This class implements the Transaction interface from bip174 library. * It contains a liquidjs-lib Transaction object. */ class PsbtTransaction { - constructor(buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) { - this.tx = transaction_1.Transaction.fromBuffer(buffer); - checkTxEmpty(this.tx); - Object.defineProperty(this, 'tx', { - enumerable: false, - writable: true, - }); - } - getInputOutputCounts() { - return { - inputCount: this.tx.ins.length, - outputCount: this.tx.outs.length, - }; - } - addInput(input) { - if ( - input.hash === undefined || - input.index === undefined || - (!Buffer.isBuffer(input.hash) && typeof input.hash !== 'string') || - typeof input.index !== 'number' - ) { - throw new Error('Error adding input.'); - } - const hash = - typeof input.hash === 'string' - ? bufferutils_1.reverseBuffer(Buffer.from(input.hash, 'hex')) - : input.hash; - this.tx.addInput(hash, input.index, input.sequence); - } - addOutput(output) { - if ( - output.script === undefined || - (!Buffer.isBuffer(output.script) && typeof output.script !== 'string') || - output.value === undefined || - (!Buffer.isBuffer(output.value) && typeof output.value !== 'number') || - output.asset === undefined || - (!Buffer.isBuffer(output.asset) && typeof output.asset !== 'string') - ) { - throw new Error('Error adding output.'); - } - const nonce = Buffer.alloc(1, 0); - const script = Buffer.isBuffer(output.script) - ? output.script - : Buffer.from(output.script, 'hex'); - const value = Buffer.isBuffer(output.value) - ? output.value - : confidential.satoshiToConfidentialValue(output.value); - const asset = Buffer.isBuffer(output.asset) - ? output.asset - : Buffer.concat([ - Buffer.alloc(1, 1), - bufferutils_1.reverseBuffer(Buffer.from(output.asset, 'hex')), - ]); - this.tx.addOutput(script, value, asset, nonce); - } - toBuffer() { - return this.tx.toBuffer(); - } + constructor(buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) { + this.tx = transaction_1.Transaction.fromBuffer(buffer); + checkTxEmpty(this.tx); + Object.defineProperty(this, 'tx', { + enumerable: false, + writable: true, + }); + } + getInputOutputCounts() { + return { + inputCount: this.tx.ins.length, + outputCount: this.tx.outs.length, + }; + } + addInput(input) { + if (input.hash === undefined || + input.index === undefined || + (!Buffer.isBuffer(input.hash) && + typeof input.hash !== 'string') || + typeof input.index !== 'number') { + throw new Error('Error adding input.'); + } + const hash = typeof input.hash === 'string' + ? bufferutils_1.reverseBuffer(Buffer.from(input.hash, 'hex')) + : input.hash; + this.tx.addInput(hash, input.index, input.sequence); + } + addOutput(output) { + if (output.script === undefined || + (!Buffer.isBuffer(output.script) && + typeof output.script !== 'string') || + output.value === undefined || + (!Buffer.isBuffer(output.value) && + typeof output.value !== 'number') || + output.asset === undefined || + (!Buffer.isBuffer(output.asset) && + typeof output.asset !== 'string')) { + throw new Error('Error adding output.'); + } + const nonce = Buffer.alloc(1, 0); + const script = Buffer.isBuffer(output.script) + ? output.script + : Buffer.from(output.script, 'hex'); + const value = Buffer.isBuffer(output.value) + ? output.value + : confidential.satoshiToConfidentialValue(output.value); + const asset = Buffer.isBuffer(output.asset) + ? output.asset + : Buffer.concat([ + Buffer.alloc(1, 1), + bufferutils_1.reverseBuffer(Buffer.from(output.asset, 'hex')), + ]); + this.tx.addOutput(script, value, asset, nonce); + } + toBuffer() { + return this.tx.toBuffer(); + } } function canFinalize(input, script, scriptType) { - switch (scriptType) { - case 'pubkey': - case 'pubkeyhash': - case 'witnesspubkeyhash': - return hasSigs(1, input.partialSig); - case 'multisig': - const p2ms = payments.p2ms({ output: script }); - return hasSigs(p2ms.m, input.partialSig, p2ms.pubkeys); - default: - return false; - } + switch (scriptType) { + case 'pubkey': + case 'pubkeyhash': + case 'witnesspubkeyhash': + return hasSigs(1, input.partialSig); + case 'multisig': + const p2ms = payments.p2ms({ output: script }); + return hasSigs(p2ms.m, input.partialSig, p2ms.pubkeys); + default: + return false; + } } function hasSigs(neededSigs, partialSig, pubkeys) { - if (!partialSig) return false; - let sigs; - if (pubkeys) { - sigs = pubkeys - .map(pkey => { - const pubkey = ecpair_1.fromPublicKey(pkey, { compressed: true }) - .publicKey; - return partialSig.find(pSig => pSig.pubkey.equals(pubkey)); - }) - .filter(v => !!v); - } else { - sigs = partialSig; - } - if (sigs.length > neededSigs) throw new Error('Too many signatures'); - return sigs.length === neededSigs; + if (!partialSig) + return false; + let sigs; + if (pubkeys) { + sigs = pubkeys + .map(pkey => { + const pubkey = ecpair_1.fromPublicKey(pkey, { compressed: true }) + .publicKey; + return partialSig.find(pSig => pSig.pubkey.equals(pubkey)); + }) + .filter(v => !!v); + } + else { + sigs = partialSig; + } + if (sigs.length > neededSigs) + throw new Error('Too many signatures'); + return sigs.length === neededSigs; } function isFinalized(input) { - return !!input.finalScriptSig || !!input.finalScriptWitness; + return !!input.finalScriptSig || !!input.finalScriptWitness; } function isPaymentFactory(payment) { - return script => { - try { - payment({ output: script }); - return true; - } catch (err) { - return false; - } - }; + return (script) => { + try { + payment({ output: script }); + return true; + } + catch (err) { + return false; + } + }; } const isP2MS = isPaymentFactory(payments.p2ms); const isP2PK = isPaymentFactory(payments.p2pk); @@ -1119,561 +868,514 @@ const isP2PKH = isPaymentFactory(payments.p2pkh); const isP2WPKH = isPaymentFactory(payments.p2wpkh); const isP2WSHScript = isPaymentFactory(payments.p2wsh); function check32Bit(num) { - if ( - typeof num !== 'number' || - num !== Math.floor(num) || - num > 0xffffffff || - num < 0 - ) { - throw new Error('Invalid 32 bit integer'); - } + if (typeof num !== 'number' || + num !== Math.floor(num) || + num > 0xffffffff || + num < 0) { + throw new Error('Invalid 32 bit integer'); + } } function checkFees(psbt, cache, opts) { - const feeRate = cache.__FEE_RATE || psbt.getFeeRate(); - const vsize = cache.__EXTRACTED_TX.virtualSize(); - const satoshis = feeRate * vsize; - if (feeRate >= opts.maximumFeeRate) { - throw new Error( - `Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` + - `fees, which is ${feeRate} satoshi per byte for a transaction ` + - `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` + - `byte). Use setMaximumFeeRate method to raise your threshold, or ` + - `pass true to the first arg of extractTransaction.`, - ); - } + const feeRate = cache.__FEE_RATE || psbt.getFeeRate(); + const vsize = cache.__EXTRACTED_TX.virtualSize(); + const satoshis = feeRate * vsize; + if (feeRate >= opts.maximumFeeRate) { + throw new Error(`Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` + + `fees, which is ${feeRate} satoshi per byte for a transaction ` + + `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` + + `byte). Use setMaximumFeeRate method to raise your threshold, or ` + + `pass true to the first arg of extractTransaction.`); + } } function checkInputsForPartialSig(inputs, action) { - inputs.forEach(input => { - let throws = false; - let pSigs = []; - if ((input.partialSig || []).length === 0) { - if (!input.finalScriptSig && !input.finalScriptWitness) return; - pSigs = getPsigsFromInputFinalScripts(input); - } else { - pSigs = input.partialSig; - } - pSigs.forEach(pSig => { - const { hashType } = bscript.signature.decode(pSig.signature); - const whitelist = []; - const isAnyoneCanPay = - hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY; - if (isAnyoneCanPay) whitelist.push('addInput'); - const hashMod = hashType & 0x1f; - switch (hashMod) { - case transaction_1.Transaction.SIGHASH_ALL: - break; - case transaction_1.Transaction.SIGHASH_SINGLE: - case transaction_1.Transaction.SIGHASH_NONE: - whitelist.push('addOutput'); - whitelist.push('setInputSequence'); - break; - } - if (whitelist.indexOf(action) === -1) { - throws = true; - } + inputs.forEach(input => { + let throws = false; + let pSigs = []; + if ((input.partialSig || []).length === 0) { + if (!input.finalScriptSig && !input.finalScriptWitness) + return; + pSigs = getPsigsFromInputFinalScripts(input); + } + else { + pSigs = input.partialSig; + } + pSigs.forEach(pSig => { + const { hashType } = bscript.signature.decode(pSig.signature); + const whitelist = []; + const isAnyoneCanPay = hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY; + if (isAnyoneCanPay) + whitelist.push('addInput'); + const hashMod = hashType & 0x1f; + switch (hashMod) { + case transaction_1.Transaction.SIGHASH_ALL: + break; + case transaction_1.Transaction.SIGHASH_SINGLE: + case transaction_1.Transaction.SIGHASH_NONE: + whitelist.push('addOutput'); + whitelist.push('setInputSequence'); + break; + } + if (whitelist.indexOf(action) === -1) { + throws = true; + } + }); + if (throws) { + throw new Error('Can not modify transaction, signatures exist.'); + } }); - if (throws) { - throw new Error('Can not modify transaction, signatures exist.'); - } - }); } function checkPartialSigSighashes(input) { - if (!input.sighashType || !input.partialSig) return; - const { partialSig, sighashType } = input; - partialSig.forEach(pSig => { - const { hashType } = bscript.signature.decode(pSig.signature); - if (sighashType !== hashType) { - throw new Error('Signature sighash does not match input sighash type'); - } - }); + if (!input.sighashType || !input.partialSig) + return; + const { partialSig, sighashType } = input; + partialSig.forEach(pSig => { + const { hashType } = bscript.signature.decode(pSig.signature); + if (sighashType !== hashType) { + throw new Error('Signature sighash does not match input sighash type'); + } + }); } function checkScriptForPubkey(pubkey, script, action) { - const pubkeyHash = crypto_1.hash160(pubkey); - const decompiled = bscript.decompile(script); - if (decompiled === null) throw new Error('Unknown script error'); - const hasKey = decompiled.some(element => { - if (typeof element === 'number') return false; - return element.equals(pubkey) || element.equals(pubkeyHash); - }); - if (!hasKey) { - throw new Error( - `Can not ${action} for this input with the key ${pubkey.toString('hex')}`, - ); - } + const pubkeyHash = crypto_1.hash160(pubkey); + const decompiled = bscript.decompile(script); + if (decompiled === null) + throw new Error('Unknown script error'); + const hasKey = decompiled.some(element => { + if (typeof element === 'number') + return false; + return element.equals(pubkey) || element.equals(pubkeyHash); + }); + if (!hasKey) { + throw new Error(`Can not ${action} for this input with the key ${pubkey.toString('hex')}`); + } } function checkTxEmpty(tx) { - const isEmpty = tx.ins.every( - input => input.script && input.script.length === 0, - ); - if (!isEmpty) { - throw new Error('Format Error: Transaction ScriptSigs are not empty'); - } - // if (tx.flag === 1 && tx.witnessIn.length > 0) { - // throw new Error('Format Error: Transaction WitnessScriptSigs are not empty'); - // } + const isEmpty = tx.ins.every(input => input.script && input.script.length === 0); + if (!isEmpty) { + throw new Error('Format Error: Transaction ScriptSigs are not empty'); + } + // if (tx.flag === 1 && tx.witnessIn.length > 0) { + // throw new Error('Format Error: Transaction WitnessScriptSigs are not empty'); + // } } function checkTxForDupeIns(tx, cache) { - tx.ins.forEach(input => { - checkTxInputCache(cache, input); - }); + tx.ins.forEach(input => { + checkTxInputCache(cache, input); + }); } function checkTxInputCache(cache, input) { - const key = - bufferutils_1.reverseBuffer(Buffer.from(input.hash)).toString('hex') + - ':' + - input.index; - if (cache.__TX_IN_CACHE[key]) throw new Error('Duplicate input detected.'); - cache.__TX_IN_CACHE[key] = 1; + const key = bufferutils_1.reverseBuffer(Buffer.from(input.hash)).toString('hex') + ':' + input.index; + if (cache.__TX_IN_CACHE[key]) + throw new Error('Duplicate input detected.'); + cache.__TX_IN_CACHE[key] = 1; } function scriptCheckerFactory(payment, paymentScriptName) { - return (inputIndex, scriptPubKey, redeemScript) => { - const redeemScriptOutput = payment({ - redeem: { output: redeemScript }, - }).output; - if (!scriptPubKey.equals(redeemScriptOutput)) { - throw new Error( - `${paymentScriptName} for input #${inputIndex} doesn't match the scriptPubKey in the prevout`, - ); - } - }; + return (inputIndex, scriptPubKey, redeemScript) => { + const redeemScriptOutput = payment({ + redeem: { output: redeemScript }, + }).output; + if (!scriptPubKey.equals(redeemScriptOutput)) { + throw new Error(`${paymentScriptName} for input #${inputIndex} doesn't match the scriptPubKey in the prevout`); + } + }; } const checkRedeemScript = scriptCheckerFactory(payments.p2sh, 'Redeem script'); -const checkWitnessScript = scriptCheckerFactory( - payments.p2wsh, - 'Witness script', -); +const checkWitnessScript = scriptCheckerFactory(payments.p2wsh, 'Witness script'); function getTxCacheValue(key, name, inputs, c) { - if (!inputs.every(isFinalized)) - throw new Error(`PSBT must be finalized to calculate ${name}`); - if (key === '__FEE_RATE' && c.__FEE_RATE) return c.__FEE_RATE; - if (key === '__FEE' && c.__FEE) return c.__FEE; - let tx; - let mustFinalize = true; - if (c.__EXTRACTED_TX) { - tx = c.__EXTRACTED_TX; - mustFinalize = false; - } else { - tx = c.__TX.clone(); - } - inputFinalizeGetAmts(inputs, tx, c, mustFinalize); - if (key === '__FEE_RATE') return c.__FEE_RATE; - else if (key === '__FEE') return c.__FEE; + if (!inputs.every(isFinalized)) + throw new Error(`PSBT must be finalized to calculate ${name}`); + if (key === '__FEE_RATE' && c.__FEE_RATE) + return c.__FEE_RATE; + if (key === '__FEE' && c.__FEE) + return c.__FEE; + let tx; + let mustFinalize = true; + if (c.__EXTRACTED_TX) { + tx = c.__EXTRACTED_TX; + mustFinalize = false; + } + else { + tx = c.__TX.clone(); + } + inputFinalizeGetAmts(inputs, tx, c, mustFinalize); + if (key === '__FEE_RATE') + return c.__FEE_RATE; + else if (key === '__FEE') + return c.__FEE; } -function getFinalScripts( - script, - scriptType, - partialSig, - isSegwit, - isP2SH, - isP2WSH, -) { - let finalScriptSig; - let finalScriptWitness; - // Wow, the payments API is very handy - const payment = getPayment(script, scriptType, partialSig); - const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment }); - const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment }); - if (isSegwit) { - if (p2wsh) { - finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness); - } else { - finalScriptWitness = witnessStackToScriptWitness(payment.witness); - } - if (p2sh) { - finalScriptSig = p2sh.input; - } - } else { - if (p2sh) { - finalScriptSig = p2sh.input; - } else { - finalScriptSig = payment.input; - } - } - return { - finalScriptSig, - finalScriptWitness, - }; +function getFinalScripts(script, scriptType, partialSig, isSegwit, isP2SH, isP2WSH) { + let finalScriptSig; + let finalScriptWitness; + // Wow, the payments API is very handy + const payment = getPayment(script, scriptType, partialSig); + const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment }); + const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment }); + if (isSegwit) { + if (p2wsh) { + finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness); + } + else { + finalScriptWitness = witnessStackToScriptWitness(payment.witness); + } + if (p2sh) { + finalScriptSig = p2sh.input; + } + } + else { + if (p2sh) { + finalScriptSig = p2sh.input; + } + else { + finalScriptSig = payment.input; + } + } + return { + finalScriptSig, + finalScriptWitness, + }; } -function getHashAndSighashType( - inputs, - inputIndex, - pubkey, - cache, - sighashTypes, -) { - const input = utils_1.checkForInput(inputs, inputIndex); - const { hash, sighashType, script } = getHashForSig( - inputIndex, - input, - cache, - sighashTypes, - ); - checkScriptForPubkey(pubkey, script, 'sign'); - return { - hash, - sighashType, - }; +function getHashAndSighashType(inputs, inputIndex, pubkey, cache, sighashTypes) { + const input = utils_1.checkForInput(inputs, inputIndex); + const { hash, sighashType, script } = getHashForSig(inputIndex, input, cache, sighashTypes); + checkScriptForPubkey(pubkey, script, 'sign'); + return { + hash, + sighashType, + }; } function getHashForSig(inputIndex, input, cache, sighashTypes) { - const unsignedTx = cache.__TX; - const sighashType = - input.sighashType || transaction_1.Transaction.SIGHASH_ALL; - if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { - const str = sighashTypeToString(sighashType); - throw new Error( - `Sighash type is not allowed. Retry the sign method passing the ` + - `sighashTypes array of whitelisted types. Sighash type: ${str}`, - ); - } - let hash; - let script; - if (input.nonWitnessUtxo) { - const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( - cache, - input, - inputIndex, - ); - const prevoutHash = unsignedTx.ins[inputIndex].hash; - const utxoHash = nonWitnessUtxoTx.getHash(); - // If a non-witness UTXO is provided, its hash must match the hash specified in the prevout - if (!prevoutHash.equals(utxoHash)) { - throw new Error( - `Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`, - ); - } - const prevoutIndex = unsignedTx.ins[inputIndex].index; - const prevout = nonWitnessUtxoTx.outs[prevoutIndex]; - if (input.redeemScript) { - // If a redeemScript is provided, the scriptPubKey must be for that redeemScript - checkRedeemScript(inputIndex, prevout.script, input.redeemScript); - script = input.redeemScript; - } else { - script = prevout.script; - } - if (isP2WSHScript(script)) { - if (!input.witnessScript) - throw new Error('Segwit input needs witnessScript if not P2WPKH'); - checkWitnessScript(inputIndex, script, input.witnessScript); - hash = unsignedTx.hashForWitnessV0( - inputIndex, - input.witnessScript, - prevout.value, - sighashType, - ); - script = input.witnessScript; - } else if (isP2WPKH(script)) { - // P2WPKH uses the P2PKH template for prevoutScript when signing - const signingScript = payments.p2pkh({ hash: script.slice(2) }).output; - hash = unsignedTx.hashForWitnessV0( - inputIndex, - signingScript, - prevout.value, - sighashType, - ); - } else { - hash = unsignedTx.hashForSignature(inputIndex, script, sighashType); - } - } else if (input.witnessUtxo) { - let _script; // so we don't shadow the `let script` above - if (input.redeemScript) { - // If a redeemScript is provided, the scriptPubKey must be for that redeemScript - checkRedeemScript( - inputIndex, - input.witnessUtxo.script, - input.redeemScript, - ); - _script = input.redeemScript; - } else { - _script = input.witnessUtxo.script; - } - if (isP2WPKH(_script)) { - // P2WPKH uses the P2PKH template for prevoutScript when signing - const signingScript = payments.p2pkh({ hash: _script.slice(2) }).output; - hash = unsignedTx.hashForWitnessV0( - inputIndex, - signingScript, - input.witnessUtxo.value, - sighashType, - ); - script = _script; - } else if (isP2WSHScript(_script)) { - if (!input.witnessScript) - throw new Error('Segwit input needs witnessScript if not P2WPKH'); - checkWitnessScript(inputIndex, _script, input.witnessScript); - hash = unsignedTx.hashForWitnessV0( - inputIndex, - input.witnessScript, - input.witnessUtxo.value, + const unsignedTx = cache.__TX; + const sighashType = input.sighashType || transaction_1.Transaction.SIGHASH_ALL; + if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { + const str = sighashTypeToString(sighashType); + throw new Error(`Sighash type is not allowed. Retry the sign method passing the ` + + `sighashTypes array of whitelisted types. Sighash type: ${str}`); + } + let hash; + let script; + if (input.nonWitnessUtxo) { + const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex); + const prevoutHash = unsignedTx.ins[inputIndex].hash; + const utxoHash = nonWitnessUtxoTx.getHash(); + // If a non-witness UTXO is provided, its hash must match the hash specified in the prevout + if (!prevoutHash.equals(utxoHash)) { + throw new Error(`Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`); + } + const prevoutIndex = unsignedTx.ins[inputIndex].index; + const prevout = nonWitnessUtxoTx.outs[prevoutIndex]; + if (input.redeemScript) { + // If a redeemScript is provided, the scriptPubKey must be for that redeemScript + checkRedeemScript(inputIndex, prevout.script, input.redeemScript); + script = input.redeemScript; + } + else { + script = prevout.script; + } + if (isP2WSHScript(script)) { + if (!input.witnessScript) + throw new Error('Segwit input needs witnessScript if not P2WPKH'); + checkWitnessScript(inputIndex, script, input.witnessScript); + hash = unsignedTx.hashForWitnessV0(inputIndex, input.witnessScript, prevout.value, sighashType); + script = input.witnessScript; + } + else if (isP2WPKH(script)) { + // P2WPKH uses the P2PKH template for prevoutScript when signing + const signingScript = payments.p2pkh({ hash: script.slice(2) }).output; + hash = unsignedTx.hashForWitnessV0(inputIndex, signingScript, prevout.value, sighashType); + } + else { + hash = unsignedTx.hashForSignature(inputIndex, script, sighashType); + } + } + else if (input.witnessUtxo) { + let _script; // so we don't shadow the `let script` above + if (input.redeemScript) { + // If a redeemScript is provided, the scriptPubKey must be for that redeemScript + checkRedeemScript(inputIndex, input.witnessUtxo.script, input.redeemScript); + _script = input.redeemScript; + } + else { + _script = input.witnessUtxo.script; + } + if (isP2WPKH(_script)) { + // P2WPKH uses the P2PKH template for prevoutScript when signing + const signingScript = payments.p2pkh({ hash: _script.slice(2) }).output; + hash = unsignedTx.hashForWitnessV0(inputIndex, signingScript, input.witnessUtxo.value, sighashType); + script = _script; + } + else if (isP2WSHScript(_script)) { + if (!input.witnessScript) + throw new Error('Segwit input needs witnessScript if not P2WPKH'); + checkWitnessScript(inputIndex, _script, input.witnessScript); + hash = unsignedTx.hashForWitnessV0(inputIndex, input.witnessScript, input.witnessUtxo.value, sighashType); + // want to make sure the script we return is the actual meaningful script + script = input.witnessScript; + } + else { + throw new Error(`Input #${inputIndex} has witnessUtxo but non-segwit script: ` + + `${_script.toString('hex')}`); + } + } + else { + throw new Error('Need a Utxo input item for signing'); + } + return { + script, sighashType, - ); - // want to make sure the script we return is the actual meaningful script - script = input.witnessScript; - } else { - throw new Error( - `Input #${inputIndex} has witnessUtxo but non-segwit script: ` + - `${_script.toString('hex')}`, - ); - } - } else { - throw new Error('Need a Utxo input item for signing'); - } - return { - script, - sighashType, - hash, - }; + hash, + }; } function getPayment(script, scriptType, partialSig) { - let payment; - switch (scriptType) { - case 'multisig': - const sigs = getSortedSigs(script, partialSig); - payment = payments.p2ms({ - output: script, - signatures: sigs, - }); - break; - case 'pubkey': - payment = payments.p2pk({ - output: script, - signature: partialSig[0].signature, - }); - break; - case 'pubkeyhash': - payment = payments.p2pkh({ - output: script, - pubkey: partialSig[0].pubkey, - signature: partialSig[0].signature, - }); - break; - case 'witnesspubkeyhash': - payment = payments.p2wpkh({ - output: script, - pubkey: partialSig[0].pubkey, - signature: partialSig[0].signature, - }); - break; - } - return payment; + let payment; + switch (scriptType) { + case 'multisig': + const sigs = getSortedSigs(script, partialSig); + payment = payments.p2ms({ + output: script, + signatures: sigs, + }); + break; + case 'pubkey': + payment = payments.p2pk({ + output: script, + signature: partialSig[0].signature, + }); + break; + case 'pubkeyhash': + payment = payments.p2pkh({ + output: script, + pubkey: partialSig[0].pubkey, + signature: partialSig[0].signature, + }); + break; + case 'witnesspubkeyhash': + payment = payments.p2wpkh({ + output: script, + pubkey: partialSig[0].pubkey, + signature: partialSig[0].signature, + }); + break; + } + return payment; } function getPsigsFromInputFinalScripts(input) { - const scriptItems = !input.finalScriptSig - ? [] - : bscript.decompile(input.finalScriptSig) || []; - const witnessItems = !input.finalScriptWitness - ? [] - : bscript.decompile(input.finalScriptWitness) || []; - return scriptItems - .concat(witnessItems) - .filter(item => { - return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); + const scriptItems = !input.finalScriptSig + ? [] + : bscript.decompile(input.finalScriptSig) || []; + const witnessItems = !input.finalScriptWitness + ? [] + : bscript.decompile(input.finalScriptWitness) || []; + return scriptItems + .concat(witnessItems) + .filter(item => { + return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); }) - .map(sig => ({ signature: sig })); + .map(sig => ({ signature: sig })); } function getScriptFromInput(inputIndex, input, cache) { - const unsignedTx = cache.__TX; - const res = { - script: null, - isSegwit: false, - isP2SH: false, - isP2WSH: false, - }; - res.isP2SH = !!input.redeemScript; - res.isP2WSH = !!input.witnessScript; - if (input.witnessScript) { - res.script = input.witnessScript; - } else if (input.redeemScript) { - res.script = input.redeemScript; - } else { - if (input.nonWitnessUtxo) { - const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( - cache, - input, - inputIndex, - ); - const prevoutIndex = unsignedTx.ins[inputIndex].index; - res.script = nonWitnessUtxoTx.outs[prevoutIndex].script; - } else if (input.witnessUtxo) { - res.script = input.witnessUtxo.script; - } - } - if (input.witnessScript || isP2WPKH(res.script)) { - res.isSegwit = true; - } - return res; + const unsignedTx = cache.__TX; + const res = { + script: null, + isSegwit: false, + isP2SH: false, + isP2WSH: false, + }; + res.isP2SH = !!input.redeemScript; + res.isP2WSH = !!input.witnessScript; + if (input.witnessScript) { + res.script = input.witnessScript; + } + else if (input.redeemScript) { + res.script = input.redeemScript; + } + else { + if (input.nonWitnessUtxo) { + const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex); + const prevoutIndex = unsignedTx.ins[inputIndex].index; + res.script = nonWitnessUtxoTx.outs[prevoutIndex].script; + } + else if (input.witnessUtxo) { + res.script = input.witnessUtxo.script; + } + } + if (input.witnessScript || isP2WPKH(res.script)) { + res.isSegwit = true; + } + return res; } function getSignersFromHD(inputIndex, inputs, hdKeyPair) { - const input = utils_1.checkForInput(inputs, inputIndex); - if (!input.bip32Derivation || input.bip32Derivation.length === 0) { - throw new Error('Need bip32Derivation to sign with HD'); - } - const myDerivations = input.bip32Derivation - .map(bipDv => { - if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) { - return bipDv; - } else { - return; - } + const input = utils_1.checkForInput(inputs, inputIndex); + if (!input.bip32Derivation || input.bip32Derivation.length === 0) { + throw new Error('Need bip32Derivation to sign with HD'); + } + const myDerivations = input.bip32Derivation + .map(bipDv => { + if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) { + return bipDv; + } + else { + return; + } }) - .filter(v => !!v); - if (myDerivations.length === 0) { - throw new Error( - 'Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint', - ); - } - const signers = myDerivations.map(bipDv => { - const node = hdKeyPair.derivePath(bipDv.path); - if (!bipDv.pubkey.equals(node.publicKey)) { - throw new Error('pubkey did not match bip32Derivation'); - } - return node; - }); - return signers; + .filter(v => !!v); + if (myDerivations.length === 0) { + throw new Error('Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint'); + } + const signers = myDerivations.map(bipDv => { + const node = hdKeyPair.derivePath(bipDv.path); + if (!bipDv.pubkey.equals(node.publicKey)) { + throw new Error('pubkey did not match bip32Derivation'); + } + return node; + }); + return signers; } function getSortedSigs(script, partialSig) { - const p2ms = payments.p2ms({ output: script }); - // for each pubkey in order of p2ms script - return p2ms.pubkeys - .map(pk => { - // filter partialSig array by pubkey being equal - return ( - partialSig.filter(ps => { - return ps.pubkey.equals(pk); - })[0] || {} - ).signature; - // Any pubkey without a match will return undefined - // this last filter removes all the undefined items in the array. + const p2ms = payments.p2ms({ output: script }); + // for each pubkey in order of p2ms script + return p2ms + .pubkeys.map(pk => { + // filter partialSig array by pubkey being equal + return (partialSig.filter(ps => { + return ps.pubkey.equals(pk); + })[0] || {}).signature; + // Any pubkey without a match will return undefined + // this last filter removes all the undefined items in the array. }) - .filter(v => !!v); + .filter(v => !!v); } function scriptWitnessToWitnessStack(buffer) { - let offset = 0; - function readSlice(n) { - offset += n; - return buffer.slice(offset - n, offset); - } - function readVarInt() { - const vi = varuint.decode(buffer, offset); - offset += varuint.decode.bytes; - return vi; - } - function readVarSlice() { - return readSlice(readVarInt()); - } - function readVector() { - const count = readVarInt(); - const vector = []; - for (let i = 0; i < count; i++) vector.push(readVarSlice()); - return vector; - } - return readVector(); + let offset = 0; + function readSlice(n) { + offset += n; + return buffer.slice(offset - n, offset); + } + function readVarInt() { + const vi = varuint.decode(buffer, offset); + offset += varuint.decode.bytes; + return vi; + } + function readVarSlice() { + return readSlice(readVarInt()); + } + function readVector() { + const count = readVarInt(); + const vector = []; + for (let i = 0; i < count; i++) + vector.push(readVarSlice()); + return vector; + } + return readVector(); } function sighashTypeToString(sighashType) { - let text = - sighashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY - ? 'SIGHASH_ANYONECANPAY | ' - : ''; - const sigMod = sighashType & 0x1f; - switch (sigMod) { - case transaction_1.Transaction.SIGHASH_ALL: - text += 'SIGHASH_ALL'; - break; - case transaction_1.Transaction.SIGHASH_SINGLE: - text += 'SIGHASH_SINGLE'; - break; - case transaction_1.Transaction.SIGHASH_NONE: - text += 'SIGHASH_NONE'; - break; - } - return text; + let text = sighashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY + ? 'SIGHASH_ANYONECANPAY | ' + : ''; + const sigMod = sighashType & 0x1f; + switch (sigMod) { + case transaction_1.Transaction.SIGHASH_ALL: + text += 'SIGHASH_ALL'; + break; + case transaction_1.Transaction.SIGHASH_SINGLE: + text += 'SIGHASH_SINGLE'; + break; + case transaction_1.Transaction.SIGHASH_NONE: + text += 'SIGHASH_NONE'; + break; + } + return text; } function witnessStackToScriptWitness(witness) { - let buffer = Buffer.allocUnsafe(0); - function writeSlice(slice) { - buffer = Buffer.concat([buffer, Buffer.from(slice)]); - } - function writeVarInt(i) { - const currentLen = buffer.length; - const varintLen = varuint.encodingLength(i); - buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); - varuint.encode(i, buffer, currentLen); - } - function writeVarSlice(slice) { - writeVarInt(slice.length); - writeSlice(slice); - } - function writeVector(vector) { - writeVarInt(vector.length); - vector.forEach(writeVarSlice); - } - writeVector(witness); - return buffer; + let buffer = Buffer.allocUnsafe(0); + function writeSlice(slice) { + buffer = Buffer.concat([buffer, Buffer.from(slice)]); + } + function writeVarInt(i) { + const currentLen = buffer.length; + const varintLen = varuint.encodingLength(i); + buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); + varuint.encode(i, buffer, currentLen); + } + function writeVarSlice(slice) { + writeVarInt(slice.length); + writeSlice(slice); + } + function writeVector(vector) { + writeVarInt(vector.length); + vector.forEach(writeVarSlice); + } + writeVector(witness); + return buffer; } function addNonWitnessTxCache(cache, input, inputIndex) { - cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo; - const tx = transaction_1.Transaction.fromBuffer(input.nonWitnessUtxo); - cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = tx; - const self = cache; - const selfIndex = inputIndex; - delete input.nonWitnessUtxo; - Object.defineProperty(input, 'nonWitnessUtxo', { - enumerable: true, - get() { - const buf = self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex]; - const txCache = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex]; - if (buf !== undefined) { - return buf; - } else { - const newBuf = txCache.toBuffer(); - self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = newBuf; - return newBuf; - } - }, - set(data) { - self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data; - }, - }); + cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo; + const tx = transaction_1.Transaction.fromBuffer(input.nonWitnessUtxo); + cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = tx; + const self = cache; + const selfIndex = inputIndex; + delete input.nonWitnessUtxo; + Object.defineProperty(input, 'nonWitnessUtxo', { + enumerable: true, + get() { + const buf = self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex]; + const txCache = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex]; + if (buf !== undefined) { + return buf; + } + else { + const newBuf = txCache.toBuffer(); + self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = newBuf; + return newBuf; + } + }, + set(data) { + self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data; + }, + }); } function inputFinalizeGetAmts(inputs, tx, cache, mustFinalize) { - inputs.forEach((input, idx) => { - if (mustFinalize && input.finalScriptSig) - tx.ins[idx].script = input.finalScriptSig; - if (mustFinalize && input.finalScriptWitness) { - tx.ins[idx].witness = scriptWitnessToWitnessStack( - input.finalScriptWitness, - ); - } - }); - if (tx.ins.some(x => x.witness.length !== 0)) { - tx.flag = 1; - } - const bytes = tx.virtualSize(); - const fee = 2 * bytes; - cache.__FEE = fee; - cache.__EXTRACTED_TX = tx; - cache.__FEE_RATE = Math.floor(fee / bytes); + inputs.forEach((input, idx) => { + if (mustFinalize && input.finalScriptSig) + tx.ins[idx].script = input.finalScriptSig; + if (mustFinalize && input.finalScriptWitness) { + tx.ins[idx].witness = scriptWitnessToWitnessStack(input.finalScriptWitness); + } + }); + if (tx.ins.some(x => x.witness.length !== 0)) { + tx.flag = 1; + } + const bytes = tx.virtualSize(); + const fee = 2 * bytes; + cache.__FEE = fee; + cache.__EXTRACTED_TX = tx; + cache.__FEE_RATE = Math.floor(fee / bytes); } function nonWitnessUtxoTxFromCache(cache, input, inputIndex) { - const c = cache.__NON_WITNESS_UTXO_TX_CACHE; - if (!c[inputIndex]) { - addNonWitnessTxCache(cache, input, inputIndex); - } - return c[inputIndex]; + const c = cache.__NON_WITNESS_UTXO_TX_CACHE; + if (!c[inputIndex]) { + addNonWitnessTxCache(cache, input, inputIndex); + } + return c[inputIndex]; } function classifyScript(script) { - if (isP2WPKH(script)) return 'witnesspubkeyhash'; - if (isP2PKH(script)) return 'pubkeyhash'; - if (isP2MS(script)) return 'multisig'; - if (isP2PK(script)) return 'pubkey'; - return 'nonstandard'; + if (isP2WPKH(script)) + return 'witnesspubkeyhash'; + if (isP2PKH(script)) + return 'pubkeyhash'; + if (isP2MS(script)) + return 'multisig'; + if (isP2PK(script)) + return 'pubkey'; + return 'nonstandard'; } function range(n) { - return [...Array(n).keys()]; + return [...Array(n).keys()]; } function randomBytes(options) { - if (options === undefined) options = {}; - const rng = options.rng || _randomBytes; - return rng(32); + if (options === undefined) + options = {}; + const rng = options.rng || _randomBytes; + return rng(32); } /** * Compute outputs blinders @@ -1682,60 +1384,43 @@ function randomBytes(options) { * @returns an array of BlindingData[] corresponding of blinders to blind outputs specified in outputsData */ function computeOutputsBlindingData(inputsBlindingData, outputsData) { - return __awaiter(this, void 0, void 0, function*() { - const outputsBlindingData = []; - outputsData - .slice(0, outputsData.length - 1) - .forEach(([satoshis, asset]) => { - const blindingData = { - value: satoshis, - asset, - valueBlindingFactor: randomBytes(), - assetBlindingFactor: randomBytes(), + return __awaiter(this, void 0, void 0, function* () { + const outputsBlindingData = []; + outputsData.slice(0, outputsData.length - 1).forEach(([satoshis, asset]) => { + const blindingData = { + value: satoshis, + asset, + valueBlindingFactor: randomBytes(), + assetBlindingFactor: randomBytes(), + }; + outputsBlindingData.push(blindingData); + }); + const [lastOutputValue, lastOutputAsset] = outputsData[outputsData.length - 1]; + const finalBlindingData = { + value: lastOutputValue, + asset: lastOutputAsset, + assetBlindingFactor: randomBytes(), + valueBlindingFactor: Buffer.from([]), }; - outputsBlindingData.push(blindingData); - }); - const [lastOutputValue, lastOutputAsset] = outputsData[ - outputsData.length - 1 - ]; - const finalBlindingData = { - value: lastOutputValue, - asset: lastOutputAsset, - assetBlindingFactor: randomBytes(), - valueBlindingFactor: Buffer.from([]), - }; - // values - const inputsValues = inputsBlindingData.map(({ value }) => value); - const outputsValues = outputsData - .map(([amount]) => amount) - .concat(lastOutputValue); - // asset blinders - const inputsAssetBlinders = inputsBlindingData.map( - ({ assetBlindingFactor }) => assetBlindingFactor, - ); - const outputsAssetBlinders = outputsBlindingData - .map(({ assetBlindingFactor }) => assetBlindingFactor) - .concat(finalBlindingData.assetBlindingFactor); - // value blinders - const inputsAmountBlinders = inputsBlindingData.map( - ({ valueBlindingFactor }) => valueBlindingFactor, - ); - const outputsAmountBlinders = outputsBlindingData.map( - ({ valueBlindingFactor }) => valueBlindingFactor, - ); - // compute output final amount blinder - const finalAmountBlinder = yield confidential.valueBlindingFactor( - inputsValues, - outputsValues, - inputsAssetBlinders, - outputsAssetBlinders, - inputsAmountBlinders, - outputsAmountBlinders, - ); - finalBlindingData.valueBlindingFactor = finalAmountBlinder; - outputsBlindingData.push(finalBlindingData); - return outputsBlindingData; - }); + // values + const inputsValues = inputsBlindingData.map(({ value }) => value); + const outputsValues = outputsData + .map(([amount]) => amount) + .concat(lastOutputValue); + // asset blinders + const inputsAssetBlinders = inputsBlindingData.map(({ assetBlindingFactor }) => assetBlindingFactor); + const outputsAssetBlinders = outputsBlindingData + .map(({ assetBlindingFactor }) => assetBlindingFactor) + .concat(finalBlindingData.assetBlindingFactor); + // value blinders + const inputsAmountBlinders = inputsBlindingData.map(({ valueBlindingFactor }) => valueBlindingFactor); + const outputsAmountBlinders = outputsBlindingData.map(({ valueBlindingFactor }) => valueBlindingFactor); + // compute output final amount blinder + const finalAmountBlinder = yield confidential.valueBlindingFactor(inputsValues, outputsValues, inputsAssetBlinders, outputsAssetBlinders, inputsAmountBlinders, outputsAmountBlinders); + finalBlindingData.valueBlindingFactor = finalAmountBlinder; + outputsBlindingData.push(finalBlindingData); + return outputsBlindingData; + }); } exports.computeOutputsBlindingData = computeOutputsBlindingData; /** @@ -1744,25 +1429,27 @@ exports.computeOutputsBlindingData = computeOutputsBlindingData; * @param witnessUtxo the prevout of the input I */ function toBlindingData(blindDataLike, witnessUtxo) { - return __awaiter(this, void 0, void 0, function*() { - if (!blindDataLike) { - if (!witnessUtxo) throw new Error('need witnessUtxo'); - return getUnconfidentialWitnessUtxoBlindingData(witnessUtxo); - } - if (Buffer.isBuffer(blindDataLike)) { - if (!witnessUtxo) throw new Error('need witnessUtxo'); - return confidential.unblindOutputWithKey(witnessUtxo, blindDataLike); - } - return blindDataLike; - }); + return __awaiter(this, void 0, void 0, function* () { + if (!blindDataLike) { + if (!witnessUtxo) + throw new Error('need witnessUtxo'); + return getUnconfidentialWitnessUtxoBlindingData(witnessUtxo); + } + if (Buffer.isBuffer(blindDataLike)) { + if (!witnessUtxo) + throw new Error('need witnessUtxo'); + return confidential.unblindOutputWithKey(witnessUtxo, blindDataLike); + } + return blindDataLike; + }); } exports.toBlindingData = toBlindingData; function getUnconfidentialWitnessUtxoBlindingData(prevout) { - const unblindedInputBlindingData = { - value: confidential.confidentialValueToSatoshi(prevout.value).toString(10), - valueBlindingFactor: transaction_1.ZERO, - asset: prevout.asset.slice(1), - assetBlindingFactor: transaction_1.ZERO, - }; - return unblindedInputBlindingData; + const unblindedInputBlindingData = { + value: confidential.confidentialValueToSatoshi(prevout.value).toString(10), + valueBlindingFactor: transaction_1.ZERO, + asset: prevout.asset.slice(1), + assetBlindingFactor: transaction_1.ZERO, + }; + return unblindedInputBlindingData; } diff --git a/src/script.js b/src/script.js index d727fe082..957489d9b 100644 --- a/src/script.js +++ b/src/script.js @@ -1,19 +1,15 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const scriptNumber = __importStar(require('./script_number')); -const scriptSignature = __importStar(require('./script_signature')); -const types = __importStar(require('./types')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const scriptNumber = __importStar(require("./script_number")); +const scriptSignature = __importStar(require("./script_signature")); +const types = __importStar(require("./types")); const bip66 = require('bip66'); const ecc = require('tiny-secp256k1'); const pushdata = require('pushdata-bitcoin'); @@ -22,165 +18,179 @@ exports.OPS = require('bitcoin-ops'); const REVERSE_OPS = require('bitcoin-ops/map'); const OP_INT_BASE = exports.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) - ); + return (types.Number(value) && + (value === exports.OPS.OP_0 || + (value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) || + value === exports.OPS.OP_1NEGATE)); } function isPushOnlyChunk(value) { - return types.Buffer(value) || isOPInt(value); + return types.Buffer(value) || isOPInt(value); } function isPushOnly(value) { - return types.Array(value) && value.every(isPushOnlyChunk); + return types.Array(value) && value.every(isPushOnlyChunk); } exports.isPushOnly = isPushOnly; function asMinimalOP(buffer) { - if (buffer.length === 0) return exports.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.length === 0) + return exports.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; } function chunksIsBuffer(buf) { - return Buffer.isBuffer(buf); + return Buffer.isBuffer(buf); } function chunksIsArray(buf) { - return types.Array(buf); + return types.Array(buf); } function singleChunkIsBuffer(buf) { - return Buffer.isBuffer(buf); + return Buffer.isBuffer(buf); } function compile(chunks) { - // TODO: remove me - if (chunksIsBuffer(chunks)) return chunks; - typeforce(types.Array, chunks); - const bufferSize = chunks.reduce((accum, chunk) => { - // data chunk - if (singleChunkIsBuffer(chunk)) { - // adhere to BIP62.3, minimal push policy - if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) { + // TODO: remove me + if (chunksIsBuffer(chunks)) + return chunks; + typeforce(types.Array, chunks); + const bufferSize = chunks.reduce((accum, chunk) => { + // data chunk + if (singleChunkIsBuffer(chunk)) { + // adhere to BIP62.3, minimal push policy + if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) { + return accum + 1; + } + return accum + pushdata.encodingLength(chunk.length) + chunk.length; + } + // opcode return accum + 1; - } - return accum + pushdata.encodingLength(chunk.length) + chunk.length; - } - // opcode - return accum + 1; - }, 0.0); - const buffer = Buffer.allocUnsafe(bufferSize); - let offset = 0; - chunks.forEach(chunk => { - // data chunk - if (singleChunkIsBuffer(chunk)) { - // adhere to BIP62.3, minimal push policy - const opcode = asMinimalOP(chunk); - if (opcode !== undefined) { - buffer.writeUInt8(opcode, offset); - offset += 1; - return; - } - offset += pushdata.encode(buffer, chunk.length, offset); - chunk.copy(buffer, offset); - offset += chunk.length; - // opcode - } else { - buffer.writeUInt8(chunk, offset); - offset += 1; - } - }); - if (offset !== buffer.length) throw new Error('Could not decode chunks'); - return buffer; + }, 0.0); + const buffer = Buffer.allocUnsafe(bufferSize); + let offset = 0; + chunks.forEach(chunk => { + // data chunk + if (singleChunkIsBuffer(chunk)) { + // adhere to BIP62.3, minimal push policy + const opcode = asMinimalOP(chunk); + if (opcode !== undefined) { + buffer.writeUInt8(opcode, offset); + offset += 1; + return; + } + offset += pushdata.encode(buffer, chunk.length, offset); + chunk.copy(buffer, offset); + offset += chunk.length; + // opcode + } + else { + buffer.writeUInt8(chunk, offset); + offset += 1; + } + }); + if (offset !== buffer.length) + throw new Error('Could not decode chunks'); + return buffer; } exports.compile = compile; function decompile(buffer) { - // TODO: remove me - if (chunksIsArray(buffer)) return buffer; - typeforce(types.Buffer, buffer); - const chunks = []; - let i = 0; - while (i < buffer.length) { - const opcode = buffer[i]; - // data chunk - if (opcode > exports.OPS.OP_0 && opcode <= exports.OPS.OP_PUSHDATA4) { - const d = pushdata.decode(buffer, i); - // did reading a pushDataInt fail? - if (d === null) return null; - i += d.size; - // attempt to read too much data? - if (i + d.number > buffer.length) return null; - const data = buffer.slice(i, i + d.number); - i += d.number; - // decompile minimally - const op = asMinimalOP(data); - if (op !== undefined) { - chunks.push(op); - } else { - chunks.push(data); - } - // opcode - } else { - chunks.push(opcode); - i += 1; + // TODO: remove me + if (chunksIsArray(buffer)) + return buffer; + typeforce(types.Buffer, buffer); + const chunks = []; + let i = 0; + while (i < buffer.length) { + const opcode = buffer[i]; + // data chunk + if (opcode > exports.OPS.OP_0 && opcode <= exports.OPS.OP_PUSHDATA4) { + const d = pushdata.decode(buffer, i); + // did reading a pushDataInt fail? + if (d === null) + return null; + i += d.size; + // attempt to read too much data? + if (i + d.number > buffer.length) + return null; + const data = buffer.slice(i, i + d.number); + i += d.number; + // decompile minimally + const op = asMinimalOP(data); + if (op !== undefined) { + chunks.push(op); + } + else { + chunks.push(data); + } + // opcode + } + else { + chunks.push(opcode); + i += 1; + } } - } - return chunks; + return chunks; } exports.decompile = decompile; function toASM(chunks) { - if (chunksIsBuffer(chunks)) { - chunks = decompile(chunks); - } - return chunks - .map(chunk => { - // data? - if (singleChunkIsBuffer(chunk)) { - const op = asMinimalOP(chunk); - if (op === undefined) return chunk.toString('hex'); - chunk = op; - } - // opcode! - return REVERSE_OPS[chunk]; + if (chunksIsBuffer(chunks)) { + chunks = decompile(chunks); + } + return chunks + .map(chunk => { + // data? + if (singleChunkIsBuffer(chunk)) { + const op = asMinimalOP(chunk); + if (op === undefined) + return chunk.toString('hex'); + chunk = op; + } + // opcode! + return REVERSE_OPS[chunk]; }) - .join(' '); + .join(' '); } exports.toASM = toASM; function fromASM(asm) { - typeforce(types.String, asm); - return compile( - asm.split(' ').map(chunkStr => { - // opcode? - if (exports.OPS[chunkStr] !== undefined) return exports.OPS[chunkStr]; - typeforce(types.Hex, chunkStr); - // data! - return Buffer.from(chunkStr, 'hex'); - }), - ); + typeforce(types.String, asm); + return compile(asm.split(' ').map(chunkStr => { + // opcode? + if (exports.OPS[chunkStr] !== undefined) + return exports.OPS[chunkStr]; + typeforce(types.Hex, chunkStr); + // data! + return Buffer.from(chunkStr, 'hex'); + })); } exports.fromASM = fromASM; function toStack(chunks) { - chunks = decompile(chunks); - typeforce(isPushOnly, chunks); - return chunks.map(op => { - if (singleChunkIsBuffer(op)) return op; - if (op === exports.OPS.OP_0) return Buffer.allocUnsafe(0); - return scriptNumber.encode(op - OP_INT_BASE); - }); + chunks = decompile(chunks); + typeforce(isPushOnly, chunks); + return chunks.map(op => { + if (singleChunkIsBuffer(op)) + return op; + if (op === exports.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 ecc.isPoint(buffer); } exports.isCanonicalPubKey = isCanonicalPubKey; function isDefinedHashType(hashType) { - const hashTypeMod = hashType & ~0x80; - // return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE - return hashTypeMod > 0x00 && hashTypeMod < 0x04; + const hashTypeMod = hashType & ~0x80; + // return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE + return hashTypeMod > 0x00 && hashTypeMod < 0x04; } exports.isDefinedHashType = isDefinedHashType; function isCanonicalScriptSignature(buffer) { - if (!Buffer.isBuffer(buffer)) return false; - if (!isDefinedHashType(buffer[buffer.length - 1])) return false; - return bip66.check(buffer.slice(0, -1)); + if (!Buffer.isBuffer(buffer)) + return false; + if (!isDefinedHashType(buffer[buffer.length - 1])) + return false; + return bip66.check(buffer.slice(0, -1)); } exports.isCanonicalScriptSignature = isCanonicalScriptSignature; // tslint:disable-next-line variable-name diff --git a/src/script_number.js b/src/script_number.js index 3f313af56..3a30a43de 100644 --- a/src/script_number.js +++ b/src/script_number.js @@ -1,61 +1,65 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); function decode(buffer, maxLength, minimal) { - maxLength = maxLength || 4; - minimal = minimal === undefined ? true : minimal; - const length = buffer.length; - if (length === 0) return 0; - if (length > maxLength) throw new TypeError('Script number overflow'); - if (minimal) { - if ((buffer[length - 1] & 0x7f) === 0) { - if (length <= 1 || (buffer[length - 2] & 0x80) === 0) - throw new Error('Non-minimally encoded script number'); + maxLength = maxLength || 4; + minimal = minimal === undefined ? true : minimal; + const length = buffer.length; + if (length === 0) + return 0; + if (length > maxLength) + throw new TypeError('Script number overflow'); + if (minimal) { + if ((buffer[length - 1] & 0x7f) === 0) { + if (length <= 1 || (buffer[length - 2] & 0x80) === 0) + throw new Error('Non-minimally encoded script number'); + } } - } - // 40-bit - if (length === 5) { - const a = buffer.readUInt32LE(0); - const b = buffer.readUInt8(4); - if (b & 0x80) return -((b & ~0x80) * 0x100000000 + a); - return b * 0x100000000 + a; - } - // 32-bit / 24-bit / 16-bit / 8-bit - let result = 0; - for (let i = 0; i < length; ++i) { - result |= buffer[i] << (8 * i); - } - if (buffer[length - 1] & 0x80) - return -(result & ~(0x80 << (8 * (length - 1)))); - return result; + // 40-bit + if (length === 5) { + const a = buffer.readUInt32LE(0); + const b = buffer.readUInt8(4); + if (b & 0x80) + return -((b & ~0x80) * 0x100000000 + a); + return b * 0x100000000 + a; + } + // 32-bit / 24-bit / 16-bit / 8-bit + let result = 0; + for (let i = 0; i < length; ++i) { + result |= buffer[i] << (8 * i); + } + if (buffer[length - 1] & 0x80) + return -(result & ~(0x80 << (8 * (length - 1)))); + return result; } exports.decode = decode; function scriptNumSize(i) { - return i > 0x7fffffff - ? 5 - : i > 0x7fffff - ? 4 - : i > 0x7fff - ? 3 - : i > 0x7f - ? 2 - : i > 0x00 - ? 1 - : 0; + return i > 0x7fffffff + ? 5 + : i > 0x7fffff + ? 4 + : i > 0x7fff + ? 3 + : i > 0x7f + ? 2 + : i > 0x00 + ? 1 + : 0; } function encode(_number) { - let value = Math.abs(_number); - const size = scriptNumSize(value); - const buffer = Buffer.allocUnsafe(size); - const negative = _number < 0; - for (let i = 0; i < size; ++i) { - buffer.writeUInt8(value & 0xff, i); - value >>= 8; - } - if (buffer[size - 1] & 0x80) { - buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1); - } else if (negative) { - buffer[size - 1] |= 0x80; - } - return buffer; + let value = Math.abs(_number); + const size = scriptNumSize(value); + const buffer = Buffer.allocUnsafe(size); + const negative = _number < 0; + for (let i = 0; i < size; ++i) { + buffer.writeUInt8(value & 0xff, i); + value >>= 8; + } + if (buffer[size - 1] & 0x80) { + buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1); + } + else if (negative) { + buffer[size - 1] |= 0x80; + } + return buffer; } exports.encode = encode; diff --git a/src/script_signature.js b/src/script_signature.js index 116196fc8..6f36f1127 100644 --- a/src/script_signature.js +++ b/src/script_signature.js @@ -1,63 +1,60 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const types = __importStar(require('./types')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const types = __importStar(require("./types")); const bip66 = require('bip66'); const typeforce = require('typeforce'); const ZERO = Buffer.alloc(1, 0); function toDER(x) { - let i = 0; - while (x[i] === 0) ++i; - if (i === x.length) return ZERO; - x = x.slice(i); - if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length); - return x; + let i = 0; + while (x[i] === 0) + ++i; + if (i === x.length) + return ZERO; + x = x.slice(i); + if (x[0] & 0x80) + return Buffer.concat([ZERO, x], 1 + x.length); + return x; } function fromDER(x) { - if (x[0] === 0x00) x = x.slice(1); - const buffer = Buffer.alloc(32, 0); - const bstart = Math.max(0, 32 - x.length); - x.copy(buffer, bstart); - return buffer; + if (x[0] === 0x00) + x = x.slice(1); + const buffer = Buffer.alloc(32, 0); + const bstart = Math.max(0, 32 - x.length); + x.copy(buffer, bstart); + return buffer; } // BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) function decode(buffer) { - const hashType = buffer.readUInt8(buffer.length - 1); - const hashTypeMod = hashType & ~0x80; - if (hashTypeMod <= 0 || hashTypeMod >= 4) - throw new Error('Invalid hashType ' + hashType); - const decoded = bip66.decode(buffer.slice(0, -1)); - const r = fromDER(decoded.r); - const s = fromDER(decoded.s); - const signature = Buffer.concat([r, s], 64); - return { signature, hashType }; + const hashType = buffer.readUInt8(buffer.length - 1); + const hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); + const decoded = bip66.decode(buffer.slice(0, -1)); + const r = fromDER(decoded.r); + const s = fromDER(decoded.s); + const signature = Buffer.concat([r, s], 64); + return { signature, hashType }; } exports.decode = decode; function encode(signature, hashType) { - typeforce( - { - signature: types.BufferN(64), - hashType: types.UInt8, - }, - { signature, hashType }, - ); - const hashTypeMod = hashType & ~0x80; - if (hashTypeMod <= 0 || hashTypeMod >= 4) - throw new Error('Invalid hashType ' + hashType); - const hashTypeBuffer = Buffer.allocUnsafe(1); - hashTypeBuffer.writeUInt8(hashType, 0); - const r = toDER(signature.slice(0, 32)); - const s = toDER(signature.slice(32, 64)); - return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]); + typeforce({ + signature: types.BufferN(64), + hashType: types.UInt8, + }, { signature, hashType }); + const hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); + const hashTypeBuffer = Buffer.allocUnsafe(1); + hashTypeBuffer.writeUInt8(hashType, 0); + const r = toDER(signature.slice(0, 32)); + const s = toDER(signature.slice(32, 64)); + return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]); } exports.encode = encode; diff --git a/src/sha256d.js b/src/sha256d.js index e0f1b9031..05feaa9ce 100644 --- a/src/sha256d.js +++ b/src/sha256d.js @@ -1,5 +1,5 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); // SHA-256 (+ HMAC and PBKDF2) for JavaScript. // // Written in 2014-2016 by Dmitry Chestnykh. @@ -20,304 +20,304 @@ exports.digestLength = 32; exports.blockSize = 64; // SHA-256 constants const K = new Uint32Array([ - 0x428a2f98, - 0x71374491, - 0xb5c0fbcf, - 0xe9b5dba5, - 0x3956c25b, - 0x59f111f1, - 0x923f82a4, - 0xab1c5ed5, - 0xd807aa98, - 0x12835b01, - 0x243185be, - 0x550c7dc3, - 0x72be5d74, - 0x80deb1fe, - 0x9bdc06a7, - 0xc19bf174, - 0xe49b69c1, - 0xefbe4786, - 0x0fc19dc6, - 0x240ca1cc, - 0x2de92c6f, - 0x4a7484aa, - 0x5cb0a9dc, - 0x76f988da, - 0x983e5152, - 0xa831c66d, - 0xb00327c8, - 0xbf597fc7, - 0xc6e00bf3, - 0xd5a79147, - 0x06ca6351, - 0x14292967, - 0x27b70a85, - 0x2e1b2138, - 0x4d2c6dfc, - 0x53380d13, - 0x650a7354, - 0x766a0abb, - 0x81c2c92e, - 0x92722c85, - 0xa2bfe8a1, - 0xa81a664b, - 0xc24b8b70, - 0xc76c51a3, - 0xd192e819, - 0xd6990624, - 0xf40e3585, - 0x106aa070, - 0x19a4c116, - 0x1e376c08, - 0x2748774c, - 0x34b0bcb5, - 0x391c0cb3, - 0x4ed8aa4a, - 0x5b9cca4f, - 0x682e6ff3, - 0x748f82ee, - 0x78a5636f, - 0x84c87814, - 0x8cc70208, - 0x90befffa, - 0xa4506ceb, - 0xbef9a3f7, - 0xc67178f2, + 0x428a2f98, + 0x71374491, + 0xb5c0fbcf, + 0xe9b5dba5, + 0x3956c25b, + 0x59f111f1, + 0x923f82a4, + 0xab1c5ed5, + 0xd807aa98, + 0x12835b01, + 0x243185be, + 0x550c7dc3, + 0x72be5d74, + 0x80deb1fe, + 0x9bdc06a7, + 0xc19bf174, + 0xe49b69c1, + 0xefbe4786, + 0x0fc19dc6, + 0x240ca1cc, + 0x2de92c6f, + 0x4a7484aa, + 0x5cb0a9dc, + 0x76f988da, + 0x983e5152, + 0xa831c66d, + 0xb00327c8, + 0xbf597fc7, + 0xc6e00bf3, + 0xd5a79147, + 0x06ca6351, + 0x14292967, + 0x27b70a85, + 0x2e1b2138, + 0x4d2c6dfc, + 0x53380d13, + 0x650a7354, + 0x766a0abb, + 0x81c2c92e, + 0x92722c85, + 0xa2bfe8a1, + 0xa81a664b, + 0xc24b8b70, + 0xc76c51a3, + 0xd192e819, + 0xd6990624, + 0xf40e3585, + 0x106aa070, + 0x19a4c116, + 0x1e376c08, + 0x2748774c, + 0x34b0bcb5, + 0x391c0cb3, + 0x4ed8aa4a, + 0x5b9cca4f, + 0x682e6ff3, + 0x748f82ee, + 0x78a5636f, + 0x84c87814, + 0x8cc70208, + 0x90befffa, + 0xa4506ceb, + 0xbef9a3f7, + 0xc67178f2, ]); function hashBlocks(w, v, p, pos, len) { - let a; - let b; - let c; - let d; - let e; - let f; - let g; - let h; - let u; - let i; - let j; - let t1; - let t2; - while (len >= 64) { - a = v[0]; - b = v[1]; - c = v[2]; - d = v[3]; - e = v[4]; - f = v[5]; - g = v[6]; - h = v[7]; - for (i = 0; i < 16; i++) { - j = pos + i * 4; - w[i] = - ((p[j] & 0xff) << 24) | - ((p[j + 1] & 0xff) << 16) | - ((p[j + 2] & 0xff) << 8) | - (p[j + 3] & 0xff); + let a; + let b; + let c; + let d; + let e; + let f; + let g; + let h; + let u; + let i; + let j; + let t1; + let t2; + while (len >= 64) { + a = v[0]; + b = v[1]; + c = v[2]; + d = v[3]; + e = v[4]; + f = v[5]; + g = v[6]; + h = v[7]; + for (i = 0; i < 16; i++) { + j = pos + i * 4; + w[i] = + ((p[j] & 0xff) << 24) | + ((p[j + 1] & 0xff) << 16) | + ((p[j + 2] & 0xff) << 8) | + (p[j + 3] & 0xff); + } + for (i = 16; i < 64; i++) { + u = w[i - 2]; + t1 = + ((u >>> 17) | (u << (32 - 17))) ^ + ((u >>> 19) | (u << (32 - 19))) ^ + (u >>> 10); + u = w[i - 15]; + t2 = + ((u >>> 7) | (u << (32 - 7))) ^ + ((u >>> 18) | (u << (32 - 18))) ^ + (u >>> 3); + w[i] = ((t1 + w[i - 7]) | 0) + ((t2 + w[i - 16]) | 0); + } + for (i = 0; i < 64; i++) { + t1 = + ((((((e >>> 6) | (e << (32 - 6))) ^ + ((e >>> 11) | (e << (32 - 11))) ^ + ((e >>> 25) | (e << (32 - 25)))) + + ((e & f) ^ (~e & g))) | + 0) + + ((h + ((K[i] + w[i]) | 0)) | 0)) | + 0; + t2 = + ((((a >>> 2) | (a << (32 - 2))) ^ + ((a >>> 13) | (a << (32 - 13))) ^ + ((a >>> 22) | (a << (32 - 22)))) + + ((a & b) ^ (a & c) ^ (b & c))) | + 0; + h = g; + g = f; + f = e; + e = (d + t1) | 0; + d = c; + c = b; + b = a; + a = (t1 + t2) | 0; + } + v[0] += a; + v[1] += b; + v[2] += c; + v[3] += d; + v[4] += e; + v[5] += f; + v[6] += g; + v[7] += h; + pos += 64; + len -= 64; } - for (i = 16; i < 64; i++) { - u = w[i - 2]; - t1 = - ((u >>> 17) | (u << (32 - 17))) ^ - ((u >>> 19) | (u << (32 - 19))) ^ - (u >>> 10); - u = w[i - 15]; - t2 = - ((u >>> 7) | (u << (32 - 7))) ^ - ((u >>> 18) | (u << (32 - 18))) ^ - (u >>> 3); - w[i] = ((t1 + w[i - 7]) | 0) + ((t2 + w[i - 16]) | 0); - } - for (i = 0; i < 64; i++) { - t1 = - ((((((e >>> 6) | (e << (32 - 6))) ^ - ((e >>> 11) | (e << (32 - 11))) ^ - ((e >>> 25) | (e << (32 - 25)))) + - ((e & f) ^ (~e & g))) | - 0) + - ((h + ((K[i] + w[i]) | 0)) | 0)) | - 0; - t2 = - ((((a >>> 2) | (a << (32 - 2))) ^ - ((a >>> 13) | (a << (32 - 13))) ^ - ((a >>> 22) | (a << (32 - 22)))) + - ((a & b) ^ (a & c) ^ (b & c))) | - 0; - h = g; - g = f; - f = e; - e = (d + t1) | 0; - d = c; - c = b; - b = a; - a = (t1 + t2) | 0; - } - v[0] += a; - v[1] += b; - v[2] += c; - v[3] += d; - v[4] += e; - v[5] += f; - v[6] += g; - v[7] += h; - pos += 64; - len -= 64; - } - return pos; + return pos; } // Hash implements SHA256 hash algorithm. class Hash { - constructor() { - this.digestLength = exports.digestLength; - this.blockSize = exports.blockSize; - this.finished = false; // indicates whether the hash was finalized - // Note: Int32Array is used instead of Uint32Array for performance reasons. - this.state = new Int32Array(8); // hash state - this.temp = new Int32Array(64); // temporary state - this.buffer = new Uint8Array(128); // buffer for data to hash - this.bufferLength = 0; // number of bytes in buffer - this.bytesHashed = 0; // number of total bytes hashed - this.reset(); - } - // Resets hash state making it possible - // to re-use this instance to hash other data. - reset() { - this.state[0] = 0x6a09e667; - this.state[1] = 0xbb67ae85; - this.state[2] = 0x3c6ef372; - this.state[3] = 0xa54ff53a; - this.state[4] = 0x510e527f; - this.state[5] = 0x9b05688c; - this.state[6] = 0x1f83d9ab; - this.state[7] = 0x5be0cd19; - this.bufferLength = 0; - this.bytesHashed = 0; - this.finished = false; - return this; - } - // Cleans internal buffers and re-initializes hash state. - clean() { - for (let i = 0; i < this.buffer.length; i++) { - this.buffer[i] = 0; - } - for (let i = 0; i < this.temp.length; i++) { - this.temp[i] = 0; + constructor() { + this.digestLength = exports.digestLength; + this.blockSize = exports.blockSize; + this.finished = false; // indicates whether the hash was finalized + // Note: Int32Array is used instead of Uint32Array for performance reasons. + this.state = new Int32Array(8); // hash state + this.temp = new Int32Array(64); // temporary state + this.buffer = new Uint8Array(128); // buffer for data to hash + this.bufferLength = 0; // number of bytes in buffer + this.bytesHashed = 0; // number of total bytes hashed + this.reset(); } - this.reset(); - } - // Updates hash state with the given data. - // - // Optionally, length of the data can be specified to hash - // fewer bytes than data.length. - // - // Throws error when trying to update already finalized hash: - // instance must be reset to use it again. - update(data, dataLength = data.length) { - if (this.finished) { - throw new Error("SHA256: can't update because hash was finished."); - } - let dataPos = 0; - this.bytesHashed += dataLength; - if (this.bufferLength > 0) { - while (this.bufferLength < 64 && dataLength > 0) { - this.buffer[this.bufferLength++] = data[dataPos++]; - dataLength--; - } - if (this.bufferLength === 64) { - hashBlocks(this.temp, this.state, this.buffer, 0, 64); + // Resets hash state making it possible + // to re-use this instance to hash other data. + reset() { + this.state[0] = 0x6a09e667; + this.state[1] = 0xbb67ae85; + this.state[2] = 0x3c6ef372; + this.state[3] = 0xa54ff53a; + this.state[4] = 0x510e527f; + this.state[5] = 0x9b05688c; + this.state[6] = 0x1f83d9ab; + this.state[7] = 0x5be0cd19; this.bufferLength = 0; - } + this.bytesHashed = 0; + this.finished = false; + return this; } - if (dataLength >= 64) { - dataPos = hashBlocks(this.temp, this.state, data, dataPos, dataLength); - dataLength %= 64; + // Cleans internal buffers and re-initializes hash state. + clean() { + for (let i = 0; i < this.buffer.length; i++) { + this.buffer[i] = 0; + } + for (let i = 0; i < this.temp.length; i++) { + this.temp[i] = 0; + } + this.reset(); } - while (dataLength > 0) { - this.buffer[this.bufferLength++] = data[dataPos++]; - dataLength--; + // Updates hash state with the given data. + // + // Optionally, length of the data can be specified to hash + // fewer bytes than data.length. + // + // Throws error when trying to update already finalized hash: + // instance must be reset to use it again. + update(data, dataLength = data.length) { + if (this.finished) { + throw new Error("SHA256: can't update because hash was finished."); + } + let dataPos = 0; + this.bytesHashed += dataLength; + if (this.bufferLength > 0) { + while (this.bufferLength < 64 && dataLength > 0) { + this.buffer[this.bufferLength++] = data[dataPos++]; + dataLength--; + } + if (this.bufferLength === 64) { + hashBlocks(this.temp, this.state, this.buffer, 0, 64); + this.bufferLength = 0; + } + } + if (dataLength >= 64) { + dataPos = hashBlocks(this.temp, this.state, data, dataPos, dataLength); + dataLength %= 64; + } + while (dataLength > 0) { + this.buffer[this.bufferLength++] = data[dataPos++]; + dataLength--; + } + return this; } - return this; - } - // Finalizes hash state and puts hash into out. - // - // If hash was already finalized, puts the same value. - finish(out) { - if (!this.finished) { - const bytesHashed = this.bytesHashed; - const left = this.bufferLength; - const bitLenHi = (bytesHashed / 0x20000000) | 0; - const bitLenLo = bytesHashed << 3; - const padLength = bytesHashed % 64 < 56 ? 64 : 128; - this.buffer[left] = 0x80; - for (let i = left + 1; i < padLength - 8; i++) { - this.buffer[i] = 0; - } - this.buffer[padLength - 8] = (bitLenHi >>> 24) & 0xff; - this.buffer[padLength - 7] = (bitLenHi >>> 16) & 0xff; - this.buffer[padLength - 6] = (bitLenHi >>> 8) & 0xff; - this.buffer[padLength - 5] = (bitLenHi >>> 0) & 0xff; - this.buffer[padLength - 4] = (bitLenLo >>> 24) & 0xff; - this.buffer[padLength - 3] = (bitLenLo >>> 16) & 0xff; - this.buffer[padLength - 2] = (bitLenLo >>> 8) & 0xff; - this.buffer[padLength - 1] = (bitLenLo >>> 0) & 0xff; - hashBlocks(this.temp, this.state, this.buffer, 0, padLength); - this.finished = true; + // Finalizes hash state and puts hash into out. + // + // If hash was already finalized, puts the same value. + finish(out) { + if (!this.finished) { + const bytesHashed = this.bytesHashed; + const left = this.bufferLength; + const bitLenHi = (bytesHashed / 0x20000000) | 0; + const bitLenLo = bytesHashed << 3; + const padLength = bytesHashed % 64 < 56 ? 64 : 128; + this.buffer[left] = 0x80; + for (let i = left + 1; i < padLength - 8; i++) { + this.buffer[i] = 0; + } + this.buffer[padLength - 8] = (bitLenHi >>> 24) & 0xff; + this.buffer[padLength - 7] = (bitLenHi >>> 16) & 0xff; + this.buffer[padLength - 6] = (bitLenHi >>> 8) & 0xff; + this.buffer[padLength - 5] = (bitLenHi >>> 0) & 0xff; + this.buffer[padLength - 4] = (bitLenLo >>> 24) & 0xff; + this.buffer[padLength - 3] = (bitLenLo >>> 16) & 0xff; + this.buffer[padLength - 2] = (bitLenLo >>> 8) & 0xff; + this.buffer[padLength - 1] = (bitLenLo >>> 0) & 0xff; + hashBlocks(this.temp, this.state, this.buffer, 0, padLength); + this.finished = true; + } + for (let i = 0; i < 8; i++) { + out[i * 4 + 0] = (this.state[i] >>> 24) & 0xff; + out[i * 4 + 1] = (this.state[i] >>> 16) & 0xff; + out[i * 4 + 2] = (this.state[i] >>> 8) & 0xff; + out[i * 4 + 3] = (this.state[i] >>> 0) & 0xff; + } + return this; } - for (let i = 0; i < 8; i++) { - out[i * 4 + 0] = (this.state[i] >>> 24) & 0xff; - out[i * 4 + 1] = (this.state[i] >>> 16) & 0xff; - out[i * 4 + 2] = (this.state[i] >>> 8) & 0xff; - out[i * 4 + 3] = (this.state[i] >>> 0) & 0xff; + // Returns the final hash digest. + digest() { + const out = new Uint8Array(this.digestLength); + this.finish(out); + return out; } - return this; - } - // Returns the final hash digest. - digest() { - const out = new Uint8Array(this.digestLength); - this.finish(out); - return out; - } - // Internal function for use in HMAC for optimization. - _saveState(out) { - for (let i = 0; i < this.state.length; i++) { - out[i] = this.state[i]; + // Internal function for use in HMAC for optimization. + _saveState(out) { + for (let i = 0; i < this.state.length; i++) { + out[i] = this.state[i]; + } } - } - // Internal function for use in HMAC for optimization. - _restoreState(from, bytesHashed) { - for (let i = 0; i < this.state.length; i++) { - this.state[i] = from[i]; + // Internal function for use in HMAC for optimization. + _restoreState(from, bytesHashed) { + for (let i = 0; i < this.state.length; i++) { + this.state[i] = from[i]; + } + this.bytesHashed = bytesHashed; + this.finished = false; + this.bufferLength = 0; } - this.bytesHashed = bytesHashed; - this.finished = false; - this.bufferLength = 0; - } } exports.Hash = Hash; // Returns SHA256 hash of data. function hash(data) { - const h = new Hash().update(data); - const digest = h.digest(); - h.clean(); - return digest; + const h = new Hash().update(data); + const digest = h.digest(); + h.clean(); + return digest; } exports.hash = hash; function sha256Midstate(data) { - let d = data; - if (data.length > exports.blockSize) { - d = data.slice(0, exports.blockSize); - } - const h = new Hash(); - h.reset(); - h.update(Uint8Array.from(d)); - const midstate = Buffer.alloc(exports.digestLength); - for (let i = 0; i < 8; i++) { - midstate[i * 4 + 0] = (h.state[i] >>> 24) & 0xff; - midstate[i * 4 + 1] = (h.state[i] >>> 16) & 0xff; - midstate[i * 4 + 2] = (h.state[i] >>> 8) & 0xff; - midstate[i * 4 + 3] = (h.state[i] >>> 0) & 0xff; - } - return midstate; + let d = data; + if (data.length > exports.blockSize) { + d = data.slice(0, exports.blockSize); + } + const h = new Hash(); + h.reset(); + h.update(Uint8Array.from(d)); + const midstate = Buffer.alloc(exports.digestLength); + for (let i = 0; i < 8; i++) { + midstate[i * 4 + 0] = (h.state[i] >>> 24) & 0xff; + midstate[i * 4 + 1] = (h.state[i] >>> 16) & 0xff; + midstate[i * 4 + 2] = (h.state[i] >>> 8) & 0xff; + midstate[i * 4 + 3] = (h.state[i] >>> 0) & 0xff; + } + return midstate; } exports.sha256Midstate = sha256Midstate; diff --git a/src/templates/multisig/index.js b/src/templates/multisig/index.js index a010c5408..218d0d404 100644 --- a/src/templates/multisig/index.js +++ b/src/templates/multisig/index.js @@ -1,17 +1,13 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const input = __importStar(require('./input')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const input = __importStar(require("./input")); exports.input = input; -const output = __importStar(require('./output')); +const output = __importStar(require("./output")); exports.output = output; diff --git a/src/templates/multisig/input.js b/src/templates/multisig/input.js index 0e959b751..faf94dded 100644 --- a/src/templates/multisig/input.js +++ b/src/templates/multisig/input.js @@ -1,34 +1,30 @@ -'use strict'; +"use strict"; // OP_0 [signatures ...] -var __importStar = - (this && this.__importStar) || - function(mod) { +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bscript = __importStar(require('../../script')); -const script_1 = require('../../script'); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = __importStar(require("../../script")); +const script_1 = require("../../script"); function partialSignature(value) { - return ( - value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value) - ); + return (value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value)); } function check(script, allowIncomplete) { - const chunks = bscript.decompile(script); - if (chunks.length < 2) return false; - if (chunks[0] !== script_1.OPS.OP_0) return false; - if (allowIncomplete) { - return chunks.slice(1).every(partialSignature); - } - return chunks.slice(1).every(bscript.isCanonicalScriptSignature); + const chunks = bscript.decompile(script); + if (chunks.length < 2) + return false; + if (chunks[0] !== script_1.OPS.OP_0) + return false; + if (allowIncomplete) { + return chunks.slice(1).every(partialSignature); + } + return chunks.slice(1).every(bscript.isCanonicalScriptSignature); } exports.check = check; check.toJSON = () => { - return 'multisig input'; + return 'multisig input'; }; diff --git a/src/templates/multisig/output.js b/src/templates/multisig/output.js index 642f8bdb2..072cd333e 100644 --- a/src/templates/multisig/output.js +++ b/src/templates/multisig/output.js @@ -1,38 +1,43 @@ -'use strict'; +"use strict"; // m [pubKeys ...] n OP_CHECKMULTISIG -var __importStar = - (this && this.__importStar) || - function(mod) { +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bscript = __importStar(require('../../script')); -const script_1 = require('../../script'); -const types = __importStar(require('../../types')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = __importStar(require("../../script")); +const script_1 = require("../../script"); +const types = __importStar(require("../../types")); const OP_INT_BASE = script_1.OPS.OP_RESERVED; // OP_1 - 1 function check(script, allowIncomplete) { - const chunks = bscript.decompile(script); - if (chunks.length < 4) return false; - if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG) return false; - if (!types.Number(chunks[0])) return false; - if (!types.Number(chunks[chunks.length - 2])) return false; - const m = chunks[0] - OP_INT_BASE; - const n = chunks[chunks.length - 2] - OP_INT_BASE; - if (m <= 0) return false; - if (n > 16) return false; - if (m > n) return false; - if (n !== chunks.length - 3) return false; - if (allowIncomplete) return true; - const keys = chunks.slice(1, -2); - return keys.every(bscript.isCanonicalPubKey); + const chunks = bscript.decompile(script); + if (chunks.length < 4) + return false; + if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG) + return false; + if (!types.Number(chunks[0])) + return false; + if (!types.Number(chunks[chunks.length - 2])) + return false; + const m = chunks[0] - OP_INT_BASE; + const n = chunks[chunks.length - 2] - OP_INT_BASE; + if (m <= 0) + return false; + if (n > 16) + return false; + if (m > n) + return false; + if (n !== chunks.length - 3) + return false; + if (allowIncomplete) + return true; + const keys = chunks.slice(1, -2); + return keys.every(bscript.isCanonicalPubKey); } exports.check = check; check.toJSON = () => { - return 'multi-sig output'; + return 'multi-sig output'; }; diff --git a/src/templates/nulldata.js b/src/templates/nulldata.js index bb54106ee..2bbca7aa9 100644 --- a/src/templates/nulldata.js +++ b/src/templates/nulldata.js @@ -1,26 +1,22 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); +}; +Object.defineProperty(exports, "__esModule", { value: true }); // OP_RETURN {data} -const bscript = __importStar(require('../script')); +const bscript = __importStar(require("../script")); const OPS = bscript.OPS; function check(script) { - const buffer = bscript.compile(script); - return buffer.length > 1 && buffer[0] === OPS.OP_RETURN; + const buffer = bscript.compile(script); + return buffer.length > 1 && buffer[0] === OPS.OP_RETURN; } exports.check = check; check.toJSON = () => { - return 'null data output'; + return 'null data output'; }; const output = { check }; exports.output = output; diff --git a/src/templates/pubkey/index.js b/src/templates/pubkey/index.js index a010c5408..218d0d404 100644 --- a/src/templates/pubkey/index.js +++ b/src/templates/pubkey/index.js @@ -1,17 +1,13 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const input = __importStar(require('./input')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const input = __importStar(require("./input")); exports.input = input; -const output = __importStar(require('./output')); +const output = __importStar(require("./output")); exports.output = output; diff --git a/src/templates/pubkey/input.js b/src/templates/pubkey/input.js index eef3fc500..89694240d 100644 --- a/src/templates/pubkey/input.js +++ b/src/templates/pubkey/input.js @@ -1,23 +1,20 @@ -'use strict'; +"use strict"; // {signature} -var __importStar = - (this && this.__importStar) || - function(mod) { +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bscript = __importStar(require('../../script')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = __importStar(require("../../script")); function check(script) { - const chunks = bscript.decompile(script); - return chunks.length === 1 && bscript.isCanonicalScriptSignature(chunks[0]); + const chunks = bscript.decompile(script); + return (chunks.length === 1 && + bscript.isCanonicalScriptSignature(chunks[0])); } exports.check = check; check.toJSON = () => { - return 'pubKey input'; + return 'pubKey input'; }; diff --git a/src/templates/pubkey/output.js b/src/templates/pubkey/output.js index cdff455fd..6bf5ba42b 100644 --- a/src/templates/pubkey/output.js +++ b/src/templates/pubkey/output.js @@ -1,28 +1,22 @@ -'use strict'; +"use strict"; // {pubKey} OP_CHECKSIG -var __importStar = - (this && this.__importStar) || - function(mod) { +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bscript = __importStar(require('../../script')); -const script_1 = require('../../script'); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = __importStar(require("../../script")); +const script_1 = require("../../script"); function check(script) { - const chunks = bscript.decompile(script); - return ( - chunks.length === 2 && - bscript.isCanonicalPubKey(chunks[0]) && - chunks[1] === script_1.OPS.OP_CHECKSIG - ); + const chunks = bscript.decompile(script); + return (chunks.length === 2 && + bscript.isCanonicalPubKey(chunks[0]) && + chunks[1] === script_1.OPS.OP_CHECKSIG); } exports.check = check; check.toJSON = () => { - return 'pubKey output'; + return 'pubKey output'; }; diff --git a/src/templates/pubkeyhash/index.js b/src/templates/pubkeyhash/index.js index a010c5408..218d0d404 100644 --- a/src/templates/pubkeyhash/index.js +++ b/src/templates/pubkeyhash/index.js @@ -1,17 +1,13 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const input = __importStar(require('./input')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const input = __importStar(require("./input")); exports.input = input; -const output = __importStar(require('./output')); +const output = __importStar(require("./output")); exports.output = output; diff --git a/src/templates/pubkeyhash/input.js b/src/templates/pubkeyhash/input.js index ec869cace..0c13041a3 100644 --- a/src/templates/pubkeyhash/input.js +++ b/src/templates/pubkeyhash/input.js @@ -1,27 +1,21 @@ -'use strict'; +"use strict"; // {signature} {pubKey} -var __importStar = - (this && this.__importStar) || - function(mod) { +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bscript = __importStar(require('../../script')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = __importStar(require("../../script")); function check(script) { - const chunks = bscript.decompile(script); - return ( - chunks.length === 2 && - bscript.isCanonicalScriptSignature(chunks[0]) && - bscript.isCanonicalPubKey(chunks[1]) - ); + const chunks = bscript.decompile(script); + return (chunks.length === 2 && + bscript.isCanonicalScriptSignature(chunks[0]) && + bscript.isCanonicalPubKey(chunks[1])); } exports.check = check; check.toJSON = () => { - return 'pubKeyHash input'; + return 'pubKeyHash input'; }; diff --git a/src/templates/pubkeyhash/output.js b/src/templates/pubkeyhash/output.js index 98a708ff4..32b7be5f5 100644 --- a/src/templates/pubkeyhash/output.js +++ b/src/templates/pubkeyhash/output.js @@ -1,31 +1,25 @@ -'use strict'; +"use strict"; // OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG -var __importStar = - (this && this.__importStar) || - function(mod) { +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bscript = __importStar(require('../../script')); -const script_1 = require('../../script'); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = __importStar(require("../../script")); +const script_1 = require("../../script"); function check(script) { - const buffer = bscript.compile(script); - return ( - buffer.length === 25 && - buffer[0] === script_1.OPS.OP_DUP && - buffer[1] === script_1.OPS.OP_HASH160 && - buffer[2] === 0x14 && - buffer[23] === script_1.OPS.OP_EQUALVERIFY && - buffer[24] === script_1.OPS.OP_CHECKSIG - ); + const buffer = bscript.compile(script); + return (buffer.length === 25 && + buffer[0] === script_1.OPS.OP_DUP && + buffer[1] === script_1.OPS.OP_HASH160 && + buffer[2] === 0x14 && + buffer[23] === script_1.OPS.OP_EQUALVERIFY && + buffer[24] === script_1.OPS.OP_CHECKSIG); } exports.check = check; check.toJSON = () => { - return 'pubKeyHash output'; + return 'pubKeyHash output'; }; diff --git a/src/templates/scripthash/index.js b/src/templates/scripthash/index.js index a010c5408..218d0d404 100644 --- a/src/templates/scripthash/index.js +++ b/src/templates/scripthash/index.js @@ -1,17 +1,13 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const input = __importStar(require('./input')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const input = __importStar(require("./input")); exports.input = input; -const output = __importStar(require('./output')); +const output = __importStar(require("./output")); exports.output = output; diff --git a/src/templates/scripthash/input.js b/src/templates/scripthash/input.js index 81ec82a5f..3009335ae 100644 --- a/src/templates/scripthash/input.js +++ b/src/templates/scripthash/input.js @@ -1,61 +1,51 @@ -'use strict'; +"use strict"; // {serialized scriptPubKey script} -var __importStar = - (this && this.__importStar) || - function(mod) { +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bscript = __importStar(require('../../script')); -const p2ms = __importStar(require('../multisig')); -const p2pk = __importStar(require('../pubkey')); -const p2pkh = __importStar(require('../pubkeyhash')); -const p2wpkho = __importStar(require('../witnesspubkeyhash/output')); -const p2wsho = __importStar(require('../witnessscripthash/output')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = __importStar(require("../../script")); +const p2ms = __importStar(require("../multisig")); +const p2pk = __importStar(require("../pubkey")); +const p2pkh = __importStar(require("../pubkeyhash")); +const p2wpkho = __importStar(require("../witnesspubkeyhash/output")); +const p2wsho = __importStar(require("../witnessscripthash/output")); function check(script, allowIncomplete) { - const chunks = bscript.decompile(script); - if (chunks.length < 1) return false; - const lastChunk = chunks[chunks.length - 1]; - if (!Buffer.isBuffer(lastChunk)) return false; - const scriptSigChunks = bscript.decompile( - bscript.compile(chunks.slice(0, -1)), - ); - const redeemScriptChunks = bscript.decompile(lastChunk); - // is redeemScript a valid script? - if (!redeemScriptChunks) return false; - // is redeemScriptSig push only? - if (!bscript.isPushOnly(scriptSigChunks)) return false; - // is witness? - if (chunks.length === 1) { - return ( - p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks) - ); - } - // match types - if ( - p2pkh.input.check(scriptSigChunks) && - p2pkh.output.check(redeemScriptChunks) - ) - return true; - if ( - p2ms.input.check(scriptSigChunks, allowIncomplete) && - p2ms.output.check(redeemScriptChunks) - ) - return true; - if ( - p2pk.input.check(scriptSigChunks) && - p2pk.output.check(redeemScriptChunks) - ) - return true; - return false; + const chunks = bscript.decompile(script); + if (chunks.length < 1) + return false; + const lastChunk = chunks[chunks.length - 1]; + if (!Buffer.isBuffer(lastChunk)) + return false; + const scriptSigChunks = bscript.decompile(bscript.compile(chunks.slice(0, -1))); + const redeemScriptChunks = bscript.decompile(lastChunk); + // is redeemScript a valid script? + if (!redeemScriptChunks) + return false; + // is redeemScriptSig push only? + if (!bscript.isPushOnly(scriptSigChunks)) + return false; + // is witness? + if (chunks.length === 1) { + return (p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks)); + } + // match types + if (p2pkh.input.check(scriptSigChunks) && + p2pkh.output.check(redeemScriptChunks)) + return true; + if (p2ms.input.check(scriptSigChunks, allowIncomplete) && + p2ms.output.check(redeemScriptChunks)) + return true; + if (p2pk.input.check(scriptSigChunks) && + p2pk.output.check(redeemScriptChunks)) + return true; + return false; } exports.check = check; check.toJSON = () => { - return 'scriptHash input'; + return 'scriptHash input'; }; diff --git a/src/templates/scripthash/output.js b/src/templates/scripthash/output.js index 39af5731e..addd738c0 100644 --- a/src/templates/scripthash/output.js +++ b/src/templates/scripthash/output.js @@ -1,29 +1,23 @@ -'use strict'; +"use strict"; // OP_HASH160 {scriptHash} OP_EQUAL -var __importStar = - (this && this.__importStar) || - function(mod) { +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bscript = __importStar(require('../../script')); -const script_1 = require('../../script'); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = __importStar(require("../../script")); +const script_1 = require("../../script"); function check(script) { - const buffer = bscript.compile(script); - return ( - buffer.length === 23 && - buffer[0] === script_1.OPS.OP_HASH160 && - buffer[1] === 0x14 && - buffer[22] === script_1.OPS.OP_EQUAL - ); + const buffer = bscript.compile(script); + return (buffer.length === 23 && + buffer[0] === script_1.OPS.OP_HASH160 && + buffer[1] === 0x14 && + buffer[22] === script_1.OPS.OP_EQUAL); } exports.check = check; check.toJSON = () => { - return 'scriptHash output'; + return 'scriptHash output'; }; diff --git a/src/templates/witnesscommitment/index.js b/src/templates/witnesscommitment/index.js index 16e612b37..7db518569 100644 --- a/src/templates/witnesscommitment/index.js +++ b/src/templates/witnesscommitment/index.js @@ -1,15 +1,11 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const output = __importStar(require('./output')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const output = __importStar(require("./output")); exports.output = output; diff --git a/src/templates/witnesscommitment/output.js b/src/templates/witnesscommitment/output.js index b843c5f3c..938b579f3 100644 --- a/src/templates/witnesscommitment/output.js +++ b/src/templates/witnesscommitment/output.js @@ -1,45 +1,39 @@ -'use strict'; +"use strict"; // OP_RETURN {aa21a9ed} {commitment} -var __importStar = - (this && this.__importStar) || - function(mod) { +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bscript = __importStar(require('../../script')); -const script_1 = require('../../script'); -const types = __importStar(require('../../types')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = __importStar(require("../../script")); +const script_1 = require("../../script"); +const types = __importStar(require("../../types")); const typeforce = require('typeforce'); const HEADER = Buffer.from('aa21a9ed', 'hex'); function check(script) { - const buffer = bscript.compile(script); - return ( - buffer.length > 37 && - buffer[0] === script_1.OPS.OP_RETURN && - buffer[1] === 0x24 && - buffer.slice(2, 6).equals(HEADER) - ); + const buffer = bscript.compile(script); + return (buffer.length > 37 && + buffer[0] === script_1.OPS.OP_RETURN && + buffer[1] === 0x24 && + buffer.slice(2, 6).equals(HEADER)); } exports.check = check; check.toJSON = () => { - return 'Witness commitment output'; + return 'Witness commitment output'; }; function encode(commitment) { - typeforce(types.Hash256bit, commitment); - const buffer = Buffer.allocUnsafe(36); - HEADER.copy(buffer, 0); - commitment.copy(buffer, 4); - return bscript.compile([script_1.OPS.OP_RETURN, buffer]); + typeforce(types.Hash256bit, commitment); + const buffer = Buffer.allocUnsafe(36); + HEADER.copy(buffer, 0); + commitment.copy(buffer, 4); + return bscript.compile([script_1.OPS.OP_RETURN, buffer]); } exports.encode = encode; function decode(buffer) { - typeforce(check, buffer); - return bscript.decompile(buffer)[1].slice(4, 36); + typeforce(check, buffer); + return bscript.decompile(buffer)[1].slice(4, 36); } exports.decode = decode; diff --git a/src/templates/witnesspubkeyhash/index.js b/src/templates/witnesspubkeyhash/index.js index a010c5408..218d0d404 100644 --- a/src/templates/witnesspubkeyhash/index.js +++ b/src/templates/witnesspubkeyhash/index.js @@ -1,17 +1,13 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const input = __importStar(require('./input')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const input = __importStar(require("./input")); exports.input = input; -const output = __importStar(require('./output')); +const output = __importStar(require("./output")); exports.output = output; diff --git a/src/templates/witnesspubkeyhash/input.js b/src/templates/witnesspubkeyhash/input.js index 820935f15..d98f08072 100644 --- a/src/templates/witnesspubkeyhash/input.js +++ b/src/templates/witnesspubkeyhash/input.js @@ -1,30 +1,24 @@ -'use strict'; +"use strict"; // {signature} {pubKey} -var __importStar = - (this && this.__importStar) || - function(mod) { +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bscript = __importStar(require('../../script')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = __importStar(require("../../script")); function isCompressedCanonicalPubKey(pubKey) { - return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33; + return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33; } function check(script) { - const chunks = bscript.decompile(script); - return ( - chunks.length === 2 && - bscript.isCanonicalScriptSignature(chunks[0]) && - isCompressedCanonicalPubKey(chunks[1]) - ); + const chunks = bscript.decompile(script); + return (chunks.length === 2 && + bscript.isCanonicalScriptSignature(chunks[0]) && + isCompressedCanonicalPubKey(chunks[1])); } exports.check = check; check.toJSON = () => { - return 'witnessPubKeyHash input'; + return 'witnessPubKeyHash input'; }; diff --git a/src/templates/witnesspubkeyhash/output.js b/src/templates/witnesspubkeyhash/output.js index 545251353..5ee0f07c6 100644 --- a/src/templates/witnesspubkeyhash/output.js +++ b/src/templates/witnesspubkeyhash/output.js @@ -1,28 +1,20 @@ -'use strict'; +"use strict"; // OP_0 {pubKeyHash} -var __importStar = - (this && this.__importStar) || - function(mod) { +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bscript = __importStar(require('../../script')); -const script_1 = require('../../script'); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = __importStar(require("../../script")); +const script_1 = require("../../script"); function check(script) { - const buffer = bscript.compile(script); - return ( - buffer.length === 22 && - buffer[0] === script_1.OPS.OP_0 && - buffer[1] === 0x14 - ); + const buffer = bscript.compile(script); + return buffer.length === 22 && buffer[0] === script_1.OPS.OP_0 && buffer[1] === 0x14; } exports.check = check; check.toJSON = () => { - return 'Witness pubKeyHash output'; + return 'Witness pubKeyHash output'; }; diff --git a/src/templates/witnessscripthash/index.js b/src/templates/witnessscripthash/index.js index a010c5408..218d0d404 100644 --- a/src/templates/witnessscripthash/index.js +++ b/src/templates/witnessscripthash/index.js @@ -1,17 +1,13 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const input = __importStar(require('./input')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const input = __importStar(require("./input")); exports.input = input; -const output = __importStar(require('./output')); +const output = __importStar(require("./output")); exports.output = output; diff --git a/src/templates/witnessscripthash/input.js b/src/templates/witnessscripthash/input.js index be2a82eeb..a1ac77663 100644 --- a/src/templates/witnessscripthash/input.js +++ b/src/templates/witnessscripthash/input.js @@ -1,50 +1,43 @@ -'use strict'; +"use strict"; // {serialized scriptPubKey script} -var __importStar = - (this && this.__importStar) || - function(mod) { +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bscript = __importStar(require('../../script')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = __importStar(require("../../script")); const typeforce = require('typeforce'); -const p2ms = __importStar(require('../multisig')); -const p2pk = __importStar(require('../pubkey')); -const p2pkh = __importStar(require('../pubkeyhash')); +const p2ms = __importStar(require("../multisig")); +const p2pk = __importStar(require("../pubkey")); +const p2pkh = __importStar(require("../pubkeyhash")); function check(chunks, allowIncomplete) { - typeforce(typeforce.Array, chunks); - if (chunks.length < 1) return false; - const witnessScript = chunks[chunks.length - 1]; - if (!Buffer.isBuffer(witnessScript)) return false; - const witnessScriptChunks = bscript.decompile(witnessScript); - // is witnessScript a valid script? - if (!witnessScriptChunks || witnessScriptChunks.length === 0) return false; - const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1)); - // match types - if ( - p2pkh.input.check(witnessRawScriptSig) && - p2pkh.output.check(witnessScriptChunks) - ) - return true; - if ( - p2ms.input.check(witnessRawScriptSig, allowIncomplete) && - p2ms.output.check(witnessScriptChunks) - ) - return true; - if ( - p2pk.input.check(witnessRawScriptSig) && - p2pk.output.check(witnessScriptChunks) - ) - return true; - return false; + typeforce(typeforce.Array, chunks); + if (chunks.length < 1) + return false; + const witnessScript = chunks[chunks.length - 1]; + if (!Buffer.isBuffer(witnessScript)) + return false; + const witnessScriptChunks = bscript.decompile(witnessScript); + // is witnessScript a valid script? + if (!witnessScriptChunks || witnessScriptChunks.length === 0) + return false; + const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1)); + // match types + if (p2pkh.input.check(witnessRawScriptSig) && + p2pkh.output.check(witnessScriptChunks)) + return true; + if (p2ms.input.check(witnessRawScriptSig, allowIncomplete) && + p2ms.output.check(witnessScriptChunks)) + return true; + if (p2pk.input.check(witnessRawScriptSig) && + p2pk.output.check(witnessScriptChunks)) + return true; + return false; } exports.check = check; check.toJSON = () => { - return 'witnessScriptHash input'; + return 'witnessScriptHash input'; }; diff --git a/src/templates/witnessscripthash/output.js b/src/templates/witnessscripthash/output.js index 9159204ef..7b95a1b6e 100644 --- a/src/templates/witnessscripthash/output.js +++ b/src/templates/witnessscripthash/output.js @@ -1,28 +1,20 @@ -'use strict'; +"use strict"; // OP_0 {scriptHash} -var __importStar = - (this && this.__importStar) || - function(mod) { +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bscript = __importStar(require('../../script')); -const script_1 = require('../../script'); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bscript = __importStar(require("../../script")); +const script_1 = require("../../script"); function check(script) { - const buffer = bscript.compile(script); - return ( - buffer.length === 34 && - buffer[0] === script_1.OPS.OP_0 && - buffer[1] === 0x20 - ); + const buffer = bscript.compile(script); + return buffer.length === 34 && buffer[0] === script_1.OPS.OP_0 && buffer[1] === 0x20; } exports.check = check; check.toJSON = () => { - return 'Witness scriptHash output'; + return 'Witness scriptHash output'; }; diff --git a/src/transaction.js b/src/transaction.js index 5475e01a4..792853b6b 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -1,37 +1,27 @@ -'use strict'; -var __importStar = - (this && this.__importStar) || - function(mod) { +"use strict"; +var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) - for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result["default"] = mod; return result; - }; -Object.defineProperty(exports, '__esModule', { value: true }); -const bufferutils_1 = require('./bufferutils'); -const bcrypto = __importStar(require('./crypto')); -const bscript = __importStar(require('./script')); -const script_1 = require('./script'); -const types = __importStar(require('./types')); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const bufferutils_1 = require("./bufferutils"); +const bcrypto = __importStar(require("./crypto")); +const bscript = __importStar(require("./script")); +const script_1 = require("./script"); +const types = __importStar(require("./types")); const typeforce = require('typeforce'); const varuint = require('varuint-bitcoin'); function varSliceSize(someScript) { - const length = someScript.length; - return varuint.encodingLength(length) + length; + const length = someScript.length; + return varuint.encodingLength(length) + length; } const EMPTY_SCRIPT = Buffer.allocUnsafe(0); const EMPTY_WITNESS = []; -exports.ZERO = Buffer.from( - '0000000000000000000000000000000000000000000000000000000000000000', - 'hex', -); -const ONE = Buffer.from( - '0000000000000000000000000000000000000000000000000000000000000001', - 'hex', -); +exports.ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'); +const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex'); const WITNESS_SCALE_FACTOR = 4; const OUTPOINT_ISSUANCE_FLAG = (1 << 31) >>> 0; const OUTPOINT_PEGIN_FLAG = (1 << 30) >>> 0; @@ -39,637 +29,553 @@ const OUTPOINT_INDEX_MASK = 0x3fffffff; const MINUS_1 = 4294967295; const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex'); const BLANK_OUTPUT = { - script: EMPTY_SCRIPT, - asset: exports.ZERO, - nonce: exports.ZERO, - value: VALUE_UINT64_MAX, + script: EMPTY_SCRIPT, + asset: exports.ZERO, + nonce: exports.ZERO, + value: VALUE_UINT64_MAX, }; class Transaction { - constructor() { - this.version = 1; - this.locktime = 0; - this.flag = 0; - this.ins = []; - this.outs = []; - } - static fromBuffer(buffer, _NO_STRICT) { - const bufferReader = new bufferutils_1.BufferReader(buffer); - const tx = new Transaction(); - tx.version = bufferReader.readInt32(); - tx.flag = bufferReader.readUInt8(); - const vinLen = bufferReader.readVarInt(); - for (let i = 0; i < vinLen; ++i) { - const inHash = bufferReader.readSlice(32); - let inIndex = bufferReader.readUInt32(); - const inScript = bufferReader.readVarSlice(); - const inSequence = bufferReader.readUInt32(); - let inIsPegin = false; - let inIssuance; - if (inIndex !== MINUS_1) { - if (inIndex & OUTPOINT_ISSUANCE_FLAG) { - inIssuance = bufferReader.readIssuance(); + constructor() { + this.version = 1; + this.locktime = 0; + this.flag = 0; + this.ins = []; + this.outs = []; + } + static fromBuffer(buffer, _NO_STRICT) { + const bufferReader = new bufferutils_1.BufferReader(buffer); + const tx = new Transaction(); + tx.version = bufferReader.readInt32(); + tx.flag = bufferReader.readUInt8(); + const vinLen = bufferReader.readVarInt(); + for (let i = 0; i < vinLen; ++i) { + const inHash = bufferReader.readSlice(32); + let inIndex = bufferReader.readUInt32(); + const inScript = bufferReader.readVarSlice(); + const inSequence = bufferReader.readUInt32(); + let inIsPegin = false; + let inIssuance; + if (inIndex !== MINUS_1) { + if (inIndex & OUTPOINT_ISSUANCE_FLAG) { + inIssuance = bufferReader.readIssuance(); + } + if (inIndex & OUTPOINT_PEGIN_FLAG) { + inIsPegin = true; + } + inIndex &= OUTPOINT_INDEX_MASK; + } + tx.ins.push({ + hash: inHash, + index: inIndex, + script: inScript, + sequence: inSequence, + witness: EMPTY_WITNESS, + isPegin: inIsPegin, + issuance: inIssuance, + peginWitness: EMPTY_WITNESS, + issuanceRangeProof: EMPTY_SCRIPT, + inflationRangeProof: EMPTY_SCRIPT, + }); + } + const voutLen = bufferReader.readVarInt(); + for (let i = 0; i < voutLen; ++i) { + const asset = bufferReader.readConfidentialAsset(); + const value = bufferReader.readConfidentialValue(); + const nonce = bufferReader.readConfidentialNonce(); + const script = bufferReader.readVarSlice(); + tx.outs.push({ + asset, + value, + nonce, + script, + rangeProof: EMPTY_SCRIPT, + surjectionProof: EMPTY_SCRIPT, + }); + } + tx.locktime = bufferReader.readUInt32(); + if (tx.flag === 1) { + for (let i = 0; i < vinLen; ++i) { + const { witness, peginWitness, issuanceRangeProof, inflationRangeProof, } = bufferReader.readConfidentialInFields(); + tx.ins[i].witness = witness; + tx.ins[i].peginWitness = peginWitness; + tx.ins[i].issuanceRangeProof = issuanceRangeProof; + tx.ins[i].inflationRangeProof = inflationRangeProof; + } + for (let i = 0; i < voutLen; ++i) { + const { rangeProof, surjectionProof, } = bufferReader.readConfidentialOutFields(); + tx.outs[i].rangeProof = rangeProof; + tx.outs[i].surjectionProof = surjectionProof; + } } - if (inIndex & OUTPOINT_PEGIN_FLAG) { - inIsPegin = true; + if (_NO_STRICT) + return tx; + if (bufferReader.offset !== buffer.length) + throw new Error('Transaction has unexpected data'); + return tx; + } + static fromHex(hex) { + return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false); + } + static isCoinbaseHash(buffer) { + typeforce(types.Hash256bit, buffer); + for (let i = 0; i < 32; ++i) { + if (buffer[i] !== 0) + return false; } - inIndex &= OUTPOINT_INDEX_MASK; - } - tx.ins.push({ - hash: inHash, - index: inIndex, - script: inScript, - sequence: inSequence, - witness: EMPTY_WITNESS, - isPegin: inIsPegin, - issuance: inIssuance, - peginWitness: EMPTY_WITNESS, - issuanceRangeProof: EMPTY_SCRIPT, - inflationRangeProof: EMPTY_SCRIPT, - }); + return true; } - const voutLen = bufferReader.readVarInt(); - for (let i = 0; i < voutLen; ++i) { - const asset = bufferReader.readConfidentialAsset(); - const value = bufferReader.readConfidentialValue(); - const nonce = bufferReader.readConfidentialNonce(); - const script = bufferReader.readVarSlice(); - tx.outs.push({ - asset, - value, - nonce, - script, - rangeProof: EMPTY_SCRIPT, - surjectionProof: EMPTY_SCRIPT, - }); + isCoinbase() { + return (this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)); } - tx.locktime = bufferReader.readUInt32(); - if (tx.flag === 1) { - for (let i = 0; i < vinLen; ++i) { - const { - witness, - peginWitness, - issuanceRangeProof, - inflationRangeProof, - } = bufferReader.readConfidentialInFields(); - tx.ins[i].witness = witness; - tx.ins[i].peginWitness = peginWitness; - tx.ins[i].issuanceRangeProof = issuanceRangeProof; - tx.ins[i].inflationRangeProof = inflationRangeProof; - } - for (let i = 0; i < voutLen; ++i) { - const { - rangeProof, - surjectionProof, - } = bufferReader.readConfidentialOutFields(); - tx.outs[i].rangeProof = rangeProof; - tx.outs[i].surjectionProof = surjectionProof; - } + // A quick and reliable way to validate that all the buffers are of correct type and length + validateIssuance(assetBlindingNonce, assetEntropy, assetAmount, tokenAmount) { + typeforce(types.Hash256bit, assetBlindingNonce); + typeforce(types.Hash256bit, assetEntropy); + typeforce(types.oneOf(types.ConfidentialValue, types.ConfidentialCommitment, types.BufferOne), assetAmount); + typeforce(types.oneOf(types.ConfidentialValue, types.ConfidentialCommitment, types.BufferOne), tokenAmount); + return true; } - if (_NO_STRICT) return tx; - if (bufferReader.offset !== buffer.length) - throw new Error('Transaction has unexpected data'); - return tx; - } - static fromHex(hex) { - return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false); - } - static isCoinbaseHash(buffer) { - typeforce(types.Hash256bit, buffer); - for (let i = 0; i < 32; ++i) { - if (buffer[i] !== 0) return false; + addInput(hash, index, sequence, scriptSig, issuance) { + typeforce(types.tuple(types.Hash256bit, types.UInt32, types.maybe(types.UInt32), types.maybe(types.Buffer), types.maybe(types.Object)), arguments); + let isPegin = false; + if (index !== MINUS_1) { + if (index & OUTPOINT_ISSUANCE_FLAG) { + if (!issuance) { + throw new Error('Issuance flag has been set but the Issuance object is not defined or invalid'); + } + else + this.validateIssuance(issuance.assetBlindingNonce, issuance.assetEntropy, issuance.assetAmount, issuance.tokenAmount); + } + if (index & OUTPOINT_PEGIN_FLAG) { + isPegin = true; + } + index &= OUTPOINT_INDEX_MASK; + } + // Add the input and return the input's index + return (this.ins.push({ + hash, + index, + isPegin, + issuance, + witness: EMPTY_WITNESS, + peginWitness: EMPTY_WITNESS, + issuanceRangeProof: EMPTY_SCRIPT, + inflationRangeProof: EMPTY_SCRIPT, + script: scriptSig || EMPTY_SCRIPT, + sequence: sequence || Transaction.DEFAULT_SEQUENCE, + }) - 1); } - return true; - } - isCoinbase() { - return ( - this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash) - ); - } - // A quick and reliable way to validate that all the buffers are of correct type and length - validateIssuance(assetBlindingNonce, assetEntropy, assetAmount, tokenAmount) { - typeforce(types.Hash256bit, assetBlindingNonce); - typeforce(types.Hash256bit, assetEntropy); - typeforce( - types.oneOf( - types.ConfidentialValue, - types.ConfidentialCommitment, - types.BufferOne, - ), - assetAmount, - ); - typeforce( - types.oneOf( - types.ConfidentialValue, - types.ConfidentialCommitment, - types.BufferOne, - ), - tokenAmount, - ); - return true; - } - addInput(hash, index, sequence, scriptSig, issuance) { - typeforce( - types.tuple( - types.Hash256bit, - types.UInt32, - types.maybe(types.UInt32), - types.maybe(types.Buffer), - types.maybe(types.Object), - ), - arguments, - ); - let isPegin = false; - if (index !== MINUS_1) { - if (index & OUTPOINT_ISSUANCE_FLAG) { - if (!issuance) { - throw new Error( - 'Issuance flag has been set but the Issuance object is not defined or invalid', - ); - } else - this.validateIssuance( - issuance.assetBlindingNonce, - issuance.assetEntropy, - issuance.assetAmount, - issuance.tokenAmount, - ); - } - if (index & OUTPOINT_PEGIN_FLAG) { - isPegin = true; - } - index &= OUTPOINT_INDEX_MASK; + addOutput(scriptPubKey, value, asset, nonce, rangeProof, surjectionProof) { + typeforce(types.tuple(types.Buffer, types.oneOf(types.ConfidentialValue, types.ConfidentialCommitment, types.BufferOne), types.oneOf(types.ConfidentialCommitment, types.BufferOne), types.oneOf(types.ConfidentialCommitment, types.BufferOne), types.maybe(types.Buffer), types.maybe(types.Buffer)), arguments); + // Add the output and return the output's index + return (this.outs.push({ + script: scriptPubKey, + value, + asset, + nonce, + rangeProof: rangeProof || EMPTY_SCRIPT, + surjectionProof: surjectionProof || EMPTY_SCRIPT, + }) - 1); } - // Add the input and return the input's index - return ( - this.ins.push({ - hash, - index, - isPegin, - issuance, - witness: EMPTY_WITNESS, - peginWitness: EMPTY_WITNESS, - issuanceRangeProof: EMPTY_SCRIPT, - inflationRangeProof: EMPTY_SCRIPT, - script: scriptSig || EMPTY_SCRIPT, - sequence: sequence || Transaction.DEFAULT_SEQUENCE, - }) - 1 - ); - } - addOutput(scriptPubKey, value, asset, nonce, rangeProof, surjectionProof) { - typeforce( - types.tuple( - types.Buffer, - types.oneOf( - types.ConfidentialValue, - types.ConfidentialCommitment, - types.BufferOne, - ), - types.oneOf(types.ConfidentialCommitment, types.BufferOne), - types.oneOf(types.ConfidentialCommitment, types.BufferOne), - types.maybe(types.Buffer), - types.maybe(types.Buffer), - ), - arguments, - ); - // Add the output and return the output's index - return ( - this.outs.push({ - script: scriptPubKey, - value, - asset, - nonce, - rangeProof: rangeProof || EMPTY_SCRIPT, - surjectionProof: surjectionProof || EMPTY_SCRIPT, - }) - 1 - ); - } - hasWitnesses() { - return ( - this.flag === 1 || - this.ins.some(x => { - return x.witness.length !== 0; - }) || - this.outs.some(x => { - return x.rangeProof.length !== 0 && x.surjectionProof.length !== 0; - }) - ); - } - weight() { - const base = this.__byteLength(false); - const total = this.__byteLength(true); - return base * (WITNESS_SCALE_FACTOR - 1) + total; - } - virtualSize() { - const vsize = - (this.weight() + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR; - return Math.floor(vsize); - } - byteLength(_ALLOW_WITNESS) { - return this.__byteLength(_ALLOW_WITNESS || true); - } - clone() { - const newTx = new Transaction(); - newTx.version = this.version; - newTx.locktime = this.locktime; - newTx.flag = this.flag; - newTx.ins = this.ins.map(txIn => { - return { - hash: txIn.hash, - index: txIn.index, - script: txIn.script, - sequence: txIn.sequence, - witness: txIn.witness, - isPegin: txIn.isPegin, - issuance: txIn.issuance, - peginWitness: txIn.peginWitness, - issuanceRangeProof: txIn.issuanceRangeProof, - inflationRangeProof: txIn.inflationRangeProof, - }; - }); - newTx.outs = this.outs.map(txOut => { - return { - script: txOut.script, - value: txOut.value, - asset: txOut.asset, - nonce: txOut.nonce, - rangeProof: txOut.rangeProof, - surjectionProof: txOut.surjectionProof, - }; - }); - return newTx; - } - /** - * Hash transaction for signing a specific input. - * - * Bitcoin uses a different hash for each signed transaction input. - * This method copies the transaction, makes the necessary changes based on the - * hashType, and then hashes the result. - * This hash can then be used to sign the provided transaction input. - */ - hashForSignature(inIndex, prevOutScript, hashType) { - typeforce( - types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), - arguments, - ); - // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29 - if (inIndex >= this.ins.length) return ONE; - // ignore OP_CODESEPARATOR - const ourScript = bscript.compile( - bscript.decompile(prevOutScript).filter(x => { - return x !== script_1.OPS.OP_CODESEPARATOR; - }), - ); - const txTmp = this.clone(); - // SIGHASH_NONE: ignore all outputs? (wildcard payee) - if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) { - txTmp.outs = []; - // ignore sequence numbers (except at inIndex) - txTmp.ins.forEach((input, i) => { - if (i === inIndex) return; - input.sequence = 0; - }); - // SIGHASH_SINGLE: ignore all outputs, except at the same index? - } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) { - // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60 - if (inIndex >= this.outs.length) return ONE; - // truncate outputs after - txTmp.outs.length = inIndex + 1; - // "blank" outputs before - for (let i = 0; i < inIndex; i++) { - txTmp.outs[i] = BLANK_OUTPUT; - } - // ignore sequence numbers (except at inIndex) - txTmp.ins.forEach((input, y) => { - if (y === inIndex) return; - input.sequence = 0; - }); + hasWitnesses() { + return (this.flag === 1 || + this.ins.some(x => { + return x.witness.length !== 0; + }) || + this.outs.some(x => { + return x.rangeProof.length !== 0 && x.surjectionProof.length !== 0; + })); } - // SIGHASH_ANYONECANPAY: ignore inputs entirely? - if (hashType & Transaction.SIGHASH_ANYONECANPAY) { - txTmp.ins = [txTmp.ins[inIndex]]; - txTmp.ins[0].script = ourScript; - // SIGHASH_ALL: only ignore input scripts - } else { - // "blank" others input scripts - txTmp.ins.forEach(input => { - input.script = EMPTY_SCRIPT; - }); - txTmp.ins[inIndex].script = ourScript; + weight() { + const base = this.__byteLength(false); + const total = this.__byteLength(true); + return base * (WITNESS_SCALE_FACTOR - 1) + total; } - // serialize and hash - const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false, true) + 4); - buffer.writeInt32LE(hashType, buffer.length - 4); - txTmp.__toBuffer(buffer, 0, false, true, true); - return bcrypto.hash256(buffer); - } - hashForWitnessV0(inIndex, prevOutScript, value, hashType) { - typeforce( - types.tuple(types.UInt32, types.Buffer, types.Buffer, types.UInt32), - arguments, - ); - function writeInputs(ins) { - const tBuffer = Buffer.allocUnsafe(36 * ins.length); - const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); - ins.forEach(txIn => { - tBufferWriter.writeSlice(txIn.hash); - tBufferWriter.writeUInt32(txIn.index); - }); - return bcrypto.hash256(tBuffer); + virtualSize() { + const vsize = (this.weight() + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR; + return Math.floor(vsize); } - function writeSequences(ins) { - const tBuffer = Buffer.allocUnsafe(4 * ins.length); - const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); - ins.forEach(txIn => { - tBufferWriter.writeUInt32(txIn.sequence); - }); - return bcrypto.hash256(tBuffer); + byteLength(_ALLOW_WITNESS) { + return this.__byteLength(_ALLOW_WITNESS || true); } - function issuanceSize(ins) { - return ins.reduce( - (sum, txIn) => - !types.Null(txIn.issuance) - ? sum + - txIn.issuance.assetBlindingNonce.length + - txIn.issuance.assetEntropy.length + - txIn.issuance.assetAmount.length + - txIn.issuance.tokenAmount.length - : sum, // we'll use the empty 00 Buffer if issuance is not set - 0, - ); + clone() { + const newTx = new Transaction(); + newTx.version = this.version; + newTx.locktime = this.locktime; + newTx.flag = this.flag; + newTx.ins = this.ins.map(txIn => { + return { + hash: txIn.hash, + index: txIn.index, + script: txIn.script, + sequence: txIn.sequence, + witness: txIn.witness, + isPegin: txIn.isPegin, + issuance: txIn.issuance, + peginWitness: txIn.peginWitness, + issuanceRangeProof: txIn.issuanceRangeProof, + inflationRangeProof: txIn.inflationRangeProof, + }; + }); + newTx.outs = this.outs.map(txOut => { + return { + script: txOut.script, + value: txOut.value, + asset: txOut.asset, + nonce: txOut.nonce, + rangeProof: txOut.rangeProof, + surjectionProof: txOut.surjectionProof, + }; + }); + return newTx; } - function writeIssuances(ins, sizeIssuances) { - const size = sizeIssuances === 0 ? ins.length : sizeIssuances; - const tBuffer = Buffer.allocUnsafe(size); - const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); - ins.forEach(txIn => { - if (!types.Null(txIn.issuance)) { - tBufferWriter.writeSlice(txIn.issuance.assetBlindingNonce); - tBufferWriter.writeSlice(txIn.issuance.assetEntropy); - tBufferWriter.writeSlice(txIn.issuance.assetAmount); - tBufferWriter.writeSlice(txIn.issuance.tokenAmount); - } else { - tBufferWriter.writeSlice(Buffer.from('00', 'hex')); + /** + * Hash transaction for signing a specific input. + * + * Bitcoin uses a different hash for each signed transaction input. + * This method copies the transaction, makes the necessary changes based on the + * hashType, and then hashes the result. + * This hash can then be used to sign the provided transaction input. + */ + hashForSignature(inIndex, prevOutScript, hashType) { + typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments); + // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29 + if (inIndex >= this.ins.length) + return ONE; + // ignore OP_CODESEPARATOR + const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(x => { + return x !== script_1.OPS.OP_CODESEPARATOR; + })); + const txTmp = this.clone(); + // SIGHASH_NONE: ignore all outputs? (wildcard payee) + if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) { + txTmp.outs = []; + // ignore sequence numbers (except at inIndex) + txTmp.ins.forEach((input, i) => { + if (i === inIndex) + return; + input.sequence = 0; + }); + // SIGHASH_SINGLE: ignore all outputs, except at the same index? + } + else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) { + // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60 + if (inIndex >= this.outs.length) + return ONE; + // truncate outputs after + txTmp.outs.length = inIndex + 1; + // "blank" outputs before + for (let i = 0; i < inIndex; i++) { + txTmp.outs[i] = BLANK_OUTPUT; + } + // ignore sequence numbers (except at inIndex) + txTmp.ins.forEach((input, y) => { + if (y === inIndex) + return; + input.sequence = 0; + }); + } + // SIGHASH_ANYONECANPAY: ignore inputs entirely? + if (hashType & Transaction.SIGHASH_ANYONECANPAY) { + txTmp.ins = [txTmp.ins[inIndex]]; + txTmp.ins[0].script = ourScript; + // SIGHASH_ALL: only ignore input scripts + } + else { + // "blank" others input scripts + txTmp.ins.forEach(input => { + input.script = EMPTY_SCRIPT; + }); + txTmp.ins[inIndex].script = ourScript; + } + // serialize and hash + const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false, true) + 4); + buffer.writeInt32LE(hashType, buffer.length - 4); + txTmp.__toBuffer(buffer, 0, false, true, true); + return bcrypto.hash256(buffer); + } + hashForWitnessV0(inIndex, prevOutScript, value, hashType) { + typeforce(types.tuple(types.UInt32, types.Buffer, types.Buffer, types.UInt32), arguments); + function writeInputs(ins) { + const tBuffer = Buffer.allocUnsafe(36 * ins.length); + const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); + ins.forEach((txIn) => { + tBufferWriter.writeSlice(txIn.hash); + tBufferWriter.writeUInt32(txIn.index); + }); + return bcrypto.hash256(tBuffer); + } + function writeSequences(ins) { + const tBuffer = Buffer.allocUnsafe(4 * ins.length); + const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); + ins.forEach((txIn) => { + tBufferWriter.writeUInt32(txIn.sequence); + }); + return bcrypto.hash256(tBuffer); + } + function issuanceSize(ins) { + return ins.reduce((sum, txIn) => !types.Null(txIn.issuance) + ? sum + + txIn.issuance.assetBlindingNonce.length + + txIn.issuance.assetEntropy.length + + txIn.issuance.assetAmount.length + + txIn.issuance.tokenAmount.length + : sum, // we'll use the empty 00 Buffer if issuance is not set + 0); + } + function writeIssuances(ins, sizeIssuances) { + const size = sizeIssuances === 0 ? ins.length : sizeIssuances; + const tBuffer = Buffer.allocUnsafe(size); + const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); + ins.forEach((txIn) => { + if (!types.Null(txIn.issuance)) { + tBufferWriter.writeSlice(txIn.issuance.assetBlindingNonce); + tBufferWriter.writeSlice(txIn.issuance.assetEntropy); + tBufferWriter.writeSlice(txIn.issuance.assetAmount); + tBufferWriter.writeSlice(txIn.issuance.tokenAmount); + } + else { + tBufferWriter.writeSlice(Buffer.from('00', 'hex')); + } + }); + return bcrypto.hash256(tBuffer); } - }); - return bcrypto.hash256(tBuffer); + function writeOutputs(outs) { + const outsSize = outs.reduce((sum, txOut) => sum + + txOut.asset.length + + txOut.value.length + + txOut.nonce.length + + varSliceSize(txOut.script), 0); + const tBuffer = Buffer.allocUnsafe(outsSize); + const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); + outs.forEach((txOut) => { + tBufferWriter.writeSlice(txOut.asset); + tBufferWriter.writeSlice(txOut.value); + tBufferWriter.writeSlice(txOut.nonce); + tBufferWriter.writeVarSlice(txOut.script); + }); + return bcrypto.hash256(tBuffer); + } + let hashOutputs = exports.ZERO; + let hashPrevouts = exports.ZERO; + let hashSequences = exports.ZERO; + let hashIssuances = exports.ZERO; + let sizeOfIssuances = 0; + // Inputs + if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { + hashPrevouts = writeInputs(this.ins); + } + // Sequences + if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) && + (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && + (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { + hashSequences = writeSequences(this.ins); + } + // Issuances + if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { + sizeOfIssuances = issuanceSize(this.ins); + hashIssuances = writeIssuances(this.ins, sizeOfIssuances); + } + // Outputs + if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && + (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { + hashOutputs = writeOutputs(this.outs); + } + else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && + inIndex < this.outs.length) { + hashOutputs = writeOutputs([this.outs[inIndex]]); + } + const input = this.ins[inIndex]; + const hasIssuance = !types.Null(input.issuance); + const bufferSize = 4 + // version + hashPrevouts.length + + hashSequences.length + + hashIssuances.length + + input.hash.length + + 4 + // input.index + varSliceSize(prevOutScript) + + value.length + + 4 + // input.sequence + hashOutputs.length + + sizeOfIssuances + + 4 + // locktime + 4; // hashType + const buffer = Buffer.allocUnsafe(bufferSize); + const bufferWriter = new bufferutils_1.BufferWriter(buffer, 0); + bufferWriter.writeUInt32(this.version); + bufferWriter.writeSlice(hashPrevouts); + bufferWriter.writeSlice(hashSequences); + bufferWriter.writeSlice(hashIssuances); + bufferWriter.writeSlice(input.hash); + bufferWriter.writeUInt32(input.index); + bufferWriter.writeVarSlice(prevOutScript); + bufferWriter.writeSlice(value); + bufferWriter.writeUInt32(input.sequence); + if (hasIssuance) { + bufferWriter.writeSlice(input.issuance.assetBlindingNonce); + bufferWriter.writeSlice(input.issuance.assetEntropy); + bufferWriter.writeSlice(input.issuance.assetAmount); + bufferWriter.writeSlice(input.issuance.tokenAmount); + } + bufferWriter.writeSlice(hashOutputs); + bufferWriter.writeUInt32(this.locktime); + bufferWriter.writeUInt32(hashType); + return bcrypto.hash256(buffer); + } + getHash(forWitness) { + // wtxid for coinbase is always 32 bytes of 0x00 + if (forWitness && this.isCoinbase()) + return Buffer.alloc(32, 0); + return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness, true)); } - function writeOutputs(outs) { - const outsSize = outs.reduce( - (sum, txOut) => - sum + - txOut.asset.length + - txOut.value.length + - txOut.nonce.length + - varSliceSize(txOut.script), - 0, - ); - const tBuffer = Buffer.allocUnsafe(outsSize); - const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); - outs.forEach(txOut => { - tBufferWriter.writeSlice(txOut.asset); - tBufferWriter.writeSlice(txOut.value); - tBufferWriter.writeSlice(txOut.nonce); - tBufferWriter.writeVarSlice(txOut.script); - }); - return bcrypto.hash256(tBuffer); + getId() { + // transaction hash's are displayed in reverse order + return bufferutils_1.reverseBuffer(this.getHash(false)).toString('hex'); } - let hashOutputs = exports.ZERO; - let hashPrevouts = exports.ZERO; - let hashSequences = exports.ZERO; - let hashIssuances = exports.ZERO; - let sizeOfIssuances = 0; - // Inputs - if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { - hashPrevouts = writeInputs(this.ins); + toBuffer(buffer, initialOffset) { + return this.__toBuffer(buffer, initialOffset, true, false); } - // Sequences - if ( - !(hashType & Transaction.SIGHASH_ANYONECANPAY) && - (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && - (hashType & 0x1f) !== Transaction.SIGHASH_NONE - ) { - hashSequences = writeSequences(this.ins); + toHex() { + return this.toBuffer(undefined, undefined).toString('hex'); } - // Issuances - if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { - sizeOfIssuances = issuanceSize(this.ins); - hashIssuances = writeIssuances(this.ins, sizeOfIssuances); + setInputScript(index, scriptSig) { + typeforce(types.tuple(types.Number, types.Buffer), arguments); + this.ins[index].script = scriptSig; } - // Outputs - if ( - (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && - (hashType & 0x1f) !== Transaction.SIGHASH_NONE - ) { - hashOutputs = writeOutputs(this.outs); - } else if ( - (hashType & 0x1f) === Transaction.SIGHASH_SINGLE && - inIndex < this.outs.length - ) { - hashOutputs = writeOutputs([this.outs[inIndex]]); + setWitness(index, witness) { + typeforce(types.tuple(types.Number, [types.Buffer]), arguments); + this.ins[index].witness = witness; } - const input = this.ins[inIndex]; - const hasIssuance = !types.Null(input.issuance); - const bufferSize = - 4 + // version - hashPrevouts.length + - hashSequences.length + - hashIssuances.length + - input.hash.length + - 4 + // input.index - varSliceSize(prevOutScript) + - value.length + - 4 + // input.sequence - hashOutputs.length + - sizeOfIssuances + - 4 + // locktime - 4; // hashType - const buffer = Buffer.allocUnsafe(bufferSize); - const bufferWriter = new bufferutils_1.BufferWriter(buffer, 0); - bufferWriter.writeUInt32(this.version); - bufferWriter.writeSlice(hashPrevouts); - bufferWriter.writeSlice(hashSequences); - bufferWriter.writeSlice(hashIssuances); - bufferWriter.writeSlice(input.hash); - bufferWriter.writeUInt32(input.index); - bufferWriter.writeVarSlice(prevOutScript); - bufferWriter.writeSlice(value); - bufferWriter.writeUInt32(input.sequence); - if (hasIssuance) { - bufferWriter.writeSlice(input.issuance.assetBlindingNonce); - bufferWriter.writeSlice(input.issuance.assetEntropy); - bufferWriter.writeSlice(input.issuance.assetAmount); - bufferWriter.writeSlice(input.issuance.tokenAmount); + setPeginWitness(index, peginWitness) { + typeforce(types.tuple(types.Number, [types.Buffer]), arguments); + this.ins[index].peginWitness = peginWitness; } - bufferWriter.writeSlice(hashOutputs); - bufferWriter.writeUInt32(this.locktime); - bufferWriter.writeUInt32(hashType); - return bcrypto.hash256(buffer); - } - getHash(forWitness) { - // wtxid for coinbase is always 32 bytes of 0x00 - if (forWitness && this.isCoinbase()) return Buffer.alloc(32, 0); - return bcrypto.hash256( - this.__toBuffer(undefined, undefined, forWitness, true), - ); - } - getId() { - // transaction hash's are displayed in reverse order - return bufferutils_1.reverseBuffer(this.getHash(false)).toString('hex'); - } - toBuffer(buffer, initialOffset) { - return this.__toBuffer(buffer, initialOffset, true, false); - } - toHex() { - return this.toBuffer(undefined, undefined).toString('hex'); - } - setInputScript(index, scriptSig) { - typeforce(types.tuple(types.Number, types.Buffer), arguments); - this.ins[index].script = scriptSig; - } - setWitness(index, witness) { - typeforce(types.tuple(types.Number, [types.Buffer]), arguments); - this.ins[index].witness = witness; - } - setPeginWitness(index, peginWitness) { - typeforce(types.tuple(types.Number, [types.Buffer]), arguments); - this.ins[index].peginWitness = peginWitness; - } - setInputIssuanceRangeProof(index, issuanceRangeProof) { - typeforce(types.tuple(types.Buffer), arguments); - if (this.ins[index].issuance === undefined) - throw new Error('Issuance not set for input #' + index); - this.ins[index].issuanceRangeProof = issuanceRangeProof; - } - setInputInflationRangeProof(index, inflationRangeProof) { - typeforce(types.tuple(types.Buffer), arguments); - if (this.ins[index].issuance === undefined) - throw new Error('Issuance not set for input #' + index); - this.ins[index].inflationRangeProof = inflationRangeProof; - } - setOutputNonce(index, nonce) { - typeforce(types.tuple(types.Number, types.Buffer), arguments); - this.outs[index].nonce = nonce; - } - setOutputRangeProof(index, proof) { - typeforce(types.tuple(types.Number, types.Buffer), arguments); - this.outs[index].rangeProof = proof; - } - setOutputSurjectionProof(index, proof) { - typeforce(types.tuple(types.Number, types.Buffer), arguments); - this.outs[index].surjectionProof = proof; - } - __byteLength(_ALLOW_WITNESS, forSignature) { - const extraByte = forSignature ? 0 : 1; - let size = - 8 + - extraByte + - varuint.encodingLength(this.ins.length) + - varuint.encodingLength(this.outs.length); - for (const txIn of this.ins) { - size += 40 + varSliceSize(txIn.script); - if (txIn.issuance) { - size += - 64 + - txIn.issuance.assetAmount.length + - txIn.issuance.tokenAmount.length; - } + setInputIssuanceRangeProof(index, issuanceRangeProof) { + typeforce(types.tuple(types.Buffer), arguments); + if (this.ins[index].issuance === undefined) + throw new Error('Issuance not set for input #' + index); + this.ins[index].issuanceRangeProof = issuanceRangeProof; } - for (const txOut of this.outs) { - size += - txOut.asset.length + - txOut.value.length + - txOut.nonce.length + - varSliceSize(txOut.script); + setInputInflationRangeProof(index, inflationRangeProof) { + typeforce(types.tuple(types.Buffer), arguments); + if (this.ins[index].issuance === undefined) + throw new Error('Issuance not set for input #' + index); + this.ins[index].inflationRangeProof = inflationRangeProof; } - if (_ALLOW_WITNESS && this.hasWitnesses()) { - for (const txIn of this.ins) { - size += varSliceSize(txIn.issuanceRangeProof); - size += varSliceSize(txIn.inflationRangeProof); - size += varuint.encodingLength(txIn.witness.length); - for (const wit of txIn.witness) { - size += varSliceSize(wit); + setOutputNonce(index, nonce) { + typeforce(types.tuple(types.Number, types.Buffer), arguments); + this.outs[index].nonce = nonce; + } + setOutputRangeProof(index, proof) { + typeforce(types.tuple(types.Number, types.Buffer), arguments); + this.outs[index].rangeProof = proof; + } + setOutputSurjectionProof(index, proof) { + typeforce(types.tuple(types.Number, types.Buffer), arguments); + this.outs[index].surjectionProof = proof; + } + __byteLength(_ALLOW_WITNESS, forSignature) { + const extraByte = forSignature ? 0 : 1; + let size = 8 + + extraByte + + varuint.encodingLength(this.ins.length) + + varuint.encodingLength(this.outs.length); + for (const txIn of this.ins) { + size += 40 + varSliceSize(txIn.script); + if (txIn.issuance) { + size += + 64 + + txIn.issuance.assetAmount.length + + txIn.issuance.tokenAmount.length; + } } - size += varuint.encodingLength((txIn.peginWitness || []).length); - for (const wit of txIn.peginWitness || []) { - size += varSliceSize(wit); + for (const txOut of this.outs) { + size += + txOut.asset.length + + txOut.value.length + + txOut.nonce.length + + varSliceSize(txOut.script); } - } - for (const txOut of this.outs) { - size += varSliceSize(txOut.surjectionProof); - size += varSliceSize(txOut.rangeProof); - } - } - return size; - } - __toBuffer( - buffer, - initialOffset, - _ALLOW_WITNESS, - forceZeroFlag, - forSignature, - ) { - if (!buffer) - buffer = Buffer.allocUnsafe( - this.__byteLength(_ALLOW_WITNESS, forSignature), - ); - const bufferWriter = new bufferutils_1.BufferWriter( - buffer, - initialOffset || 0, - ); - bufferWriter.writeInt32(this.version); - const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); - if (!forSignature) { - let value = Transaction.ADVANCED_TRANSACTION_MARKER; - if (hasWitnesses && !forceZeroFlag) { - value = Transaction.ADVANCED_TRANSACTION_FLAG; - } - bufferWriter.writeUInt8(value); + if (_ALLOW_WITNESS && this.hasWitnesses()) { + for (const txIn of this.ins) { + size += varSliceSize(txIn.issuanceRangeProof); + size += varSliceSize(txIn.inflationRangeProof); + size += varuint.encodingLength(txIn.witness.length); + for (const wit of txIn.witness) { + size += varSliceSize(wit); + } + size += varuint.encodingLength((txIn.peginWitness || []).length); + for (const wit of txIn.peginWitness || []) { + size += varSliceSize(wit); + } + } + for (const txOut of this.outs) { + size += varSliceSize(txOut.surjectionProof); + size += varSliceSize(txOut.rangeProof); + } + } + return size; } - bufferWriter.writeVarInt(this.ins.length); - this.ins.forEach(txIn => { - bufferWriter.writeSlice(txIn.hash); - let prevIndex = txIn.index; - if (txIn.issuance) { - prevIndex = (prevIndex | OUTPOINT_ISSUANCE_FLAG) >>> 0; - } - if (txIn.isPegin) { - prevIndex = (prevIndex | OUTPOINT_PEGIN_FLAG) >>> 0; - } - bufferWriter.writeUInt32(prevIndex); - bufferWriter.writeVarSlice(txIn.script); - bufferWriter.writeUInt32(txIn.sequence); - if (txIn.issuance) { - bufferWriter.writeSlice(txIn.issuance.assetBlindingNonce); - bufferWriter.writeSlice(txIn.issuance.assetEntropy); - bufferWriter.writeSlice(txIn.issuance.assetAmount); - bufferWriter.writeSlice(txIn.issuance.tokenAmount); - } - }); - bufferWriter.writeVarInt(this.outs.length); - this.outs.forEach(txOut => { - // if we are serializing a confidential output for producing a signature, - // we must exclude the confidential value from the serialization and - // use the satoshi 0 value instead, as done for typical bitcoin witness signatures. - const val = forSignature && hasWitnesses ? Buffer.alloc(0) : txOut.value; - bufferWriter.writeSlice(txOut.asset); - bufferWriter.writeSlice(val); - bufferWriter.writeSlice(txOut.nonce); - if (forSignature && hasWitnesses) bufferWriter.writeUInt64(0); - bufferWriter.writeVarSlice(txOut.script); - }); - bufferWriter.writeUInt32(this.locktime); - if (!forSignature && hasWitnesses) { - this.ins.forEach(input => { - bufferWriter.writeConfidentialInFields(input); - }); - this.outs.forEach(output => { - bufferWriter.writeConfidentialOutFields(output); - }); + __toBuffer(buffer, initialOffset, _ALLOW_WITNESS, forceZeroFlag, forSignature) { + if (!buffer) + buffer = Buffer.allocUnsafe(this.__byteLength(_ALLOW_WITNESS, forSignature)); + const bufferWriter = new bufferutils_1.BufferWriter(buffer, initialOffset || 0); + bufferWriter.writeInt32(this.version); + const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); + if (!forSignature) { + let value = Transaction.ADVANCED_TRANSACTION_MARKER; + if (hasWitnesses && !forceZeroFlag) { + value = Transaction.ADVANCED_TRANSACTION_FLAG; + } + bufferWriter.writeUInt8(value); + } + bufferWriter.writeVarInt(this.ins.length); + this.ins.forEach(txIn => { + bufferWriter.writeSlice(txIn.hash); + let prevIndex = txIn.index; + if (txIn.issuance) { + prevIndex = (prevIndex | OUTPOINT_ISSUANCE_FLAG) >>> 0; + } + if (txIn.isPegin) { + prevIndex = (prevIndex | OUTPOINT_PEGIN_FLAG) >>> 0; + } + bufferWriter.writeUInt32(prevIndex); + bufferWriter.writeVarSlice(txIn.script); + bufferWriter.writeUInt32(txIn.sequence); + if (txIn.issuance) { + bufferWriter.writeSlice(txIn.issuance.assetBlindingNonce); + bufferWriter.writeSlice(txIn.issuance.assetEntropy); + bufferWriter.writeSlice(txIn.issuance.assetAmount); + bufferWriter.writeSlice(txIn.issuance.tokenAmount); + } + }); + bufferWriter.writeVarInt(this.outs.length); + this.outs.forEach(txOut => { + // if we are serializing a confidential output for producing a signature, + // we must exclude the confidential value from the serialization and + // use the satoshi 0 value instead, as done for typical bitcoin witness signatures. + const val = forSignature && hasWitnesses ? Buffer.alloc(0) : txOut.value; + bufferWriter.writeSlice(txOut.asset); + bufferWriter.writeSlice(val); + bufferWriter.writeSlice(txOut.nonce); + if (forSignature && hasWitnesses) + bufferWriter.writeUInt64(0); + bufferWriter.writeVarSlice(txOut.script); + }); + bufferWriter.writeUInt32(this.locktime); + if (!forSignature && hasWitnesses) { + this.ins.forEach((input) => { + bufferWriter.writeConfidentialInFields(input); + }); + this.outs.forEach((output) => { + bufferWriter.writeConfidentialOutFields(output); + }); + } + // avoid slicing unless necessary + if (initialOffset !== undefined) + return buffer.slice(initialOffset, bufferWriter.offset); + return buffer; } - // avoid slicing unless necessary - if (initialOffset !== undefined) - return buffer.slice(initialOffset, bufferWriter.offset); - return buffer; - } } Transaction.DEFAULT_SEQUENCE = 0xffffffff; Transaction.SIGHASH_ALL = 0x01; diff --git a/src/types.js b/src/types.js index ec87eeabf..2b70dd270 100644 --- a/src/types.js +++ b/src/types.js @@ -1,45 +1,43 @@ -'use strict'; -Object.defineProperty(exports, '__esModule', { value: true }); +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); const typeforce = require('typeforce'); const UINT31_MAX = Math.pow(2, 31) - 1; function UInt31(value) { - return typeforce.UInt32(value) && value <= UINT31_MAX; + return typeforce.UInt32(value) && value <= UINT31_MAX; } exports.UInt31 = UInt31; function BIP32Path(value) { - return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); + return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); } exports.BIP32Path = BIP32Path; BIP32Path.toJSON = () => { - return 'BIP32 derivation path'; + return 'BIP32 derivation path'; }; function Signer(obj) { - return ( - (typeforce.Buffer(obj.publicKey) || - typeof obj.getPublicKey === 'function') && - typeof obj.sign === 'function' - ); + return ((typeforce.Buffer(obj.publicKey) || + typeof obj.getPublicKey === 'function') && + typeof obj.sign === 'function'); } exports.Signer = Signer; const SATOSHI_MAX = 21 * 1e14; function Satoshi(value) { - return typeforce.UInt53(value) && value <= SATOSHI_MAX; + return typeforce.UInt53(value) && value <= SATOSHI_MAX; } exports.Satoshi = Satoshi; // external dependent types exports.ECPoint = typeforce.quacksLike('Point'); // exposed, external API exports.Network = typeforce.compile({ - messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), - bip32: { - public: typeforce.UInt32, - private: typeforce.UInt32, - }, - pubKeyHash: typeforce.UInt8, - scriptHash: typeforce.UInt8, - wif: typeforce.UInt8, - assetHash: typeforce.String, - confidentialPrefix: typeforce.UInt8, + messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), + bip32: { + public: typeforce.UInt32, + private: typeforce.UInt32, + }, + pubKeyHash: typeforce.UInt8, + scriptHash: typeforce.UInt8, + wif: typeforce.UInt8, + assetHash: typeforce.String, + confidentialPrefix: typeforce.UInt8, }); exports.Buffer256bit = typeforce.BufferN(32); exports.Hash160bit = typeforce.BufferN(20); diff --git a/test/block.spec.ts b/test/block.spec.ts new file mode 100644 index 000000000..ce8ec6aa9 --- /dev/null +++ b/test/block.spec.ts @@ -0,0 +1,9 @@ +import { describe } from 'mocha'; +import * as fixtures from './fixtures/block_deserialize.json'; +import * as block from '../ts_src/block'; + +describe('block deserialization ', () => { + fixtures.test.forEach(f => { + block.Block.fromBuffer(Buffer.from(f.hex, 'hex')); + }); +}); diff --git a/test/fixtures/block_deserialize.json b/test/fixtures/block_deserialize.json new file mode 100644 index 000000000..ebb805e80 --- /dev/null +++ b/test/fixtures/block_deserialize.json @@ -0,0 +1,24 @@ +{ + "test":[ + { + "name": "proof - no dynamic federations", + "numOfTx": "1", + "hex": "0000002069de100c1bae40e1cf8819bd18282e4ca370f62123c8ea2c60836984ba052270ee0cb6e5458591ac157ad414a111db4d34cedffc22e096291f7b4b3c8de3f69f8d53815b03000000695221031c25c60ef342990d9bf75425c1dc2392b5e206268d9d35044b731735db230c38210319c5a32a8ae698aaf1246784f54231d8d20f81b91c31353214538b827d718c8d210399d55e0a7fb30281da074dfbbb2654cacc2d03289ba79feae702ad6dbb542aab53ae9000463044022029bbe179c2f0d8e6d1576869cea19ef439d0e52373f7efab77cd6ccb551b29f6022042baa3c17fccfb265ee878059b6cb85d40b976a30495c6ca14b7ffe6d1d87572473045022100da88bb6fa1ecf3060ad7c8347eaa1a7ef8c9ae27a8b0136cff909994ca409f9e022068ddf3090bde1e04deda04f762eb35858d7dfc17e156bfc1c8131ca07a349dda010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03530101ffffffff02018dc25a055e773e7e91d4678053ebc702cce47f07b29f3ebd7c4b34cd30fb240201000000000000000000016a018dc25a055e773e7e91d4678053ebc702cce47f07b29f3ebd7c4b34cd30fb240201000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "name": "dynamic federation - full current and proposed", + "numOfTx": "1", + "hex": "000000a01ecf88cda4d9e6339109c685417c526e8316fe0d3ea058765634dcbb205d3081bd83073b1f1793154ab820c70a1fda32a0d45bb0e1f40c0c61ae03507f49c293debcc45d1400000002220020a6794de47a1612cc94c1b978d5bd1b25873f4cab0b1a76260b0b8af9ad954dc74b0100002200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260015100022200204cb40d59d6e1bbe963f3a63021b0d7d5474b87206978a1129fbffc4d1c1cf7e44b0100002200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc332600151000500483045022100ab2203a8a68d7eca8a3a0fac91e7c7802656d937535800da82f7102e1c06f7b80220576f004cb14b95178c71e36bf947bfea496b00b66c18d2eb8febf46362d50e2b0147304402202d4630887d661a50b76b7b32555fd76906ad298ce24483df42310ffbf62d451802200e0d64069e58047c271c1b4051c1ff3d1cba7d32e56abb0d8b8bc30e1bed075b01483045022100c6b196967c661c4543802a895ae731af44862e75d9e3c65b8efdd668727a34af022041ff4d67029052eb6305d25d0fc4813d21a939ff5316a12562d0c9038976f8e1016953210296db75c11ea3a292a372f6c94f5013eaeb379f701857a702f3b83f88da21be6f21021f0d8638c413ef7769cd711ce84c8f192f5a85f0fd6d8e63ddb4d2cf6740b23b2103cadff18e928133df2e670a3715c4e7a81d357de36ddaa5016628e70a3e6a452f53ae010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401140101ffffffff020137c495f58d698979ff9124e8c7455fe79b13ddb96afa25c45894eb059868a8c001000000000000000000016a0137c495f58d698979ff9124e8c7455fe79b13ddb96afa25c45894eb059868a8c001000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "name": "dynamic federation - compact", + "numOfTx": "2", + "hex": "000000a0a8d446ef9d78103b06e6a3af54a4c28c792c6fb3035dc428cb03c373cb7daf7c3ed98edf8f484508ea0f2853fd8b0204cbab1c6fc0cba260d8ea3828a4c3b3a793045e607f000000012200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc332604a000000fbee9cea00d8efdc49cfbec328537e0d7032194de6ebf3cf42e5c05bb89a08b100010151020200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04017f0101ffffffff020125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000000f0d0017a9143a5839b8f943a149f3bb3b03a5ee81a3da9fca52870125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000000000266a24aa21a9ed0d9aa5a0cab48746412b08c038ba2fa11065bb86031be7d018dade387366c289000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000020000000102435d740d05da8077191107c03adfea535c95b3d6f9723813e96ba519aee68c920000000000ffffffff02ec45f4fdf659ff3defd76eb9d47b10d1b55b126614f1dea1afd0c9a2ac62e00100000000ffffffff170bd2f4da4c56e93100816db5e309f043dfb674c2eb9197e93b8440d041809ced6b0991011c1577b61d45cda9a10e3f35cc73bee52a5d4f6791201a1868d7adf06a2002cd0fa7673a7c958611faee00167e10c19b4e6756486dfee4ae7b7bb0d7460186160014e87366855e73431f607fb0867150d44102c3cdb50b583dd77eb44d72ecf9292fc294fa5660314d3f3978ef4ecab47b157814cccca109aa48db7728b7fba6b771ada23b76707ae72f461d4fa9aa81f13382312f620f5c0365433e24f6f88d6a036f762b26f273521d2754af4c18243d2c5049d65b9e064c160014cabb8f8c29d62408ceb5ad6ed7f2051d7a37c5300b51e5d811890efd807ccb15075ca91f7afd53411d27834550715c8158e65baa9e09e7968e0c0cf4739001d38fa469e944b337be5dd198e1723b8d81aafcd45ef09c03c2d3af137ac29c1a149421e94a267c98bdc082ff3327aef4086f1c71f48da0a816001481ed44d4ee30a87f6a9058ad046c73fc9c800bd50bce9c8cba7372ca98f44387280e9ff6e9caffd42853d582f7d6d96b78eaac310f094c43eaea4e9ca53b67391b4233a88c5497c3f1d83f639d9b00ac7f36fe2c8bf603cd31fbf01b8b643ccd9103719fe5db1e83be1d899d24e5d064cf84b572a63c40160014a862422b441bc4183aa3ffa944e0c0be5f76c2500b36656df4793d0dcae42c0b973fd8093a0ae7ec60900b153c8728347f4eb4f44709c7a73335c9a7954d487fb067b4cd3f3ff357cc951b1dacb37eb547e4927de8b703036ca99b670049d5e60f13091781ebbde5eca3b0703edcc206ca8d7b3dcdb0eb160014c6d033db90b4cb4c422559b7373a6190d07317020a8790fc61cf11930ebd67d1ece1d2b07c96c73265a2917fbcaa18a352ae0113d008c98b3655400f77657c312aab8e9b73ae6e1fe5a8436c4f5f8bdf0c30825447ce03da6d763207deecbdfee6ac28bbbdf8b404d44b9d45509ba03dfc5cce0d9ae34516001413161df67b184053336f8cea99f1b1b3e947da6b0bf630ea2f07681eed44cc8a95d314e79c0382a629421bf002978e4b7d29999cae0890eb5d6bb65f41c8568c7b3769cb8544ca90a65fa680b88dd7f2b55adf6e862603d533562c5c239439c1e6c53fe5fe7e134cae8f2eef065fe5715736d8c43f008d16001431530ed6ec2440bc556937c0ac6d34518b47c1fc0a86381e5ef3d6376c71f8eeac777b0624b7413361e7b4b6ae70dfed9de5ea859f0937d1e95b2a2287aec9aecfe6391b1452995f5182383587403712054e4334ff810275c725bf416b8ac06b61f37ef9e49570c5ce55b45ba96e8971275b927f747157160014c12fb400eb2ff9f348675d658204c2f62b53775c0ac7354935940c984cf55cbb7adfde655c07a867c2cb380f5493bde47deac6a8520873c0cb0cfca095c250ef70932b305d3ad4d97816b0066c60780f330bfc6d32f8021da100aa93d570f7b31a15d9fc71a934bb4132cf70896a45f724bed6e607248b160014d87797df972e1d31a7c0787432804784ac717b760a776aedc329eaba2e5d5702f62692a6c3f15d5f77465bd399d00744be2a8cab590885ad2d2d4aa1b3ece05f41359a01ea3ec52829b704c68f121abefc4cb5d7bd8e02447cf7d1fe0991dbe094d39d9babd832d59bd9e21d980cb921a7deee64856b80160014f58475246ce667cc9295d0242cbd5068c7e2c0930aeaf518e3bc9f26f46d41d3e927d666e88b7d7c3e414430468b2d0318279762f90939f4aed1c77022d1c939311c4e17a8842199dba965b0c78e42da75f3a07eda0d02e829d7008faff605ef528d2b58aad449e9434329e137c12a1358038f3da49a5e160014d12f69541624478071ab05893bae4df548de27390a25dfaeb373dc501cfc9778f1cef14c0ec55564abfce6735ae0a41be88e300e4608c89ac781c84117543c8f705a7003ed8a93b25e56498a14250106b0a841827d09020b24a4065762f0407407358168af10fd74fb50d2deb2d741d1cf814e776699a9160014fa244d6e2a6b2e1508dfe241ad876a83f28d1ed90bdaa6ce1d45b50f985e569e88af94c0d0934f6db9a7e7c2c4ae67dcd460087aa40815fe8158138634ee342f40f88271b98ce1917640439c8799737cf1b3ceffe62d0370c4943847626cad849083ba85580cd12c4bb670d3ee7fec3191e3750915624516001483342e1e701df4f55957270572cad84392971a980bb029b057aa9e35c60493fb00b79bf3322255047d893082ed1172a5bd247d9d8d08e588ea93dea310a2ed345237acfc37809c4c21dfe6c3e849d5ff3b9aefaf204c025528d1f6b9682264f72066eb2e418f22da5461f12aa69d40fd8f0f04460835051600145d22dc1b9eec324a4253e018a4f0d61719c168230bee518a52be7469cf55deaf633e55a6cf071ccefa044df93c4f43d42b4c52795809375aede1fdfc85295fe6e39bd5897ecec8d586d8a28afe63238966624590300703fa0450521beea79f61727b3df7d575eb3b961af9bbb774d6afa243d3a324ebe9160014f4d1159c2886f0fa764b15a6a75874606b585aa30a82975a0005958cc493c0dfd89706fb12790111da04ee50450d91f4dc49446717089af8fb9451722119067e166dff4c808d966508d22a54bdf90944de1d727adbfc03d718d3cc4d61de108adb2ba139e2a7aeedfdffe9a5b343e0e585edeced73d3b6160014ab6cd763df0183f98957b361bccfcb15dd40a0930a5c82f223f4918e24201119f13c28bdd9a7e6d943bbfe6e009783ae05b0f8bcfe097acdac028779f2b61aaeab887e19e9550556a9b0455b5251f652e9437d58684903ce5bae43790cc39feb556869f6344ef160ecd1efaa587b6a126995267eb76703160014dfb2f3459b4c269248c2952728b9c343644e0d8f0a1eb16e831692222cc8adb883082858747e15bfd1a1cd8b9219360bf8849d0c2008a09d802d6c59d9cb323bec20fa1238a12311fc48ff256943e83bc0cd505c5f7602b57fb3f6232149a092a366eb191fe9b80ed1e79d0a3fda36e5c0feb1078557ce160014bfa4fbe3b29891ee0a6e94e9fc0c7b37cda266160b5e1006780f13ca2ab1769fc635770d64bcbe176417d2a8cab5b3e679b5067f010924826fdc539e012defe2f4e6e2ee6a667bc4bda7719fd329d1f5a8b2fa4cda4102896f8c6419f2ed3bb0bbf94a58535256805027bd51837ff8feddbfada6e74d85160014f85c21de703e15c876d68ee9a67d9ca419b5e96c0aab711436add19692214c677f090e1b8fdeed25b80d9197f58adf0f2b412c9b8a09d4dfe47cf3ac740e72e4251ff97536ef9e40550d61e038d329a608dc5094aa2f03ee31826e195e25a3c3f1118ba3a10a9ee3d09d236fe68d30d959547c160a31cd16001477ae933131eacec4fe0ba9958662b417922e6bd80b60c9271ad044d1aa9c359d18315e2e033955a4b56fc9ddfee3bf7b4776975e430833a7826f19733433b1c7aa9735cfaf30cd0dbe3224198309e03681f677b3643f025f3a3e991d3e9aa59f079840f7b1ba7909b4f863ad9a1e962aee01496667ca7c160014a36286c52af6bdff02be57dad8715cd5449fd7a00a755e63cd17960e49e02b46934ee3ee45733e4e23ecebf6923bdadd359c16993b084b81f2940c1ce0d93f95b575a8a16f5e718f0b863be09bfd007af521fa14409c039695ff3a30f6ac7dbd0718d2037997c526aa7ae198c67c3ed269c022c908872516001476a5b8d27ca4ee40cd14871281758d8e37d9930e0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000000f0d000000000000000002473044022040c7b3da6d6ec5894ddafd85470f5e85b3dd02d4c393c431fe031f789e25c36c022078ab40ac50e4d7a7aeb994cf5bd7d06b325d8522924564ccc5527d8022df6b50012102af61fac2926ebba4cd36c4ea45d331ef7fa4657535dccdfaeec5cc390519be760000000247304402203cb4746196b5b7c60cec2f0a5ab49c6ed0560fc4a0edb4157fbf8f3dcade58cb02203d50220fa7b2c5ba2e89a0a17c605b33f843db5a320390b542e964439b4effd5012102af61fac2926ebba4cd36c4ea45d331ef7fa4657535dccdfaeec5cc390519be760063020003bba889c0e60d2325d6d0397dc7a9c54b8a0a0f8ff83feced8d751af47f99f52d2c7b345204abb4367568db7c0fdf03278d7a5b82189075d407034cac01c38669e3e4a851a2d70f77c2b405ad2884276b3a000463225343dba3c606a38c9322b3fd4e106033000000000000000193bc460154014f71a82b5580857a427077812dbc4e3b34fce7341e4fac57cfccb52ced248d69d082ad16bec684efbd100e783639648d23cf18def64032ca0b6d328a4c0f88a6881adc8f6c09e57c760b252ab8dafc97fdcd8671b971649c5075db84769fd049fb0f5e64cc3795ec52bb0dc8f5bcb30407fafaedb21908f5a0bc70f443f1af9ffe2f5c342d6a746e72c4edf1e5098d06caf941166e9bf86ca5307bfbbed1202cd4b99725d57dbf154f5feff0ae58d4a3cf89dc64edfb429da48d3a274fc85d78277340653f597ab1a00280dab98a58987d7203efff3fe7e395aeb870998297fcc75b119ef07eb033208637175ce6368f56d58825a60f6eb7a105a6a04bde2fa1251759870257b1e63a3e7911f761fbe5d1a9a9066bacdb59f208cd9e888e84afd1c4533615cd9a41b6337ba89391c4d5e93d0a7917952d879a9812d2bc329a81a826bd38824e66760e75599fb9f3ece2d5ccc47da6784c3f325b4faa31bf57efb3d304dbc6414f5d284a120471894e76a013eb9fd815e436b6bced0ee6faba3303d453794b3766ae672e0ee830e27b0492b61af735474bfa67311327b0a1815470882c11cf0e66f48f9f793e28f3239ee1932eb6c75b6c55a26cc9bba9f09362bd9b2030a1214ca18ac38e0e495912a4d22a3f12a45fffd6040c846d3831ead56090b16fec7662deee6ef6d3f5834234a28576b46947e3031a483b67ffe71c9f3501f6eb781940080d10d68dba086b054cb55d62641f44fa66f9aedd633d69eee14b6d76e35e0111b3fa4840f273ebafe6a5642b62ed76c7e02837771c9e444d5d8b3fe0b7fc86a56ae871600b67010f026cf4ffc64f311b798a4bb2441b1b40bc3234c8b967cabfced42c26ab45dd4b1656dc76e1edcad389457f893d3a10f22069f65a8597256ae0508483dd3ce1e61ad0957be3a1018fdee6d30fa5d89a5fd6f6de7f332d3756f9945cfcefc010ef7db2810324d7f3aba83b6f7d97d773957978a861b3fb4ead6a1d0b1f493c68188840152c8739075185aa37400b7f2a0b0b621ab8717a5b4593ac0ee087525aa8babec8667db69d082c48f64191dd343d259334b29941a399e293c829cdd77bbaeccd9d4d73c9306ddb831a582a933de1d16743de7ca5bc504072fd117f88c37da8f0b7ba606d35027f839ba5b62ac1141d271098a6a58258741e4ff834b82d7e95e7e6c324bbd6d1e2ba3ceba989918d8be280c3f39bbc1553f8bad4b37fb9fd1e29c556703c46e872a747e0705d505c7b0b2a3b57f38a65850529d31fc1df6d2a6f99511fee18b816bdfdde485d06301b85929b0e0d412704827ff3af7e9464bf1ab5a3de312f5c9a69f484710b0da7d9054bf47c556524f0e3f21a5ae6211a0b1166df9390f4a48da52bdbb2f4ace85481663ea5e615eda4c014d32c8dd5bd1cca6447c0d0a76af7a25ff320060be5141c626fc50a556dadb5a3f93067746a89525fd0a07b058887360cfab4a7f055766cc2fae9d6b59506ef854910ab79ed6626ad198b416c13ce16f42ce1ddddca653433d70e80b0af02176b37f2af0ad1c5cb23f284ac99598088af50e6ca22ac45f65ceb06aadf11aab3a41788ef5499626b249041b49bd5ca71b0b17006d7df17a3e1ce14abc863acd1219d6642bedbc375866f94417c0072ec31b7697659a61e6601fe206248aa660b60a02368bf421fb938d2642f86cb927d5a0dfedc4d99cd42e1c439f7cf21d15f93538cd2ae1df7d06cbe2fafccf134a9d3e12afc70b35424d67aae7918b39e6f0807f4046c579f1d5c1cbea6ad7719e8a62094d12a3c5fb4515c8dace908813f042f34fd7da1a7b6bcca9f8222691e784ac84b20441044cd73ca3435ae641a359cc58b3f243b6af452cbaf27be20e27b2e2233a5fe7a29f7636d66942ccb787d40867a8f4d1e6d86e120da2fa23cd0cdd96bb771ee71d572039bd1ea4b8eed49c0e445dbc0b592de2872b49b35dc94e0f158f5368e236f9a7fdf147aa8fd8e1b043079de997fc61e8ef1e6304a3f95fbe620ddbe87bf9e9dbfdba48f74c13c32da8759192651acdc46e7dc36a5dfd4d0b12c4006271bef80fae4a47d737e3dcabe83ee5331353c9a7ce8b2327cb27b6bd46e52ef70c3bf53571ca0947a668d56fb5583bb8c0f2e19906bea1c3902816902f50dd785d5b86d35829c197538bd485905f9c330c4b14ddc9af439d4ffa759a89504ef3cf49223b5c975bcb1ca3762f2349c831b76560406d636e8ac520607fea2c32e6fe47ca8811259ec5245d79851320a9de582bcb4f22c33f30e038568f7b5b6addb0242245f1da5af0889c756b47d3abe7f7ee2082359bcc7cc555449bc7c748bcfc3ceaca67cc74fd314ad91d11440e89d0501b3daf74425efcc43e51f1dbe87efb75b221900836e8cc246f5b46ede2098f35f933ea7455b6550e912cbb97263636c24913782071e60c22c4afaf0773b1f2ba7dc59abaa9a729931e9965bab9e2ec8c42424105e249e30eb890d98342a6a2750badd42111ee891d12770ca25ab900796d476eb6db6d544aaf2affb14b0b095c59558f196d20ac3bd6822efe840fe6a357fa00bdb9050b3c9fa373f3b6c5c50ba09b1054306c94cbcc97a285d0c527b801a87e90cac1bc35585f3a9442a80d467da739207e2a6049626ab91e1226cec6c80485aca844a7a28bbb5cd98e039c09dfe905735827454f0aeabdced00ec7c5cc6018c1a3c0c9744064de0493f00270e5f38b9f56522aeae54ac412345c870f30d3525168b7d1079e72d7fdc3f0ca813d1b6c96fdb56f995b8bf6f1fa525d9f6426f56452933d8902b6ff30852653414099143acd792d1cc45eb51ef8569b9260075014496d08406372cb13c146ebefce392439a8c76976c537cc5b37de42b2246696105fdfc1c9ee50206ab266c306fb9cf4577e6b481e426510fe9f66ee5c59543ffbf05f2c25387df2bc2be4940b007595f43272647e7335c32799908dd21330e45248aa870acc03bf6c4b96162917c24d816a0ed3bdd6b7588ee1ef7df35dc8bc34fb7593b18e8fd1f194dcc3df48824b4a14bba4e1c7b09fcfac005fe69408073ef60a2fd939f364e4bca50bf2a05f17a3dc3c95cc5d50a329e63fcee7918a3c6d09de75133e0aaa3d2e6ca1dcdbd470fe1c6c9c80835c86566ef0bbbdb506d3aaff97f82f98bf4b24e1bb0a6a33cb02b160eb660c048c095ca62f3c58aa15675ef3e890ac78524aacaa6c391218d684d8f013219729788562bf3310c2076b24722dab9e19e1d32c0ac33e7609775754348931db472d033cddbed270200996bb9e19cdd23408041012577113be742d1cbe55a5cea503453511689fe6ba9295dc382d72971aaeba98beb1b92f3fb7c980a72883cbc478ffcd3f36bcabc4568be77bc2bcd206d4b6f3ae3b09418f76b1267a120d67d834a94e4d0640720ea806439959701197f5f55f14c6d016420d117d778588bf46f4faf79d3ebd328c5593e20f453674b19dc476de4c1fcd68e6569bce4cb98da9fac37c9c86781d3abc76be8ec55e0dcd3efdc028027cddedabbeb22ae7a6c5490967a22fc9c2094661bd783cb23bc24903a8eba9d938425f05956a673282766854e8fdf899be5710516b234198be979ce66b6f875113f303a0374a7b2b406cbb946c65ef2fd92a3fd39a971c1b32714378190c0816abf7b9167e6232c7225d9312d5997840923c5fd32a89459617092c6f736cdade30a3fafa0558a45df1fa18131547b3a9a169d7263797a4b676a58ef2476dc30999478fcbfa97f53ead4a68dad788036d3f363de336f286a649e55acff939df54424183ea296bba152644f853320313e042231c9729c393da9d434e56f60421fd84ec962ac18e19e28163481265d98a60ab4b80fdbbe9e902d12d99769c10f45d8caa6c4ff0975e85dca036d1ba9bb39e48fc6044665cd9cc05b033e4974bd39db091ab9206224bc0dcca391780b3e9bb7c156a1ecfe016e857ed224fc6240737c161b08db2e43a819aca0d5a3d2a7fd01a4e8bc65a8e4bfc7027b603ff83f92fee62b2e91e3188b2f6e955e4c97bd305312c1559228ee9f4760071b32d2379abb95370e2158f11a44c68fc100aff2a3ea8d9b754b178c9b57424d54d285c9e85a3764e42e5d343ac3fac1038e1df7eeedaf649a9febd0d2ec04d01346d6cd2cb1d7943da9c9a27d88331665dd5483bdd469e814abc1677ea7841bdf0712fe40121c30581b482f0c939673038f14509955cefca32f6c9372bdd53beac180ee0a13622ca1a58eb18eb7e40fcd34ee9649451874dd928177016c7b0bfc6ec2436dac8e73f76f09814e280ccc7fb6f1da0cfbfecc7edfbef24694b5285c94bd750e806e1181237295f84f0ebc1e5b86bdf1e17e8f528e0253fe85c7547743125a6eeeb93e99174efc133d359b4fcf94ff121e804a08b565ed36a2cc2dbd3ada4744bb239e5d812e9286a4c130ba45c9226a0adf567de058d3a86c0c3fba1affa16ee05a4b772991ab24f80780bae20ff17b827b758e13a08d3324f244eed723b9551140dbc38218d686014399bea0e36471f59c70af1004ea4d7aaa1b49933f8f22ddd583a6d6bfef572c1993f5a0629ddea7a3cce5c7b0f232133eb929f96f48161c259438e22cd94fd3c97aef4a1bb64937cb86263f3db8691a82eea7aecb9995c0cddfe7fa5c756de22ce8f0c92f9f8bf54b398e99c0d9c8d1fe16c1a8d07be6b5b9d50f557d9cf269b14e2621e6719612678f497d2bebb267bd178aa3005a7cd6baa7cc22f65c512f2beb9564cbbdd192da490127598b4856348508592dd4152dd45fa258b323692c0782db079d693ad6bf78109735d83623d0cb5a1b4d16b9bdf69c82f67256da4042a3294a1ea0520dd4ee0135aea6be49a654f92498fc3fd9691a5c51fcf977df372c4b42c3d673afaf541b8260211af8a7303d09fc3f12057f46d098e62dd73ec48a81ea2c91efc7ead0212cf58817d9a1b24ca6b9e2c44c74dae79650018e4ffefd7f40cdb6d8d2e61024ca593159fa7cdccdcc0aa0cca2a3aee744e36d41c9850baa247e7c4f0eed69930a16a5b4e5a9eaa7b5484475a43b156f32a392c01dcce86dd9294bb3e0dff789086854836bc409548ce7c6682316531dad1cbb2dc07ffca94ddaaa928ed23e4bd7d82ed47e0f3941d0d2f48f642bab95bd64d7ced8e32b632e30c149b6539fac6052b287de43ed93517f069a821fe3d45e3968778f8b290ca88dcceb73e780931a05fb8f4d79b3a43dc5aec5c8e49dc1a4a6b9f96147f3d0883878dfe3339f79588c2edd8661c4a5b2e6f3760349fd050ec07a8ed80f5147ebba3b195abcd1032a38af87dea5dd5b56d7c7e16296e8f2194a37546356faa35c63730dbee54dee93bb2c80062b2844e0098cc2331c81ceae6318651b8ed771412f91a911b1c2433f00f828d3e348b98c6d38a587e6315d26630e093d81e7dabcdfbeb067e5c4f59b35d8a414c6e82c46d38c11e6b308be3fbd90762143c0b51f4e70b493e5a1207eb17b321a2540abdac8736ac0156d982c01a272529c3dc7f995d1a65c50f14c47dc96795ffb9953bb4f83c0b129aac0846cc818b83a330f695b085eddbb6126a6e6cc34643ba753647c9038388964cedc7848288f308aecc8a97010c225c8ee0bd01303c3a7688c375a5e5554f091bccf253b9b840a3a1f97bdeaf6c116a29a6a22341cd7c8ec0ea6d97f0f23cc0259214f8314d53d026329403db82706fd93713d8b939fde26e522a438efc4b51460f3c9f0037c768a8b0184203bf898ca4868058d5e7b16d5333d61dc796aa5eba4411a8c37a7426383eed51f5fac1267f681bbeb47c3e090b9527b1693357d9799d62958e99cb195637961b541d163020003ac32943f81b2fc8ac10a3dd14b5fda4888db17b3596469d594bfb1276b3d532ee124e760ab1b15d0e97eecea75489ec44d6a359c88b0cf47fad1bf57ab5f3cad9d1e40431e9e2ea9f0460a398beffe1983d3b0a16a4934b3da454e23b7580eebfd4e1060330000000000000001c66d3d0111274fd376ec11eef8dcc5f563889b47c3681570f0ef0d5922f2c01e6d4c3340906030c00cce6a6ac942ca8b4849bda4c1878ec252e4c1a0b20fbc52f4a6a054ab164d760bc6670d0596f7b5cc17ba4cd8fa3121a8f7b60722ab47bfe6809de08a7c7d2926b85c3c8e23b9340f22961147641bdacb6b84c3234a4c2e2dc7b9f79daa15db3b16b31c509aa4d1cdddf9d91f59e6f56f1bf9c94b0bfe7dd69216875d0869aa1e20f39ee282ec8b35f2dd39d7715eab2898b069d268a4b8142d225322cf5e728670d8a5e72da28b866526a972e9f611a6477e3b2b82b679e432f995e32a2fbf334d0dc84a19f3869f3c9da8ece3add129e54f6dd97f4c1750c855ff5c9f888e3a1eda42b4612a2cdd5fc155f9d8053cdf14571248f958f7abb859d7c7dccb793349a31d715479a178d5420c536f5f0d8f0b8982a2192f3fe1948843595471aa8dbdc42cf0a5b4a590f01e6b7375e993fdd62d1ea8099a22280462a9a948e0a84cc39c6ce267dae30047ad54d14f0652263500b659743692acda715c9c66e8d2e302236a5062f110d88465f2ae1f7a38099f022396636c68511561eccb9c3422a1386b664c89576c0a5c115e28745630d95c2ecaf27b9e144802cec6297234a02a837985a093a583d96fc519916c6d670aa60809b5a486ca069a448051134ba9eaa6c3f56a8313e8fd31ec0e6b6753b1b58f4a374ecb79f228d953b29460ff398efa5eb5c1edb8d466be4ba784c213e1100a47df4d51ee8497a20cb05c7af7c18e881d11339c884675b8f5018487ea430871a55e3c42945282190df7a0b71bc671623a13cb233a5f66b451f945ba6200436486ed47c9a948ceccf47a9ce7f8783188ca57256a001f544dc02ef9873c53735dc5c92ca08189021424df9fffbef41b37c714554846d9898e0595c7ecdca8e9397c0638638cf0200fb38479d8f34355a1877f67ca89dfd0203647ebe195eaff1c05706fab30a83f9836e3c86825b0aeb2886fa95072c4087acddff3b5cbe824047c3761c394856694dbe96a700d709d8c6ae97ef63f1c37002fd5d9e1e1000ab94461705c96039eaa00faefda4d79586822b105022eb04d1a7aa74fbe77f567542711cda977d26181c4a7ca056fb6fd52ff54f3b179c52c58c367407d07b0efc5f30ed73254fca7acd7720ef2b7c4282d11cb585e90f8e83b1da45666162e9dc5cd49e6d7221b99f9ab6de7104b3c5688d16bcd2cc01211bc19176a50e420e60d4c4899fb0f995770a730c1b1e0ac67bd748f0511eb63a8e104f2d90afff4e607f1902496d5fbbae0583719b75c3d40af055bd5048a6751dc8bf931d459e34da1b6bad87b1c461e23357321546e9c6f41fe5acaee56fcf2dc2bdfc6af8164956107c856b9c85927e8d237cfcade765481772291c213a73cf12957a61418566eb6efc77e82f6881a7997c8f44eae0883dc7cde0dd5a9f9086020c65d867e423ecc6e9bbef0f32de4e479c45d298c74c4f4abf5bf67e09cdce539ec4dcf4890100fe699f1cd45a38305e7aa60fd2ce83df8a1d8f66c55fa82a4e7673ccfdbc52c4b913e830116876992c5a5929332978c7fb6ee7dddf0af24f774bda960c039ac3f9b9a703cbec4e96212c48947c2ca54b5b2806f1bbae0451d31250def5c3ec9137c8412c0f1ad09f99412401e0fd2c6f0f03f9d5388bf4345b0dd42159c934ff3b3b7055d7dffd521dcf7049dd49c0051f590cf7433496449ab2a20f8b41265f94f442bb5560fd21f9724dd5cd7d78d6624fd3812971bbd3b7fa788f3c18b1ac9c06263ede3761894e1350b64c838591639bddf627e250de63c471012180800c2015dd81ded85a0ca3db5e0d65f61901e130dc10823376dcf371609ba3a60825b620388c1d394fa82ee68c578cfde16d144ee90a068cbef6508f781c65af1df65639c372afd1539f78b471abf2812015f1dfc4f99b4e098ebd11ad6eb9f168f21c7d93e7ede29f449ab2358ab3f68de72bb2bfde5ffb3a820d1dc41d7ed5f413ffe82d510202732080f4bd19ab862b9dc8dffb418565cbe252ddbc28adcdc266177e15b1d296b2b84ffedd4bdb9e0d8332ea0105b00dd6041b72348c91053509dcf21112bada3fdc94bef2c754e5fd4d0ce3ba358898ee1dfaa71f7597764472b4f2f5f052097f6ee4b5f2545db1cc0a7e1b8c53b2f8cf8006e9e29ffa862987eb3d0ea9c9b78960f5969d76fca62d082a2995fab29a9232aae464441583607fb59adbd3240fb097a5e72275b800bd069542135d7dfce3bd60c6563f9e295492fc63f2bba61897317cada9f24190dc4ccb0e59ec4274737530727d2dcd8abb1929363f0914666ddb1f6e65a99e39499e3c6254a9b409d6ad2b89f8657a36cc74a2ed78f1b73832f979c95cfd56376a54f3d4746e886b04df7e70f41bc2f06f42b75ee19a2956ed2b4564a71dcf5f1a313a2590d3ad695975f5c66f73caa8facb58ad8c8d7685717c40034b262656e561574041950b192d6983a7ff8d1da4e516e7b48887a4e6a1431c52cf76af438413eefa3ce8ffec357c1ef6c1714bcfcb9e923d0bdf4ba960fb44bbd30fa2148cbc42ce8f5b1d37b37b7fa3a7ac4119432412a00f3dfef0a49f8cf3a17fa006355ccd6fcf4e0df1e811119e5979c6865f4442557b1850aadd58ef905aaaea5519b6753d3ce6338442be33f9c6680df7001e20ab3b65e3d460e50c1a3aff0590391ff3ab9b143d4c7a9e1c99995778217b1b2ebb76e040577ef6dbd000ae38084bc8f75475f64a8e558f291238d3504a938d09887c06fad820d82201d2571b34cafff6e57e82cd7b7867ad6fbb006a2afa025b5694e01c565d8e8d22fea15a255031c8ef4ee2a97bcd8877e2aa6ea1edcac5d812a48e203181be1aec3342c39a31b7bbc09441deae93f4e13b1165f8950161b0c57b0fcb76642c45b333bf1a341321b8ad8af6789de71e7b45979b2e4a9053d8c5d5ab086e648b3ddee35a366b0b990d401deb443f4709a216dad79a1925b37323722a6ade4e72d29ba7472545f5dc851ec33ad978b28cb60dae56628022f2b69604f7acf7cbc74e4e07086554ef444c57dc0cbbca184cfccfbfdb0edfcd509052b858a457867732b647e540d565cc08170c3197026186b264c8cd874c6d813b6a1d74528540e5e44e06af65f989a0d64c7891fe0e9c728cce771f1e30979fd69a88ab11648cf90832a70daff1c8209fc823d613eb7ce43c96b68798a2d7e078f6506a6ccf622ac2f14e6a4eee5434b0e31357b231f44006de219d087eae489fcc8cccc95e70d4eacad3448ea47d67ed6170278325cc9849609a91dadda53de2b95695a95b594d41f98858f6f33d6957052852a7b0da4e6fd26efe7b7cc76222d65f95590b43c9678e74f9a8d85678f4a02726f1adcd2ffebb05c75b451f7b05d278a67a9b1b94b3e577d103d2b6175f448ecba8458f4e8c044eab8f28cace16e5eaf873ba39aec52c04888b027167a373fd87fd4b115a4f9454836c3baa5e42fb1dad784e3b122009c051d6963daf16777833f3a3f7093d2a5aa43b0ff3d451e7bbca6c46a142a0155ab9c1eda07536d56c74663811e79e1b3bd91dcdc5f7aa4a9594ffcce9c80d0b76db487c42b6cfa8fb166b712c7ed6e30340e4c641bef2f8e2a30c3faa66c751705938cbf31807bc975e6cf3383f5b60b0df510714b465467f2bf91e524cbb3362d3b226630fc6c00786659f4079fb1012dee75b67dc7133b68d951457fd9057a392fce27024ad9468ed48fa4ed48ad2dca22327915406570f5f43476befd9b16dcc167351034845dd10a0a33ea8d41358fbfce653bf52f48fb21e40a7a58640faecd90660f6cc60ebf238de847475ed5c49260265bab1fa29a7f54f635eb4aee9d062875831a4e6cc327a3c48bc0c24647cec3b1358d7b90e86ff8979726866b08f397554e213d219ca8237c8b628b956e33aedf62abd009e1242b1b10e72d06c6f4e5b85c33eb544d91d3645535b73e072d69443c906928902626d485bdbe536cfceb8e097cd6eae91b03c0d62204d819a9dc63e8418a49a41e2d987241852582adf66b66591b38b71cbe49b065cd828f7fa04ad80cc060df55470ac1d1eccda5f6a71950f76ee80ba76f5a1916547b67e0be8eb73eb2376a9a49acb25c6609b8c9365baba545d5fce0e553ed5b9a51b9b0c0764a51869b2dd5d81b895d047a5b4645a75ff48431bcb80f315cbee6e1ef8029f1ea2ce545e28328d1172cf63d13b88e92e4f5dd4e4d464ca5eee9e02ac32ff4b3b84573a77f815f209fa0406ebac822aa42abd7649e69b58ca01aa7295a09facdd19c97bef6d0b55684a962043f7d8f3ea09d4f0dca08cf2812056645d38d3360b3b69a8b1a96aa10cb3bebc694a76c8c8bf16a2ac38191192c2b53ab0b39ab722857643ef53a049ac17cc5d4128070ef9a47b6b87d47447aa4545b377ac3fe864ec5175d0befd2c1bd1587d7722cc2928cd19bc8c1ba8d23de5e41d8eea5a52e5b268b8e206040a7ef21a412270dede1d2a50ed7ab42992dc577ad0e1c0a1d143fbde2d8bf526ba0adb34f57aeb6a9101541b67b9477716bf78523e538bc241077501fc1ea35e350881ce4fffc7792ba2c2d4ec7fc6238ff9a293344019fcb1f880d2540fbb50624c29920ac905c0915213c07160f6d319e1eb8c9c6665327a2de5c8874f3d0e91aa1642c8aa91c3be9b71fedafc58466b4d939bdac0655590e97462a231f659f86a34bdd169c8a7c46eb48460cee6dc625e660b01637f66e03d7ad3fecb7b72843821fbda1aeeb7489584dce6e8eb0c4963502b6788f286dd46aff3289f008f7df69b3d83239b5da6fc99b733936a58dfe5df73b9193863b54cc3c363431bae1c0b95d5531c8a42426d1d4001c202451dde8adebb4abbf418a6120be46d5e3f73f2070629c27970606b7a223636770b8b194732e71896a0d4ba75ece1d7298dca7aef9394c951f94501e0ef4dc5c7626c87240b68c0eb82e040f45184d186f6fea8cb23ddd73ccff5687a08099fc84d5508f4f50f479a1ace06d72946c5752923e48e1de519c541ac00a353b07735ffec6c1923339f293f1bd2c22f889dbf9ee8dcabe7f7450d5b19fd759744ebc0cd6cd7bf4ce37b4b4562933137bd904faa4dbcde0142485457a0ef41a26428b9ecab3297416c1710b05f3f3780e4f5c8353f7ecb7425f38e5a9c202e3056f74e4bc1eb6921a2f3805f20dbbaa9808d7a1477a23b0b437e14a94798c3ae6c08e30c32c4a188ac09ac73781d657ca097a20887e58e4a9b3f12f38b3117561062929c4877ce67aab6e7c1bb539a419857b339ab31581559419450cfbfdd4648a0cdd2216788ea52c91bbe3d1f8d32c80e31af44abcd0fde7fcdbd134749780ee0da07a96cf17664abed925262f20e1824d84519841d4bcbdd1bea3178e4a881c3e3e05acf35af56cd41a1d845901d84cae3cb1b4a14f8e44c0cb3aab5bd6144724b15152d9e05bf7f5600405dc2e23d9056bbc5ee29d32c7f6d618bde1eaab90b7afbd63e3e52a9a6a0641f8acb1a208bfaf12a3cec0d6eef9e3d3ec179314c4fed56e3b6175851d8795c1858f5682705439345a70a89abc4bd9e19281624f19b2f3715765cdad77e10d42c1ceda826af0a92f26b7f362832047057b2e57f9fa53023d579c5f82450dafb259e04b22cbb55b9f7ff723a0371cb70cc8133337702c1f2ece56a00c8fd09717dda257c8d433de1e3d3d3d2537c56f05a5dd43b522d3759b1aa274a86c4a46a68e4a2b1c9d7f4f77a76418f912e0899d4726e2d734d31d149e49669d918940e227848c2ce5fa6c8c641a3d5a7bae74a69747ddd7f9ee4553c6fc2068b99c9860ad0bd48807d2567ee48cf1d3e5630200033b5728bed13de31673130b64fa6a6bbc1ff2b058e62e2a966719e665651e9eed9a6906db67ed59e6f84dd1470861bbf7aaa21ea13b7bdd14b0e815d0077e7add41bcddb2399bb40dc7e484aba9412c092f69f00f3a7ec97370f76bf37948a372fd4e1060330000000000000001ca760101364ffa054aea209053d33058f50f08a00c8106360630835f7582e26c2ceb7b5b7646107b2e6597600e743b20bae6be9651fd5a01399e83d5e1a1e90777ab37500ef34fa6424e8f810b36b19b07bbf20063ce75af11544dbe037d5406654732443e96c9beb1c9140110df0277202d98f85594511a3a42205834ef9cccd25d38cdd2f0a262e7f091851ee1233f7b790aca2674dd83d36ce5ced85f312981c43a02a436666c7862b9b0116116e6bdd325ef1397f25f96b915ac270ec322db5477b296d8c781b29f397f4e28294ff74933fe024d5f020de4e137f049523ed7248c734be169aa92e0e0eb5b508bab58a314f428279b310688f02459abd39c733bcd7d71e423938c39de72a9836c3e408fd04ce1996a9552eaa0b3d7572bfcf0b5b0e4d24d56b68c858434e87ede8ebbff2d82ed71b0f7b608161fc613efcb6fe79ff3110676122db523511bef3afa041d4d5c389a1a135f813b24ca90ac38e187c4b69901271e074ee961e050f48bec12c7edf531c49366f3cab6e50b9559662d093cf100f0b99b9527fbd95d93790e81723c255495008f01f1cdd10642e97239ef2d44961f766648f751f814df34fef0e2504225d4df08d50c96576d5bfcc1fdf301131ff74811dd385c4fb376034b2fc0ac7efea1aa9a51129b62520a14d29f08b3355685c5e28adc5ae20d5049e5794c2f633c8c7c494be059312f75a1f94b664899f3c2a3aa41104e31b547cd73a1a277ff85c229fa3ca91e455742189ff897de2291dfd6ff0cdc22dbb1de322bbf1836597a7c98d52358391a25c085be0c781c768114615537a17e50e347641ef865359ea2c8906c5cfa3459284813f137857c2cb757d2c42439cc69a774c73c78d52e5d039b3fe29ce0b46231956e72365360c39596e3384aa0045cd5bce628eae416471a79a033867dcbcd8112ee2cf4072e2bf3fea00dc0b53ea8e9475315d0d76de96fd25f405fe92f5f5fb5267b608ee32e4eafc4162daf6dbe85f4ff93f740fbd644cf15545e7c55115bbe152666f59ba6ae6ec7d1ccb669efd2a23282f00f660b24c75205edc802950e9157f9690c3989a4890e8bbdd886499d2a402ac07e865b250f22a6e629ca37e698b6e0b7056ab2a9963246c70d62d001c220fac5319876397d7ea37cf3e01386ac588c7878153ee5c5f38246536479426c0b3a6f24102c7519df3772de7df50f766778411cd8289e0b87f3e4f0562e3a740f14ee1eda42f27c4b8ce82bb3eae2a7be7e88672a0dca5468dafac8b1fd86e314a2e72973597b1b11005a032e15bd788491ad10e805ba99dbc19d8002d54e21bfc50972ce55eb4a54d17d89e09669e6e66922287930ae14e96941d21ef9b0f15b4c42988b6d3b47263494dddc0ab29496acad6312b8ed7db788983ba2e28383027bc67aa0ece0ab64cf36b6e26b7d62f9e157ac47a6f318b6baa553cd5f09634c5fc0bbd0b4684a7ebd85b5847037797a983dcdb07beaadb3ea1b9688be60bdae5bbb2f9f356167ce61164882a032d22204a56b38509a7241ea0d51efe3690c7cfabd0aa424c56ac7fd6c284befe349eb52d09a0e779bf16ea9ca2827d94ffc4d612b62c68a77ddd563fd08fc96bce82879772b4e1466024bb9e6c9219286ca4692be3b8e0e16a15f42fd65446e552c1a9781e9bf04fdbd788763af2aa532ece25516ec2e5176932b3eb3e36e264c5268db6e1521cdbd1125968bc5b25776dc64058f16b9290e5495efbb360cc105b57fc1f910c9795c808dd792f99e852d7e40166b09a5a776a6763861a0de0113ad54413015d68519d6b57190ba1494d8614cc44959ed35dd6d55705421a6f61844317dbe7cdcc9575ad8f251b5acc2c35237fa3b9efd3e5148ec385a7701426d3633e375d744a25f30c52a8e330e6042a7c79633b4549edd29ba81da3d889e00abc8b1aca93ef6135436eb200ffb8af3ce86d1f9389261d06e80672ce3330228070a7c8c61a9a4ab89bfb83772bb159aacd3857262dcad74d1ca129f7bc441f2b0357f693f2fe7d2ac2cb767bae0e25821c3f8c9c5babfd1b98123de712b5645af6ed7f0d3b1472f78360ef6f725e403dcb64bc9d9364b026ed0cbf7f5802fbc04e0bb656469cc69a6dff2da86720f9362fa399b350c6b736754602e8295f78fee67c3c981679d4974f2097b45bef54e3e3b009bf7c82284631ab9d672802d76ccf895b43bad8b163e6b372a9e2475a85733bdbdd4c7aa35a4ec761475914498c55cc25327ffd8a373d0caccde9eb4a357beac4671d588ac70d0ea57aae2a90681975d2270539f7dac796bcf33783cdb9211084d917ae0a08a5c29c45b355c1fc3c05c05dae13ba9a5e30a07dc7550e6e8c41d0dfeac3e768b898332739af056b615541021610a4721380abe6eb546919fcca7764c8fd18c341dcb06a6611c1b204ab2443e1531b35be395d4df6650b1d36c6ede69f482ff8685de4a1a16d1d0b42a0ac2e482138a568f3e093820f5d78afae4d8f66e83023bfab450ae579c5a478879e3db64be18ef8b60e4d2c7e5a1cee0176e549f53807d1fe471ed4598ad37a07ba11eff41a4d1ae3d96965ddc8660a9bd353fbba8e7b46789da2e8ab6e279113db33586fdd863f8f3021fd211a38fdd1a4fefaa66e66db4f5dda705ab4d8ab4e2c7819a9f4bcbfd53cc01966080febdffa375eb6da3744ac8956881c6a08b8d0420202ba7fdd7e8cada76573b5583323875ecc1d98529778a330c37274c50d26a99c4a4a127571716c4da1aa797f2e413a05f50030d509110d7501d7adae15b13e6b13ca979c693f6b1a1dad65ff05236b91800b7bbd30095da60e11a837ba7aac4a4b432bd797d3a8a832b69c09cefb3430b3a308ea4c4cff8582df8230d658def78a919228eac502e6b1b7d4f1201bb2259bf265712d8c21a9bbd12db467f42adca53c0d219a2083bfc012f8a12027e08b7925782e7d9f8e2e0b3817933956b6570b1853eed4ed59869576dff07e568383e80f5f18b0d07313c6bad67590f1768c95ef16d60e46076848be46b79add5f042c153d58d424545f24158e0e27d8d2cd5fce6b54d1e2b70a8db60c7321dfe698ff2353a3f3f400da28611a758b1ffb7308800791f6f01d1b7cb94b5ee6d04b4cfd6c84a1186139f60cc6436decbe86273bacbcb1ea132646843adf235018d1eba28360f6dc276f01f6f480e1a77ac975e8298e02f100b566224e4973c630836972f22f5977dbe6d5a2797962f75a92f83d7e489b9040cd10372921ed2b7a20bfc094b9613601c43895dd5abe32e9970dbbf7ff77923aa2f70e5b58f685845222b4791556d75f9123431a2b80d2d96d53299dabc42a9e0a38856a5ff64a4a5ef045a822e9945a10657297fdf680b0d52ca976e793397068a32a469b6ed2781655742c9c5716e1bc7f7a9aeb272411260a654f575c8a423d0ecde676df5d23090422ac0ea762bfdf62357b6ecaf7b73f93621457890ff51539b8bf36a21348e488901a9a1eb620dcda3906c0b7c9a8b194fd0d841b19c112ab52766ec88f3458e55c6a80c7a6e4de8d3d036d2e03cfdb59040a9d9e151199df4f1a2682c6b36f29d9222cd61a1b925c463b71d59a8c8e5debb22cb161ae7e3303319f3ec26cc68c821a95e671bca9fa6dcc68b541f1c02e07627ace08d34dab13a991ddfbfa26ec907780d9d1faa13b60972db886e6d33f011c7159d9902cfdb01da06088c67f5983793bbab51124f7eb44e4fd56106e9a72e1c424bfabe940a8421d694418b2943b5103416ad497e5df89445cf391393cedc393c1424e5322643139044a47371e6dc38d15a032fa0d27c2e7587b946c24aeb1882cde431b26bd409f191722177eb59329b276666a7c48796cf62c5acd7ab3442249c0d72d2ade6934cdb68b879a505a3b2f6166bb7b34b2b319587413d22a26109ae16afa6e4f690ba1763cc2797f38d2c96f6d73e6e5b4a780a4a0b24dcf8972b6c7581ce31b61b9c8a59b6e7b256af854a85c4e93b4014eeb3ed58988fa19252784b3c34bd818c6db5f42340ab99c9e4359ed5d8f7f1ae1be788c49faeed87666a9b159a09ecd776c44172195d178c98ba267d8be773e5c343ea13174d481a3a045c69b90af87bb842974f913eef7a11a0f55a63e42915420738d02cab0a4ef31e9a8af998a5ce949c45aa998c3acc91d9943ae0957275b8431bc16ad2e01e5eeefa254916b7d55fb727b561fb928a1d630d08de61c0a6d7d981370ce3e12afd35a93658fff453a9b7a16c0a83b3af152aaac3e5a928630d7e985e6c8026934a088f9a948ff442cfb0d6a136d9e2374519d38f6958784fc4dc48b224a4000d09a2153c3a0fe83803756cd5cdd97c01739e88bc1de0878f51e3364456c78cfe335a96414dfb48acda7332d641981ee55fbadfb0e47d3247d0a473c4d6a05a6f940ef2226c3cf98314abe7e261c4b1e5ae742946cc1a15779e4bbe35eb4e0c4626a9570626d618de48f7c80a729ffea702603ca198e584a832107585889b387d4a533a0585a114ddda42ae025a4a9b071700b914e85e0c26a2bfe2c2abb05e7f4f1d8e755a10daad59cdc175cf328e1856a233090589f2fd0b0826ac16b9a0243888da972cfe3c682dc08b1df47776026b099ab3690eb329dee348b81cda7204144c25e78f8d1b05dc96e94d912879df3442c647d3f9d2eeb5678c45da7e63397a31b8b562a1a3046a19dd81387c517178debd255f7a034b629336227fef6df56efebbb40e08138c053991fe62b52ce56bf51e5c2d7543188142305520e8d1b998929ddced2b71d7c2d481d0b0b6a53588955d2418c430768161bd9b883c882a1c3cf67c4d43924cca68d9d34b642f79b98bb03f96c62cfee3502aae5b216497bb9e5ee590ab5edb1401eab11265ab3e0edf4c44c6c2343d2e14172d8f0d5d4851cbf7b01092855c72e4c05dc6ff02679e4b01c325dfe12a4c0799655f59d46066b85dcf0c9cac313e10dec2b01ea3b9f53cf2bfa0101bd4c268ef58c925901c15a423eb798d7d20c652c0a62fa376902cc1c2959f6ea007a7d64407f5ca107a5205e6632e3b10cfea9cbf62c9757b96041119243050e57c7e5b964beaa74f1133e8b55500b15d8abc3ba31b963acb1bd9a7349c60db65d1449b949909f39020eac8259b0d2367d1c23c2ad29b9760c40efe1f116e0c0aee243eccab041ba3ca8d1edb36b831fc91d9029315cb6b3f9413ce2c376eb12cf507c734688146261ba78d4f001dc7c3ce3f5d7abebfd0c8e07b8c074861d8024824bf260e748580519a63397b76c4c1d61f338baa53a2b26ef49c023c36f70eaf7e6f886063b7b740b8b9db89120a553033c84a6aeccf423346757d67a91fcadcd19fd236c9f10946d163f3c838e516c9cb2818826c847e3b5899adea1d19f3d2ff3781347750220e0af12e3337bb89ae7401a91ae277c0d207be983451597a22f4233ba49975951d2bb66a5ccf4b02eb6041871d7e5e0f4385e5f4dc071203f32d3d0ad6bac14ee2860831dba2167f60945e09f381d9eea12f5046d896c88c93a0eed3db3c72185a4b9e2e49961c6511ec2d5a134e044fe57c25fac235bbfa5e2088ac6fe00c1e96febe67d1fed23494e121c78aba3ef50652bb17ca8bd92a2b05e8fb42c700206796d493bc07a92b2a5c98d80fba0799acd8ed2bb9c8c01c84c439158550cb415ab15920759a987cf47a4502bbf6da594087fb5e1031a2e37891faddbf99f2b0d621062e57f0b732614e64e4f66b3af2ee495392af93ff31ce2d606d577cc1eaa0cab834756ca286e6c1bf5a9b056b6fb425943db545b9923b1704c911bf8bdac81d02d65d2be5c6bf15037bc3bf23b4996b58b1bd8005c25412b91793a65b3a630200032bd37a0a6f2ff38470fe3d57e0ca3cbcfab9f2051325614695a62e76fed856f6482aa89c5496df94376f8ab32df34c1e953cca8d31674fe54fd87340226ae1258136999ccb814192c9429c497c49487b3273364fc80c430b525607cafd8aa10bfd4e106033000000000000000198073d01f6a37f774c29777793be0f2047d2497fadb9598333221384e9c20b90d40f4d554a96451462fab351c5efe0d383684ba200317c64c4fb6e68af11b1ea99c7a613cad205296917e5db49ffcef33ed9f837ed932686bf513bd75ed10f0b4f21f877ba8e7516ca8f005e9335fb4534ddbd80933ac2b45e1afc813ee9711ff78dff2e5270ac148e1bfb49f082663122d32dc37da06ded58f5c1c40f9a1c5eb5efa415a282fb12d9b8a79979695231ad6fd2d9e2bdb2bd1fbdcc20bb535734010e3e373875ac5796ea14a2bb60bc96d79f8913047e69301993e615b2d7db14f1cf68ef23d0f5a3dc651b13655c4f53b6b4b32471f333113df25c0563af6e81ac25ee54edde60e9de2c5182dca97cfb5f82240946d592ea34a733db513dc324c7d4bb36128e9272da71ab75f63602290a23038ddd71e50634f4cdc65fdc008c732f452ce0f66b543ef3707218d3bd42ea8a6d30ed61632330efc5f5d33e693b3c3685914110430edaf86c216cb204a8c5c32e00f5fdb4b78117e98d9909001f3ef91d7774927fcdda88237d3e21d124efc6b5e9061b65ae0577bc0e76548ed4440503012031a623029f6bc5241753d5e669a252c3ffa683719e246f9b3cd0e91b5fb7c438eb94410de040990d7a715d92ababcd3f940e2b3e5b754ea2d4e22dc535e899dd8347d886d0c23079e9675942a60b70bfed853053bf42341ad39f753786669693caacce1a4b09e0e99e92fce4dda16b5b494690bd3b4d06ff5627bc2e937eae4a9189214fdbc4a5fa2ef3a2d4b4c1b84118a8b11daa429672e316709a9ab90794cf3fb3657bba49b58c6228dae8dc12a8ada905650e2d19bed62df022d58bace41ae2bac6b803ba8b9de0ae793bb4ffd403024d51d4ca3020f094105fa85018643e1989eaaa6ae20beb60d20b459e2622458cc5e115afc695e3ca6d44be8c86c5bf6b742c8c587e64aa3b11bbbe34a3e74057ddba5531bc776a8a14a7688c82ca391f86478e8035ebc25a1abe1f61b3101a4fb25df3e7bebd1d013df6e305e15932d3e3ec789569bf8003256db34dab3e40a1cc603519f9e32fb07fe6ae448127750b0747729c194140273874d377e2c5d2018aa4b79729163f6602ae6dba3944c253861d8827038a23f87390644fb202a4e4ea95425df065d847723019e2c9df0b9ccaacf7a2e09843c196bfc4577113d85774b99f6d22dcecde92c5dad8abbc8512078a425070fc957d707ded416c0e24b4e0d19d436cd57241eb7975bc1077e2fb000d1e383f397cdeae7aa9098830f2132b5212e3af71377dae78b9bd01dd2a8f6ad52bdafdc4ea156371d58fd751b1a15f048425c073288e08cc0603a8abb8cf97e4c3a17ee0430866d2ec6890710b77b02ce14e2610ed27dcc96d123ac39d6d766ea9be303e22192bcd972ef566a93a0e76ecceeef0af07f4a52256543889a47733e29b39df1822633d0d6d7df94a69d9413e01d03f56cb72a5fd9b979f7773185b088f3f9c8e3a32a425943d8fb298aa8ec9c267dd0f54e84bac7d8a5973510beaf3f5ebb7a04148d5be5a94c9983c709382672280199ed8a981390bbc7e44a78ca7704116463035f96b3cd0575427e90a7fb1adb60b288ae17fc7532901ddacd61b589488a46268d7ab6cd35ce3384309ec9166eed9bf1c2c615d1a5c85f457cc77d8eb4cd0d62c76eb5468dacb57667312edd79b70f372a8888fed6bc938e2e03e00a2b98f1cce756d51dd1bb9a58cc9342b6a6152e5914d1fe8ed7a89f105c85a2bcc68232c37b6bb8011e48736162448f7063ffa516260792ac3dbba23680874236189d8452da864eef8670a54aad3d0e1c7639a1e2660df5ebb2d558115d7019459398400bd946fc94c55915e22050e708830127bed1a325ab42a2534cb6eb6b9caf35b3256a663b5b2e5094022ec79b69119ed05f79e21fcc0ae56b68fc96c3f44f9e25415c84ba5ca2a8ca1dae3fa5341aaa8b8654f00ee02a488e1fcbac573f728ddbe9ab52fd551b1e7c70d9031c3c83474df4a96c2ea407ae1040846f60ef067cf944fac0a02740722dacdf6bc498bdfe853a02058e18d2e9bb3cbb7dddbd5195b2d570e4b4dbdea3488d6e19f5fe4603a720c5afc73be9cd3ff627cba81e836fe460f8c3c09538898ac928c4dbcec037b6cbf55288050d35d39f998118c28954af70c13e8dd763655d94f3b21aa56fb35626cf3ec69592c3f8848356a0e074d603ebda5c6ed2d669b5bdb01f01354e614f5d847bc7abc34e32f52129481206658fb6062291c975ac1bbd82f316a3ba94d7e7361a1b950d92f7dc7ce209806450e223ccbbf2a13822abe9b1148d1b0dcc646df726f64b25b209b9ebd93fab723fa52c3bf4c35fb90ce659fb7adccc27ee12988fc818ee0e902001c5558874a17d1ec0599d93a4a3463349990589535bceeb36c79f959258e3aad63dc6682542ce6139aebec5704a3d8a706f2a727754aedd879706fc981561f6fe52172083d168735b2b06c2c8d4f502e637160db5706f7f0186ded68215840bf1e2c2340ed0fad731c522d39dfa3aa8f3ee8719b602bddb4dc7817b125474ef739f5260334ad79e9c3d0cf0fd4f4b6d3a9c3f16c0381af67378f7d51125bfc537d025d3bc04dabe91155bd7dfbaa022bbb88513cfb834ba7dcea665fc934cb5b388c6f7f671bb24abb35819f91edf4e929004629d8bc550ca4f6926d8bd5d614b9e484f5fcd74173e8ea31d773e2a01025d4f8d7fba1791d03c832a40614a75b6cf8c95f531e9f0a2eb95a5305d271cd30fc351d36ce3c032982ea4db16ab47eee42202ff28f12925be83192e5e75e61f73ecbcd41d71e3146f9cde8ebd0731d61742d17d4e01e3733d92b5d4897b2344db30030bef93c7b99a2ae0ff5755b1043e80f99534c485871c420e0d23d0a8ddfad00e55c41029de953abb18b186b85a2bd73d6b20a76562ac289fac09837a799244131dde02736112e391b2a59662cf6f86b4cd2d23b43512cb422917f3021d5079318051bccf77fa9d3061cc76cf0616aad67b15bfd16eb0f7bf4f0a41ccb97ce124009e78b94bb6130cbdc422ca7bb9a9883c66167eea8aa0f7392c792dc7dda06b956cb65fe3ae31f8def297ce0d9e1eef735b85e61253e0eb365b115fcb956218dbb09511bb396081c95e680edccc2b4a04d54283fd43291bb6d393454fa7339f7b029c67e76947d96634e4396cdadbe84730ece93ee3bf5f0eff4ac5b17c480436abc241ff7a3437a3769c2a6b3eaec08175e3c5729e541e05d2d1e1e3d6c04e95351159674fb25e7f01a57457fd0b5277f2fe7a34afb5fc5903d4828016eaf7e29cea0dc1d21926b2fbe45e32e9839a7023ecb6aaf46ee2ecbd2d76673a9531b611a7b460f2d564b4b384009268fe78178f0115def0815fc5a546f7ce326f726b33f3d53dc9fff010bbbe1b15469d4b1c0974365f9b241f8fb9d38894e4cdb2aa535cf23ffa064bf88e0b9369c5f34687ad5665d571e72671268268c5ceb688e03a1bee1c0036c3ae6a665198ef459002c2cb9cfc30e526fa7b4e2e682c1b62a43da37320fab1f630d7be8c5b35022942223c6dbeaf12738d23025d2b439e420b0967ec88fdded39727a814f83b9404d322790a361ea3b980038cae607792b9651da67a737c226371120535c2a2e2b98ba4268fbe9ccbd7f944a9d43417e409c7ad6d79440265836eb101cababf82c0f4d2362e64800a349f9d029ce969ecb341dcc3b4da07aa68941b96fbe18437a16e721a396c807aa76c1f998dc622f3dc1bac66784222dbeed23252f6299aca326516b5005853ca30473bca5eb5fd9f52750fb0e4c1ee5242b677e8fd87cf4bd9bcf0f5f719faf35490a7a8d9c7e87d4847a533470951570d500ef66b750f0adbed19b6c5fcd425edc7db7910de8f92c8e72235c3018128eb27b6d8259fc119f3227ea1e2ab7ca24aaa93d77bd5ecfff16e723cbefbfde958a35f597e4157e685fe7789c92dd4755b84103affcb4d60420b9a154de5379b344415399b183bd422cbc68e3f2a65e61759412d31ae2e4391098e64860dee3a81381226e87fda7a617dbed0a7473844d7c01fc1a3e23d13a163cde988f0a8cfd41dbd065027e4bc1037b3b6b6dd1e4e91a02b97ac6be137425e647c94d49a8f6384ec0ae89d56ace0a48a5f012758451a3b9dc1b722f1f0401db7cb91fc8b5a30d9b63fc24263533c71ff23488e823884994212e6d2cd16e4ae4d4f91c459351981bd859337dc8953c1f00702f4cfe94967bc62581d35e4622937f835ee66f971ee5a454546eaff9d90cc3de3bef208a7846aaf7b85e44f474ef51973f7881046403a87c84855fb85e37e7c243c3674b5ed5f5bcb59f80ef154c191206c893142b4ec0b87380cc9d9526a9e1940be7f7925f41babb8341308ac2d4bbfb34c893c936584d3bd2a17d3848712598d91ee5f43143e8a1715f46fef826d71c1df73891567c5d476cabee0f0dd191046562b652564a4fbb104fc128adda0a9060b5e195445f04594f9d352c61e9c7ad73d6f67078eea9d31cb3391c0c72296209acf91b10aee4ae0d7b49c40597e7c8ca9fa36da17f94d87fcf60f8857a889640b40d8682bb27ce2920718c593e3b12116f0ef50c4e7a522d41218dca1877012b4c7786f4f1381db68a28e8a1f6daf7ffe285ff40fc2b00664569f5d837cf34cd3df3bab71651e493a18539fcd83b79bbc4b61efea1fb43f6499c1314e68d09a572e84c6812094933465363afc2af22bb66dd0fa412f2ecbc1982f073896cf0c26ececc13d864b07519b214c38ea00b55253f35190e263402bd6544e224ca4613a9111787c7f88d74fb40795d132d63fce857c2ccfcd12330a1b7d3dd76a8488efb4e39b2686c1c07a5b776a1496f10de7417fb4a78bc895433ce25a0f4f05acc5e6e3b65ad5b075d8c13a7524bd7faf179c92bc2322574bb01b7b71ce1bc86be62ee1e6c2960593f131c078a153dbf64fae93b47db3c85fb57507e645ce96389f1ec38cf4df460170b1c6391d1dc79ecc0bd653d5d86d10c06aa091d43f78595ff434f8e89a2e8998c0788b6c01c4003416a6a835d452aba7dd95311a19fd925ded197ba6f19a58979b32bff5fb2f83848f1c7c0c303f2df6335a9ce1899e5a62a879d2cfdb8be1d5b0aded6eb213c32b8ec7dff63f8697316dc66610b4d25b8a789954d8c4d7a610a0f8abc83b318e679af81e46f009b3de26edda1f40164c7645a1321440f9fbbaae7f39d179d156acc0be37270a9dcd44c7bfb739d645ae6efea863e79563b01d4897b48628b5558f62850ffa0a2cdcdc074196e6f0fd124662afb87526da17f2abd9f13e4735ab359f9e47803987394c1717016f724c323df369d0348ba7e31a0c1495ed1f6fbe83ec48e35ceb3fc1b9558c497c294d7ac5bbaf4167c287f5c8246807633b17ce639562744c58d3342d86c89824dfd216ba493cc25b7ed068dc0102141b114d92d1a86810d8add066786a7dc9dfe70f473fddd0e6ade0005d52ca1cf99f28f2757db164de0bb833b63800d643196884e7cbf581a419970fe2933cba4f78e0e427e1b53be214e7ad8a513d574f012868d9ee01c483c6a4368c9c79009ff1f32ca55551ed94c56eb6963a7e2bdb059b0b4be93e801ca703726a4262ebdd57160380f881fa2a2ac5e52e5d8b446cd2aaa18e275954e8bad1ff3074abba975223668df251062fc10537d1e4feed5d8b8ae6ccab3ef33047cbe313b5a43c8879dd6965aa1c26d3d69001e21c249af6c30f66e18053bd7aae86b7785b3903228163e65d84ce96afec60cb2ce00de9a79b23d095231ba933d4106e8ba9b99d6cf8acf8cf345a2ad31b8aaac06591320bfa9a2d3be6943494f630200037be7736591b2ba8ca8471fde566706a0109b2f742d4f2c347f87fd601229f181a8df30e72c640ff779f01936e968b17cf140e3a1fced8dc69456580ded3a28e010ce0d49a80714ceb55b748eec5e31ea1a0f302b8a34bbcb60085baf562643ddfd4e10603300000000000000012f98a00000bd5d41c0d353354784d6799c94be5e1e294f8b119c8e813bca0b8aa2ce663c8ff86bbab583ea25a99c425dd50198e5122e2ee933e3122d8ce975c357acc282f64f433009fa09e000689bfb06ac837e6ec4f441984d2960a0a36a23510ba3f3b925f3da8b36b0e270dadc8c18e3ca81fe4cd782a99087d34ff3dc4b89dba133915f7626d1bf1ddc2e9ab4dcc93dbfa43658e20badbab6d2ed8f46ff667ea549c46b5b15e244b513466c361d511f612c45486d5c0a80c3190c39c27b5f42782063c0eae70f0009d0e181a603343c858381e7bdabd4ba4a56fb8a0b9c8942663eee02072b09f59eef3d6ce60acaec61ec14c604875210035d4e4251e99ade701706f3521e25ed9c5c601d8e2206ebc35c8bd89ded58a3874253962a3302b2fb1b23e8d73f3c0666d43ed5c2486cafd01327ad97c36301bca5f2d0e4953dd146991c2fdf69b6d8499c2755070118529ff56974daf3718d2f630027d4f76a2cfd5fb0f2204986abf2d3b5ea823a276db1a20a00345631c014bba065c49dba21348ce9992faee928530027425e4da60e57b91fa0219af3d40e08976f83121d0704dc100ac3c4add8449c88fdc77e5d3aa787e8f156856299e935fd77744b083ecb6ee8b799c4a979f44e131a2266ab7b4210aa79145d71d5709c2ee2d016266e4d5eb6105d36bdd80b896142383a9f58bb1b6d64ff57810bab9f49ad3f3ea42ef114f55826ca6f506bafef836b781ada2465820734ba3096561d26658e95a028ce320224fa03c97fde62d6281d078cf1c6f492f5526d3b3e4dff3904c0665af2bebea47bc337a16da08c827f28e4e1b9193fe37318f17c4f941c5fc7c0ff624236398438334d3855faf876ebe81f22e5f45652bd582053daaed6ed77cf3fcbd20a13c9ba0184f56ea01fc454a679058b1125fb11068bf0066da17f1cabe7ca107af08f7c35f0244f4309a52f5506fb72e8c3b2d19131ddd63cb723588dc2588a572d1c64ab6db9e5e5143136a98749c8dc996c08502e8ed92990f5bfbe22c08e3dbb04c2000ce147c2908c5e7ed1fbab86a3c0d6efff499ac6052fa8bb038c0601c609df9daa6202c1a6a1b99ce3b149d0a6210eda32ab45aeccc8c070833f1dc0aa7bb282edbf92e1dde26c401820d5c1f0876d83507fd8f3013ad3cee46cc3c7ee8553678be58aa1593131c08d68dc8e74f8a0c2f859d40f7f1f0b61172ac32c7ef7d1c99d6110e3f36e651acdd5f5e1762dcc611c713b41212f34a746fe980506676f4c0214bcab019fee3ed65ba433ae3e1be6ccf95b507ff7ae8e2f1a3294a4a7950b77a51acd51b0ca33ae452ae5ce1f7a8b6ae096af849a0db06f645f42a110e67a0acaccf1def8732f719ad57609bd04eaa4183497be5d3b3ea16969a5d141a8676ae1e61080a406590c8b8afec30adb798a932852208e897c25c636048ca5d7016b5ea0837d005bc6ca2e740063a7f10bc8b58574e8107a14ff61684b9384e00417df16b9573eab2158b2c47e421d0743dd6b0f2ad35176c5ac2b2dac6f4fffaa24e167d7c4ace840b9657bfedcb635fa850947e0042254ca005da25a46b1b98f976a81ca9fb6c999c65e490dc425e7b255575202910477f29bd9c34d7fc364f449949d0cdd860d62bf38a255966b4b567b6e37ec02c8b2e5592e2f681e0fe9dec7f4ab1bef82e328a79824f15d1a721b5540bed11d80fe5c278cda6a69bb13e1e9f086cae88b3e529171613bd5c7d1e4ffedef6d21f523a2d9bd2983d811b71d8d4ef8f07131acace120c74ce4a1ad3f925a90817a3591dcf996f18434b189a86eae0409ee815a676bd5644395d695e76939d8df803a3a34df7288e5752898adeb42b12781387e3319bdccea75a969b6bbb2a6628a0f5c0eb67970570984e9f066aa46d19f9a56783e4656d419696c162946cd5f921b904408517faa1eeafc8928f79c0455e518292d671a38a7ddb8961999b74aca5c6f6409b54c1712bee9832b7026c73a10c7d73b1de030caa04ee2baa2bf626b9eda1f92d1f224a2b8d810ccbe1fb24a5f2d87f427b99619a89d737fc7be1090d8aa06c52ca7b593d975a8921bd596fe211720556b1ba57d0372491453eff3676240ce16bd75c28bf54dc047c126df5374760effb3bbb5c38577c5944e09b0bf47730d044666112872f8f8675aee7b67d229f0b626fbfc7f5eca429fe9cc415f390abfb0b48e54468d55062b13ef0ba01ae055e79b653a0bd399362d0d8a0a49581397ae3cf5dff95f9066b94c6e7d2197e566f82f1194c748d0b221da36c35b4b727d44f9a772c662107f4aa6af69e9e841d54109d0e8a07bebe4ef0410d4488df446723562080d398667100028713152f00a67a00ba1116522fe98c3fe5bd453570fffc274429f69298f1adfb81c0a9809f7227e609108a0486056a71616b2a9f086bc6cb4e7ea0dccd490704ca34a709cf64137c323193638cc81a36b5ca770a29d2c9ca905e60a66e87b9388ec13ae355afec3ed6759061fd6a4547ecfb8c973c1649848bb24062ab0f95a7d827635911eb5a58c598ddad0c764d7e2bee73d2d16058b44ad07d954e22ee5ac27edfcdd73745053ddd94ba1c601e9684587fbf377a9952f0721f0bcc301c7200d4ccb7eadf9b7a536e8276830854e07cee8218921afb32fbf6ef595acaeeee67552303bec592888c24635d6da179b4f1a2e60aefe24b7daa000b81a4fde748e27dfe7812d267c5e903efc0e44020fecb277dd59f4b4b035e9c68347e4afa6bc65c3d90be7dd9602956331d79a07b4e968cce34c2821cacdc1d04f31c8df196a7f1c5778fa63dad97bd93c8c77ffe6237149311cb0857d282a0607d2e8c2a22afce9d8e58e7ffdbc4ba54aa5ada945ae93116c643adf6beedd4c75afea8f497427730d0639f0607cba7cc14252c105058073fd1a4aaa1d334400ab67e8000f2b98bf51cd21a76bbee4c5c646319dd66bee51c37cc26bc2b5176d4a1e7f3a773f814b2591b0792ab16992b8140374e787ca3362b231d9b01dfa9e719831c5e13fab6de0c52414257257cb9a98b936802ed197bdf32fd218fbd7cbbefa612f3a54e91e7dcc431d63b030236cd19a26b46c6939ba741cf02199502637d80bd8867bcad952e5fa1d060b6a224048b88bd848572dea4a16848d35d9e023ae0c7c23111730f5e67199f12c623013e2517dfb39f3ad49332e326299da54e8b6ba5b6f365adf6f7764cb7fe4d09eff2189e766da92cdf5a103830d8a7ff42779ec3d5ec30f5de61c756bd17e54413c6d93af2e1170b253a95bc62cdffaac6309896f7271ee4a52df82dcd0d09fbf1b8d3d3c1ad218dffc285e229efcbc6402aadb20399533d452058c4df0dca12bc6a86af33f01d68a1aa15637f8658bd84ba5cb794f4005eba3d41db92a4c376873e8409ae9b8206843a1b0d20717cd045d3ac262d9c66f4f8ff92ee57999a9de411e48ee6b979d738b897033092f873626966ced3285f90094296a4c4c9593233d6530743feff6b1f9737fcb2b3f183c5ec4d0807da9554bf839cc2dc950161d51a697ae6734b1a0a7accaaeba53a101bbbfe0a7516661a2bbd5fc7df0fc2df04d0ab1a0a4a0f0b0663180b66ed004828fdfd0ae711e3ddc6caf09828750f396b4fbe3d90c111e28ce84fa227c12295d96b15f56b70aea3bdd34027262eb7302d8087933a8c21c3d9fdcc7481bd1a27407f5e0789df2b3740ecd1c91b913ba160c2cb2a7e1f4775e931935904c46992d5f2167c0eee7dd78260337f5bce10fc6243a602d61bb557fcd6ce10f76fa7057318599cf29e3b439a8a3efe8cdc0bbc71cea9bef783c1e5614b3627ae60a663740dcbaf2e31e52ec713df9d248db2da8cd02a1dd27fde69d59ba23b4523c21bb5802ce28ac03f062f086c48a30518a8a42bd493ea2cbdb52b0a7c626371cb93e8d3ab754bf6357120427c454313926d6c38f69facc4ce6ee3c02e0933c3ee0a9d4e0048afbbc0a98037c48cee5cf37d0dd6a6229b16ee98e15362839ee0fcc6447fc67536135a95bd23d5718cc94e4446fa35abfc88849d9be5257f79e7fc1463f7b190ef9e8226ffa106618a0b3d51f5b98b7594cff74940d341cb110b4426ea29c05a1ec03df0b2707c096bc1ec9b1ffb00fea4b94f92ae485a404d737006fb90fad88adee8fce4759eae0bee9630c055679410f087b892e94f68867ad2dafc3e8a819eb0da6a2f8728f39490c25ea340536f5abadf0f34d0ee35db2ae0e381e88ea89c0f9cbe782b9a487a9ed6c58989799943a4c0b2cd32377393f92a976eb3ecfbac01897bd2a04962901970c11acf3546665dcfe07c478fda7fb5c031bf9fd27bbf8da6950e3a0a0e22e7b54139ff5ea2885e3edb25a93e3435ba52798c5b4ea6c3e9e1d4da373bfa26a1e378229f1671c16ea4a31b31c96c557e1c3290caca01c9b5af61bb9f5f5156cf6c5fc29c477c4401b2d5ec4347da0f97b1a5f29962c94810e4655f591f54b4272b6b966fb2050935af6d81771344beaad24aecd8fc7b587871992cc5fca42a5893c5b5c8cc725ff92d823cc82590e1805eeff2837d525c5cf5d8b5f2b1b8a86ad6115672d5c2887a1a77ace73fea0bc89045ad95e300b487d3f5d171b8e7cd807ed433491c7111fb15e7c27bb1f89e51ad90f28e3d874295d87a4087a71e4822e8edeab6b08487d40bbe4eccd29dad8c7a091cf5e018e64320f1281dba81be48e02610a3e7758244661d16b93671619372d1f7a60d41b4bd2d7b9290a0ecac1250f7c34a6f4b8733fac9c1c6cc1185286c1f1e0389e70fe44e4352c7d875541d582eb6ac056d07cbc2b6d4e6101506ebe9460c304a80aa52b61241243c2790ba214b12a3fe48373dc613ed898fa36e52d22edc787ee3e363b0c7bede14e7ec94902689c0f9dbc07c04fcfc20982c01ef01bf5554767269a6aef082cfa8e75404ca0529b9b228e8a46b5d759c698f7e05177149c06d2b35d2afeb3083f32185ec198af1c5f6e7d3ff383a00334c338f6ac5cf0d5ddb4d21f1c4045a0e99413f66f511dc029815f53828d510bef8492968d4d89cd527b2532f0cccf6760b67431b0753a5e5a892371b264ea5d85ebca309fd18e0f79e751d5248d223abf799efb2b3f73a95a6969fffe99938dca6587a79d87fd9cc9244a8b99f5cffbd34c369fe2b8b3f602edfdd79ef5553b65a812335b66700f6bbb4aba3cd74aa52f05bff763cb13771038b3f4e481a601182cc386750bf91ba6e120750b99319c7a600c18a90407b71da5d59f324605713ce9c60cbaa0bb197132a35b1df94fe052763d92b1ee191cb247db9f9b11b650d36ab963a55de1bbd30c8907cffa35cbcbc4d7f3023d05c36937892dfc8aa1748d1b0602477aebaae696b2ddd64ff5cb4ee9efc4dfd5db2d03bfc542f93613f034627c68c818bbfa9b11fd5652cf26ecaf51cddfb640bab63bd7f19d22e070a7fcf4a3df395eb5c41e4d45533dfdafbe907b1a320bc1833bd999fc9d0dc3e0e5afff0ca51d93d06c1dd27f8a115f01d3e8247fa9b6329edeaa51ad1b968b99da66c17e8b5adc46e2e20c3ef52c4fc345b21bc9a9883bcdf909572e243280210ce98b9b88e5232e832848a76de47dfa62184a85f7e519dfded8486591d97fc1ede7183d839c8fa62aa4b51f6627910e960dfeaa22eeeab9e4986bcf68c7e7c5ea303b63a1a12830975ae8c193b7168af521cd2276ee08751342a069bba903e9e2a2064338bfaf81649da05531a1fe89e8176fbeb845edb717399e8cd7f76d3cd92e3d75497bd9ee75254e1a8d58c40b2318f55c2f457997c0ee15ad24c787152e0b319247ee86a713831482e8b7371141ec18513c6a95f4a02676a0bda26dae13aea65344ffd52e630200036b03bd8105030ca50c875abd786ca17adc6b36da14ef7febd25e176e7e62fbc89083e17437d9903f83f66083dd47af0d8cdf905d570293bce4796a10a905b76808900121fd1a6c1c61d1eaa50725806a83a83b2830885f2aca8da225acf3b69bfd4e1060330000000000000001e0b5b701508907a0ac40deabcdf2bc8c7cba265ca6ea447ac57ea1b1569ef26120c8a218cc6126522fd9d2104b447f7a170f19155ed2096f8a5fde1741fcdb9525b9bb52fdfb971a0677bdcbfeb94c79eb5d2891a0a3ef3e6944dc1395a3cc23a61e4e7ae1b6eb022a2b7d80d2d676efdbea03f69b75c8b98a96c7a9256bf7c3d6bfe76e472e38e295c1857a0547e2b60725052ee393df384f033e5c74843d1e7c7765aeb3ad8f80ee48bf529353e1bca2b9b63295df3290d179cfd94e44dc0095237e1abcfc5de614b28f6d1b8262f96d3cb26a7fcdece97659b373cf7c6120fd1a0fa306e2911ba58aa1d9475f5ccd160eecb31268e5803899c141c6b99e54fc3060a4280503d651a94328c9d419e4bbd57590f04a36526831d012ab27053261d948df950536557a3ec147a16d32695afdb9499a055a591890d17af50cc7cfe3650d3238f2104c43068c3c8809492b2de38376fdd707c4624dff2edd658b186ce2b7c53c94e420cf532174836fe50484479bce740b01124794b319da9671a809b957b58c08b9beec59e764d994c464022a471496f7297a64d0fab8e95d9aea2391d024d49bc19c3fe449bc158066b3bb99a8c3f95bde4f10e3ce50cee386c0cf7f867c5da3ba50060646d9b4ec5676c6926a2822de598502c270b90a05c946d9dc15cb1919286cf54e271cc411dd7a6ef5c765104cec7f0c2d239ac8d577767224ade773bc6bee344a69b1783097f2305faffc5726ae85a1a641827ac581ebbcfb309faa59b206a1f14bf36ae8f6394f942858554072a620eb6c7dda109520acdc2749838fe0fe599ce0cc0eda9c08ae47107acba48eed023e0fadfb95225bcff5f1ad2cd122ddacdb7659977b03070bd5faf9eacd0f7ada7acb48cc90b48f1390a6f825c850f44d6cade8392bc59535ed2da5c6e67a05e787bdd312c075838d3d365786e82a20c934802ff3afae930002b5bf594fcd897b02d2da762e84bf8cde00fba138336a4053d87f86def2b78de56f7950ff1f07579a401d5fcb31117cb1d243d139fd5a1a6dbabc77ad27fd5facc6db62a29f32c274cb63e69549fcbf86597338a399ecf198ed779525eb8cb12e651bd50b8171b2033d6a35c7319afe1ed634db8258d43943f3807ea3c29c69db199e40c1dfbb930c851ddce91ff50e9ff3651b2ce05675b2ff5a5549386b337fa42477dc39b1e4dd8e987aeacced62547743c5b016aecd724cd67dc7748f541aa035da8ec2f90b7edacbba2476adfabca9a9c6e32ddec43e93cba36daa0bbed99e2757b0c10e3c1d542f55519cd211a9f51dbef6f1d9f72e999d95450a9109b55db8b1b6850238a8c81b56d565cdc356a8dd2018d560a93f5cb6cce46460e90b4f93be36fc5044faf090e429bc52c348c223dbf8863a22719a5592773593dcd2c26782ce82e6042e01f5d4498ea1622cc5f82eef9dac219f263393fd6d4e47ab702618f947cae95f2e9618209e65a36827572abf5630508ab7f87503790aa4349a7b624b4a5d5a3d806684664f27e0d24c9f56cbe10cde282ebec4e64a772f4ea89669177a7135c732a30e33633dda56143c32fdbffe34c7bbad4b4d9410c8e7a93bc26b83bf3f900a55a4e3de8fa3ec483aca29a7b711bfdbdf1580a12dac63e2361cf89d1283675ba01a7439b4f46eef56537946713fc3f854b458b6707ae2cc44680e3fb05c3c24e2dec5955253ce7750bdeb1af9df0de09fbba3151e91992a714375a9cfee361e5446221c101b47ab262a12ef30977f227ef5f7002233cf67dcdf34072dd826b9950091b75027608d5bdbfd774ee80f098b8823b2bca996a04266b87d7f72eb5ab272b4745dea8a5d7f226c2ef752761c49dbcb18f13b3916b1663ab8c933d1c8b20e337e66b512805446cbde46a62aacce46330be4ae1331ed7565890586d3ee2af5fb227fc71cf112088b16a841a136d9cda79c6c9f9dbca642ca84e1a7702f38f46aea59b82897e515925e7e58d59ddbbed65de6b9cb571b2dba044615d85a5e6fb53cc456b1e9bf73d275947246cd36b07fd8602c231a1464b5f5988f991bc872c15da9452e66b697cdb4b5f8fbcf597369def719eaa3a4e0305386773502fa8dd6b9773282c5dd1431f1881bb6a09d3a7d5a271b22858c02e600b9b00fedc11f0cfed98361851cd34e1894187f40828dc7ed8f9714b12e812eaee8d3d163c5cf0635737258f587b91aabb0911d89dc8d85e0369b12462aa64f57ccb1ca197ea2d13bbcfc2d7cc0d37fe45bd063e2ac243c66a80ea4a5c14838e33060686f79af868ddab218e6d60b546b54d200de9e3f6b7fe092036948ee30dadfe708d2cf94d276687a64c03c0591188da5382f3adf0815785788da538bb4c75c3020319a542fb22432c86eedf3644fc3dcdd720bb6c83bc8a136dd5201d2c1ff8d242e5bf685882f907c0ae25c37072e15a29ff87a211bf5d9717ad1a0709c6794ee60af50d25f5b0b26dfc089c3410493d6571250eb209190daf8fd27e028ed44ca27044a52a94f7d5e8638638efbf95bf80dcacd02645a3911451ac7c4a12444c7571cf3cab55e4d655d62cd4fab864d9439f17f9e60240c371dee07ff6a250c5667fb2f38f729a77f12ed74cb479f6ac165ca4f2197afe4ad52f55a1c5f9b009b15dd24adc682bc092cebc68fcf467be1aec45df556feb23b925498d9bdeb374282ee90c9887cc636f6b235eaa9c7cda84b5e1650f60019c0fe6a3e28eebbf3523f404dd61da36d3a7c3aa7ac434c9d24037ae5d96855003cb4d4a081321fc5a1f333befc62d6442e30ccd2dc2b6c2ecba060e7642dad07a4b165480a88fdf53df22a298278bdce1f72c0df4cd6ed717018ef1a10d843ade3cb265fad4f6547481c7db01b0c70dde73db94502d5a11e234e87b12fab35c212f7a06e4455550eb897b7ba465ac439a00a19c3691d6bc9506f50ce11948768ee00cf975fdeb59d15629cc51f35d74f875a4b8dcd7a7fd6b6b28da5dbce182dc9e34eaf6c054576b69773150fc4e60cdd59195f061270d8651e57ef60559c1b1e7194de405bf60f6380179a2394f0f778b63f7138d3a03e276e7409fc93d90ba071dc2369472145a393bc357a250291b69082ba3e0153eb394fb6723e500521e3e7048e53ae82bf37064396a43f182779e9ee33e47c88278bf0c425de4ecdddad2e031fcf903b348653afdb24f6c887c875c89f3d9cddfe16ee5b9611b5b6e864cc145707e0bc524a7806872a875db04c9094b927d03907fc2af080379613c60f1d7a7417c1f6fb77227db09ac2b31ddbddb0d8cbb454102d3e2e306dd199707d8d205e0367ebc11e3684ae045a168c7c6920ab69260ef65a74ca9feed7f4423eb2575dc50a0e441593ca0064c3df12e778ea4deeecedd1df6935a84fb07d34590f449fc45b6fbaee1b25be1431624d05770dfcf7758ad70c973befece8feeebdb4d44276aa7362953e3edffe7fa99e872aea2b79a1e9e2a97bdd9e7da218094f479467f9cf494c9173694092b4fc2e2606d542407f02b09bfdb00819d79133cd614f23a006e0d0e2d2ea82eb89726085ded17a0931637c3f0c2b420905eac345758f255262b4efcf6a765974bcdf1d3c0875044754d316c5e6b0fa6b0e815afe85be70bf2e0204d19bd5ff540377cea70c9ac39c7a246efdb06328e57036fe9ce5eb6e2c3e0cf7528d581d013c6503c57e6b1634a0fcb8efbdcf7be65fe33191a79b45d7c396b4ed15895428f5e962d2a55981c0d708792ca0a68b5eae3358fa847b41963600a701c26ae788d9d65332c57446179c335c74e5a9029ff715098946b62ea12237746cfbd51fec119fcd876bb3df05ef1efbcf1a6af929d1e9fcae8a675251e04df2c31342c31c6d867bb456811d68e05fb0450b030c37723db3c7c1a3c3f40add847048eff5f9e1c4153864b978446ab64b3e8613ca7c855302129c7c2b4657634ee66679125728153ea4a093b9949d6dfad8d0758ba11aa799d395cc556bc433cad4219b5d6f80699c9e19ae4818c5bd5826c01121d8da63d362a45693271f0dff2e86df220f56d5d3ddb0911558ae4afcfcbd65fb707a80c72389f529a2d419cc398926becc44e789ae21fef3f7321494f04cd9c424922be1d63b2ba0dbb3916d2fd7ab2621f7dc3e54f543c272e3d27056de045fc7e451d7a4445171993de5e1e1e9cfe7d4c364ad779a73a930eadfa431b6d65668e1441c8024cb409c93c442e305b3b29683eab4960884a4664bf96129c41bbb7a5434c1bdbf40a32a8791f8c8acdc98c65dc04f2aac2368348d69b037751fb390198dc0ea1d8b35637d978835c21d42279c8532a2240a429a587dc5a0fb53b6d17863c2288f70030e3f78364ca26aa0410ac0824f298af09c90c073f1428c725cc90325565dfd711d0b22fcc51c3a9e86485beafdf02f8c57d547bc0ac0f1d64b518088c8ff54e1f93bb2e9587d07a71458ddad81693db75c126166d048a6a26ef103848eda1b4efe82856bd382c013ec3342ca98a657a12c6d5313b87dfdf9d62a5e42a665e53a30fa3d8747fda106db58d8384fe16c5340cf05af3ca04c9c9b3d1595070d8d4265a409a0ca6a2469468f869bcba578e0b7313109e15dc3904404977f986d8b740a30e89a818d5b517a1577c5fb1817f71dcc239273fa8489ba06529e3c4bdb23dd451bf1bbf2f6486972d1b401111d360b68fe32db7a7a871811d8cc19ab93c93745f431f9402ba7685fd990dfd7f61e76cba87faefd86d99c5510ec197a7fca4cff237de79d9b8a9545429f3e17b54be06820f3d651dd0e1bbd0a19cc83f59d134ed65a4423a00348d49b205c3d5374b10edb19dbafed8edd7f6776bbc53891c7d33dc98bd8c568f004c3de719099ef370c87f99424adf255e7c0c70dcb353aa599cf79246a1114d80a78fd1b673f7932f5cfce1c9dff7695e3908cc1c303c64176784510073e94bf09f15c6153ffe86a4857eb568c61b48cf7c1fda0b5c53d39dabba10d72b51e152b052b87abdb0d270479983e90687ecfaa789461661ff4e51676ff6b92db9f8e2e0e418387cb375e3551a87f9d5e24017e76523e6485c877126933e20ea26c63c3b58da9f4fa2031110bc0d79cb82bd1695d8b41c32786d9d6c5d349697789f00a0734bcc1ecf0da15b5b52a7246479c686a18fa02309611078abdff25c0cdcb924a42b7e73f0c496dc73973854d8d40fa34173715487de2b2c3361c17b6a5e5b21fe69a8533969c203b5bf76a3a785531b9de2f2100238e9ec46bad2a65213bd2dc292757c325881348ee154f86bd71aeb99672a7c63b5ad521423a4be566bef28649c2f106469237bed78569dafcf99eb1445ceb598a9d5622075e4e6b5f058c24338546087b8e9abda739135e93d4ff124f5255eec0f2a776cdf2b768e77f511c178e4690a497d9af829cd7852cae40daf57d9b99cf6df190a9d1d4854eabcf4e1226521bbac0ed19260091ac016bc66a17be50df890e09faa7ec82e34a92cecc32d5f5b2e2462dfd547624196e25e25a9a9fc0e70459c985b91f637d3656c16c9224aa64a491cd8d7776a9ea645511fdb7af7c82820e45bb9e01afcf0d7ebee5b40ae2a7a499041160526c988dac88054962cc74232c28441cfc91bc1a82d457716f487dfe854fe028521a0db91343e40cedc32a11c108333d245340c02a70118953f559c63066a6d374123fb46062792dc602fa04101a543c0e36343e9f7f0736053904c7f3d63cea0fd2d812b71861c5c154c5788991377f615382fbc05113310fee5f418b50eafe05f267896fd4cba3a2d745a2bb4b413c68a98ccbd34e51d350513d0cb19ba06443c442eecd5cafad53f6d2a37a21cb48913bb50feb50167ab1e4ba630200031f5d1df9887703d6548b94e9d86a9684994bbdd35141b04f10b8793cc23f383ccc87e2d2785db1fe8d3a9a28babec48ff1e26c737add23d33f575ed0e11b539a7bfe7c9918acc55a9a3d80121dde15bc356de7362c7e5143c4f7f79feb02cf34fd4e1060330000000000000001eca413005d9de9561070c7ae6f3e6b20d5ce928b19a5b476fdb499725c64f6be18c8e2cc7fd516713aab398631640fb898580a492d9c1f0ad6ef18581cd21a74466fea19aefb65d4dd7da1ec0546b7b8a163e4132680f5731161b8d9c1152bfc10e5fdf6fd4064a3b56e4819da2325786f98ff80c4838fe2fa8e48bd600aa429cf59592d80dc78cf5102f001e7054339f6fa00c75fd0e7d02d8f8b351e3103fcb0a39a978cc257c9dcd6b6a8af82a04df71dc1cadc74b7f78df87aeb5a11b05065f444ae51629feb06cdd0cccff25b117fb60b56dab63397d7fa9511b8332585362997d3d5e349e95d51f3650cea33b7c6b9cbb7543c79fee1d69d5cabd52b74728c64dc5fb660d52dc443dc6451082cea91bf2ef4407fe780a8d91ee86cb45c94f37b0785c255ab9999430ebf1c7073e0729026f8b492f822346b028bc83395c98ce367f9c96ecc386c354a9ab1cfa425813de70f32d854d83a744c57ba8133c4f6af0f5e1a7c6bc20b2765c40635f0bb073e68f32d234818c5c46d54930d79530ae7752491c7e8d7e530f8b75ce6cec9bf0757e2f2f24a8cad3dd346cdaaf24d759d70aacae7f06fb66d48c8ad4c54418d160a6854bb62e28017e8498648b4fc17d77f6ef314d58d14d3375e830b6371865c770c55838521b7499e063f4c1f4003f2da808888ce14070b6869da749bb06530b6494b69dc0750a56014d4c3d7f1554d44c8749d8414f8e826747078d4709c69f573dbd30a3c5a2634ca7990e175e22b7a83209359a96a60f612035b94c1b0758595ee39d21b21b4ce1d6318eab37d50cdc1ab9c91e27300ea039360469ec8e1c352cad25da9f88aceebf5fd24a3b27d0068707df7bf9cef87e739e665272386b4b8893f7362e42ec026cb6fee730edd3cfd30020c97d972a984f77c4d571927d067e987c2d4040592c070e7512cfedef9c8bf31caff8b3167dda3ab5d4d5ba62317fa31acf1814b46b7bf69ca844fe31eb1a658a2661f471b5549848f505b7720e09655c6f6a6f7f0439a1dcf70e4f3f557d6e3666311776fe2773bbc191b3691d92a58ae41c9b15be29ccfbcc4c4e4fe0bd9f03d26e7c2770d23261375593105dd962094e82cf13450d30844192b6fb71a752ab76ba573ad57abb260c9ef039d18f45a65e4e6d94de5a48e44c4cfe9a0002626f5dfe33afd386a50560c78d35e836e3b7318e30da390d474fdd1ea6eabe98cf42b5caaffcf21b95ea2506e3a3d3e148685283b8dd00a8417edae4381521c38956fd0097fe3fbf31c741194151d3d4c04063ee8730fc2d313fb898b62b3a4f6e7ccd576be62621a7f5e0d738f8896ff213d101a5bd5547571d8220cd5f8c18f1de5415167fa1703c67c93510ef6483ff5773cd15b7e1ebd15976889fca65ee281c7930f94972efa72692615628da74373f051dae23342ba673e673720252dee86c4b50fae384b0348a60276ab938281fc7fb11c28571298abcf8d83de248fa3c64803797b1e4e2159e914cd654f897854663db80b36857e9a46d9779e7686486b721473f13369a705fcfc6ad651d9d0947f5c8f40eae0b8c03d5f72418aff1178b2c357843fc82d8784c31dcec38c66fc1ab2f4098ade5d8363bbd6e25f4435828822690463770e1946e992ebfe2d260efb65199963f81470e1c6234a4c40d50b2579097fe512142251fe8cc35427886d8abd05af79b32c816bb938c57eb875ab90764d1000ad975e16f4b53daa1fe450da5c5d051f844c658e424945738a13e1fb2459ac9ff9e22704f6caa2e25da13c4f6af074b5921347b936f721eefbf9a309349b35ed90af2528d0b5ff7657d8b3e32834adf35bf165e841124452e6d92f437936abdf1020bde6cf9b799877798e38e3bdb0140c50dfa6f7938b15e14689607a41155f4bab389cb370880f6842add409616bc323363ecf6442dcb6b0f85fc319c480a9a6290a2cc2574fcfb08d2fbf52e8fa947e883950bfe2493aa73e6bf155a89fcedc989a2b7525fb18dac1fcc2b38a828e3a2d6a4144f24bae1248678fd34ddfc4da52aa87ee3e76218d1802a217f5e28845da3b18b6a7f4a864c09cbeac2c96794ab8a072c8a7a5de6717ccb89d90205662938dc399e7c476955a6c64857fa69596b1e22e3643739e6ed77f538ddc2e4324aae970e84c23e249998bbc9777479b4ad60c8a663f09f5a6427bdd791fcaacf76bfe3cac99a1316306c0d2b440dd3390e5dfad6341ebaffc89863b95c2800489c7815f6139af92cdc435501ca4132344d4521d8a4543e20934d28058b8ee02942c1256ae055716cd62a4a7824fce19cb3bba365bccb177f58328795943723d5c28a9d84cef9b721a3b548d0352601c16f51cfbbc32fd9d283f3300bbdad8b62a2885c94684c722cf1b6539fccfb0a43c0197117a40b92448385baf3171e44f2af2eded03c67d0bbb5a8787764b8e04294f2f9403f8b2fc0cf66af162a5780d3860f0f4b99205bdd8a8e879299850ea4daef95869398c657f84c890baf9ddc60be0abb7d30f4a4451eb263bcbd7d3da2673e317ac7c57ac58e564bda3ab49b84a4f750e6687d46776b7be34fac5c8cbede97471462e2fa81eba870b83646d29d8e6a9b5bd66357f5b71d3923611e624efee55c8bcdbc9d5274fd61d70da158a707e1d9594dafe39601c12000cfb86cf8b5eaf8f76d972818e1e941a436f62b6a994caa6dd59e40c320fa945c00d3ca2ea5db8333d2c609b3ee4294bdd21b3f11ce815f9dea9840801713a2e762b260977141fc74e8201d70c466dfe0f8246b2166991b2868a4cb40af97d82334793b9fefe99f91557bb1b4d1b9f77314920fd2bfec31e5bdd5d9e5a71f32523fe237795b39e13e7f1fe76eb0f56f2ddfd99cbcba32b4d2e7a82cee13c5a03e716368102b18e8566c27f51b6d8859f56ef29c177da654aed948a1bb4985cb6e22c58cc5e7d1c1859152a028280a7feb6a78e9647aefc55d46d0c4db2fc5b0749005413d8dbdfd9a86900ca63ab8cabe3bb8bd85d035cf0f610870cfe0ac2a11016d5794eeaf765f8cbb949d41262ef38685152f72bb0deb2454c2e2b024f2ad600c41e802c7ebdc259ce4d56d5fbfe3df4380253d85cb17ee316a9f3ffb1aa1e98e167b29aa4afbebf324ca7a580d396de68cad97c89203bbfa937e98d695f67e9aca0d3fade39039edf319de02cdb14a4c4d448f26bc6fe4237698fa83081f6b001df752c02e7dbba8c601c2a21e604aafc182cc2ccbe94062dadc7b57f29fb70ca190611a801bca7c544622fdf90f75050fe1686b26edfab0e46d08aedcf20567693bf73762c8b3284d5fb8be661ec00b682691c67435ce00d055c834f18e989c6dea9cf6a8ac8d2690076e557335199f72145df0b5bca550afec7cd1d79bf38ddac9742779811893037e93875fb8b3db14f27f7e05a0e3f1bb0da4a87fab3405e74181dc8cdf30d57c43b68d70a826765c843e88af35f820a9176aafb3efd8dca04205872a046cf93cbd9096c1e09d7d8898de1806203bdf238cc62d846cb15f73fc7cc6067a7389b5319bcebf95eaa4c71cb2855f75767a822203112f0a4b23dee9257533933761f78041b5d0703accd8c1c89fbec95658584a5d9d89c221e1001a4a957f99d8e3ccd61a83f21c739ee52d13a73ee83b163cd32218fa412224ccfe87d9eb7f4ae02ac4a45fd0a5842c276d7cba2b9d467678a3be7f13e8818fc8c1eda0636f203b7dad104262fd8bca12dbe7f4af9ecc5f448cf71cf361dfa974912ba23ef754d33bd894ede9b721cc82d04f84d44eb6fe2ef45b0e6be77f2bca1d50c03b1e71ef359c151c7595fc1a5be422ab57b4d7fc671dc5a83cbc64a259ac0fcec5939f30f98db9272757f03b1a52f4979161ee9712911132dcaec9dc06b01ba299eaedba5627dd3e13f6ae270d4b6c8afa0dd58588ec4e4b94567d8566a877b215d68911b2fdc4ec662bff2a3110631aa003fd7e87ee8bd543f6efe706649a282beb97c18d5c16c69d23d08e01e99a5e2ba2b08712222ccbd0f39fbe177ece56f0baab123acf0a3e0bdca2974b80ed3669608f9c2648b014ce2b22a442a1c8f00cd5777f7013d3f234bec87f56b557e38756177492edb5ed6aa65b22ffa9dc86deae0d9a86a3453468de9e235499cb42186435fb61dce99d5db4533065c9784e2ec8c4cc95d92bdeb56c63426a6287047e2e112ba99660871f76595c77d6e3f1c3881a4d312257263dd3690374ec562e389ba99dbde719c0f5853c646cc621421f4d21a7daae208185d31977626cbcb6428f2dfb8aa27afa3321ba4add6525869ba874053f436eed7e3afa08b4f4897f84b768fb4763d726401608e5b67640e1512d14760ce92113368f2a9fe68c3990424e56c911f3d7e02a04ffe0a63facc91d64f080f89fe3512c86b863af4ad52abf4b413580dfa0ef640edea7c74c255b3fa2f00045f20ae19d1e85eaf24c358be61c301c246e71f638ea337cf75039d15efdeebd86c27c2832d11dfa1e214e21fb252a3ff63526dda3a0b93769ddec45e612d3eacd14c781fd2392d238c1e2a69814961a8453691c3e4b1a58154430aa2a0d9312f6cb435eb83f87532ea009bcfcc27601f355e5d201f82b8a9fdc5df07fc4c395408d13b39dad1a100d50ed232f27c63582993c7c0313358364ade6c2526b0bac726adeb5480fd80fa720b7cf91db6d45fde7e1b28a01e8a66962edceafdc414b997e634568145248b48fe114e92e4ea79c5da850975c5f46412bdd356033dd4f8c05bedf3db012fc3044e99fd68b77391eaff9e603d56db5e8d8d436f5c80dae926b195d9e49d38e02ee07105a3946b63830f4a6a1ad62d261ad0dd7560d261400d310f180594f0f7cc8db3f59ac27ff77624ff478adaeadf3d7aaea0b4d34b66879678c0df8aa7a16d5796d87d1c005748c094662ca4198732259dc28c4d894f02dbeb66cc56d63bd4fd66aced2ede5ecd6493a7e88c79775858e8540e85fc61bd00c432413753fc9a10c92ec80dbe34bdb90a53a66faddc43f1f76abbb5d68cb77537765c61034b86b68e03f6f86a3fb1d94e2803755818a81fbea72d17e89c5838c07c6c972824166c2973634f6bd099bab0c528fb0c46e80d15713682d3d21ce10aa8e2642178a543bc0e5535c52e632e6cf6fa08cec887cef36ff76b83dc81605ac4f3a27f020d59a03af64966e710369a89853d1b8cb99d4ff1afc26bb512dcaf59e0d3adffacf44fcb77c31baec44c36add7779024e3c6be4e1706098f43d56d8d083430e47dd592b5ce1fef3e0b440dcfbb739c04b62064e8e721d07ac22dbf26d3e9ffc332536d4c68b8ff3176cce95f915a9cb5c00f98a1b7c694e9a3ce5e33248731eb3231c6c3326a99cd6b296d4152904ca902b6d6237b300b679693b434b23765ce5003757a4781a06ea44962c00c1444615ad700487b66d705fb11dc24f240e5df8fbdc838ded790e80919bfc03d540dd0b63927acd5be553cd2ba89a565a78a4f71cc805107836f878b9969c64d95bd334b352024395e99c0e2c8663a0310e7f123aa15d25ddbb409d11d59633905d722ecf93e128b75e03a1387525194ce99e13b57f66c5aa2832b90f8137d985179168d7235111379b30e5436e1b6c46ee56b1168c5448e9590d89d46f5d81d9b834c3740d219831cbfd4755e37b8347a0773fdf1ce0ae1df17f9564d9e943f7b99a14d71f3e6290e48e32ec5c1d781235c2bcd68ec66d0beafeb80bd388dafc8a6385785e90f8fea99d9f4e076e2d4bbfae4a6921753af693773ed934c3611f53eca0491b279cc9f15e9a54315779ed5c4d5a2d0eb9e3b8abe8d18bdb92ffc5738ce88a06beda84696c15edb6b556028446ce2e029bdd139aa9afc6302000343b1663f248dc1d7bc9b9fe1d368d6bc11115d924dc225ff85833f2c51319a858a7c696e3b2b5cf4cb12b8d20653dbdda13d0b99578a24f3b35490452cd1836b4bf0b42d729bb22a651599ac321a2e2a41e6f03b16454a21453f83da86cf0267fd4e10603300000000000000013251fd01d1c901ee60aab31431ee76531fb3dd63bafa7f434559a1f86593e27cda53f3c05424c6d34be460f33226bae6ad10de5fbe33e488f914a5600b756aaab8d61981e480b5f6921416be364a3901e11512c40b2639dc53df4fcf851bc7ee6ad8a92c1159ac349be05a27d820247e262a0ed407d2d21a0323da2bfd16667bd810e0744de41dde43c7ba09d0c0c099ceb74764b79f999e12a8426fdbfd861d7e4378021d17a56383ae2b07b4a480208050cc51d2195979fe25ede6634fed3547fc8202deaea8abd8ea026a88f81f51f62d31f52bc28ca7604b94f4f6503b802f57d0656c46127daa76e838a2537776b9fae5575fcedd9a669b305c1b0e224327bf6a1a0a3b9e51c0ccce51efab89dcd87884f051e597df49056910fabc6b5439966952da6f165847d75a937dbb22985c4a4efb4570df1aaa8a71c9c123d195f0e2694283e016b747a3256164879ba3e3f2f537fc83d4ab5e2177b52865cac7811c0e8002a673e89cd3d4ea319ac403c88fba0c473c0583cca40635de46d3ccd95a876bb0211a94413539f8e9fbad7d900eeb72d45d53efcec55fba624149c1c681cd115f5954ebb4d623cf97be7a7f2e5189a5d53baba16e22bed742f9483b5bf2888fbee5570d6507434e91efcc4133c06d2b45ad97f9153bc9f15fcf4be31e2746388bb29abca0036be44bf55cbf12ace37e4ddfb97b4ec01027c18c3db4fb69aee2e8108e8bb5c4db7d8e22e1f7ec4b1426f15ef5eece2194409fe6e8e305e8251e2ae0ea54c4683a6998e64be48140a03de42b08c77ddb2321b100532317d9a3f8f8670921c6f0237183b7b0b20287f23052996a771583120d5d7a97671cbd489a0348ad36d6b604c5778e946926bd66b651aa2256768c8243ceaa5c814c4c71df63300f00f901dbeb94c68e8f8434f774a756f27f4c7ba1aada8eaee8af48e7e858b2bbe613f1a765445637b21849ccd61b7bd0edfaccbaea6a15f3f35d722ba931ba41f7865e7268fda53c302e08964988d051daf9ac5092db40592266a253a0697f877b6693c7f1afa5aa276a83834c7d5ce35b70b528a34fd61477159d648222218f68f3ed0291157b8ab4a1df945c955a5509287d630877b1bba9a045ba5f7d40b33cf3684655510ebf7adf9813cb2bbfc6377f4ea52d9ef09dae32b5e745aa7b776292c0613878d5a04d49f871cc7f9df3178becdf195e427be6249299f6ba874cf39d0270f8bcccc5a0ae407b675ba897d2bb9ea654ee02a2bc591a6380ca85b5f5955196ca8dc57ee78871b5f1fc2f3146b928205e4e2a6fe7212c77ad3adf876296167d807263765a6eef112ef290aa433d1d38c289d1856cffb67b90bd0d3a95d7c7f19cef352500daf2aab694f636f09aa063752f3280ceb58c8ea3238d19c45746dd4b44e38e75183d1442d70552b3e9b3e1a381ca57352d57b156e7aa09fe12061647f8efac92e5c587c5c3ea546dc5ebcb8fea52e62065d6a7b9ab31645a3746be8a3e8287eddec3fcf7b713fdcff243d199034ca35e8d9d8423d3a952b5d5707de1e166764a4708aaf0eb452f79435a01bdb9d3b1e76cbe7f8e49a02fbd287b276a863dfbb29114396fc6074434c7530fb1f3abc76518af4ef2b78982e0b308cd1888a2ac4d3962c4d298ad276a158c415e647a8222f501da82f60cf31fd0f9f2ad83bba0e43f7d09bf19ad655fb0dc5ad454cf295dadb20b7346c1bae02f2e05be4321966dae36830a730deb9bbbd25c80ed3f9a922a53b910179205b06466b00ee7e54c2d94802f64adb2dcf0d6d6e59b973a891842677b9fdca70f725562dc2fd7c543e212e78ea52f9dea384f14a3deb155ee408196f4d34980084753627dcf02b0fc6a87ab772cf4d91212ca8912d3258d5a41d7befa4ec9073c09f56c08d4a14cf4817b3ae18f2e235ed7744b29950765873df77ac6c283ec318c226b19566759f68979b6a8161ab937ce7bc6d6628b7ce7ffaffb2dcb4e94dd4e05371547f8a1a3892909ffa5f991710748301ff5a89d845ab11ab7c131ac19252d4cbf3d73f3d14a42f1f9e02f2476c51a214ef304306aab8d33500cdc27f4dcc8b6986a0d40d420409e3476156b7b2c713d2496a554c2c906970f17b0965971eace55176544c4441e2a04e237b7a95dae389033150c8c7549c0c17dbbd4c78af6c47311dad080dfde414f6fe1a3ddd5ebe8f747f140cfe5e57cce474b4bcabf73f9094a1670f84cb9f1198e15407dfe2090134c3d3459a2ac6f3616ddf4ab126e810cf4b0947eab9c0985263f76a5156de6477f8dd26061462933b7bff8b28e835cc9c4614888bcf6a6921e9a57203cc53313dab9c2a5804334915d48f588c7d6637aab73810016650ccf70898367361d11ae8918dce7d880e34242e5ccfc15c5e0c6335621d8014332a0d118409fedfa6f8c17fe7289e62cfc5a32bcf3c2a95a05187faf76a215ddc7cdae85eab332551db896950d38a96e9400dca16d0cf9182664b59f6db62924cccede98d79b63c73edb73c46884fc336fff21ba10bcc9f6f8d8213c6fa60e8abff165c71140459ba56533a0da89b9a92d737c90fe4ebd66aede92ed9e48d62076c0f68802ec96ace2f3707319a037056d44427dc880480ec8f688f189cfaea3b4916fc8cf5932fada94ff7232eba9e997fcaac39fd9555db96635d7a67d917689795a72ae94de0ef3a5de01c8fe9d7e880327f6ec0163bfb0e6e12f9b6c5370bd3e77c71fd3462436444b93cc1c69bea512b2a401d7ceaf8fcee829d77b5be78913479078633a44f8e9c4a2be908f327339f0871cb9e3f053218e7a25d4420ff084a29fa8b18c9ace45e881eb2f645cb9bc4a0a45974f09b5977888a2b3b04c41156de32e4728da4bc54b5f833109e226a205f9bde6426115b274db6f0d108c1a982693835f1f952278837aabd08bb7e8768f9a2299f29fb60facc7e80ea50413f7016f1cadd816d00cd8af0042cd74e3351633667d3b2af640e429234fa73a4a49f091dbaa418a7a599474b136e5deca51f585a52cbec3294fad3fbc45b33b70727f2228d2c021c7b06609d8a960766f81b0c68c37dc87c994781c60db2ac4875c02d906ebb5d9128192f7336d38f138bcba963d1414f8c757d84e8021b0df501e745602f4c53a6d11e04f0edcc8696c62ee534243cf178018e715296283ac7c6345b4347e70d6d18dd38cfa7fcd96fdb9363f48b0647ff49d31f5c5335e19ab8e4ae56659321c557901110b9fcf97344e9b52d60977323057051f37f14b2f2f7ed993e8c3b96575537b61cf27aef656719b60f632556464f3d4ec324a5d76cf6aa4497eee761020ce5e10dc5a341aaa63580a0d8ec6e48efd743f82280d5c23d2353eb3b06ad1b91c8d6880a22eb84946613dc44246403b9921f11e4d6684f66cd8de8e421490591bc2b193e998bf0f1a024736e08f65f0bdd04b9c2858db6286b5886ebf63e6d92fa9378d2655dca31b036b90026234fbeff9a61a804d24f7fd9a6c2bedb67cc238a4390e42a1efff4cf61da4d6a9af26ff6b1a3739da2d8ef59f953e5d754e481666dab68501220cf2a07582e3ebf1eb4ff7344b4f755af5b22a193786c135b3d70b24e2aed5b9027c6f70b71fbc3e94df1d17ed4aee4259f1e30674c8457f801568f71d8aeb3cbc1912dfde8393b349ad9c90cdf6a250b26171305457e019c291d00d85713863c378cb1076fc43bac35cc32def88a06daebd5d4788938df723d03b35480e4978433e87e177f87f9573011985b7bb4fd3a44275cfdb009baf1783ffa336fdea712ae21b9b1066e9df41a66f15a58148e479b15fcbc5934fa41764087a6b837da014c109f4f459a2facda78b318419d3054022d605b38f21dc9eaf70e2595e3d98b12dcd233723df476bf225e69a7fa3d3dcaf9700187f07f106626d55cedc83baf4d22225851a97cba5566880cb172ef9854d96f95b314c0b485e8c6f8f22a85729b1c84cb1c32e961c0894d0a45ff13e1bb997c0bb7e20d4c3e4453a368f301d46f65778955e88f00850b0977f834e1c56841e14037c1cccb6619807854b368ca12ec4826c82f7a1563ff765eec420e8502148a05419790daa55b985b3396053a282024793bcf409359433a1ff8f9cf9648d26916faa9ca0adbd88f0fa6378e8f1f926b5c9e5f0cc8aaf7f362803313cebc89e21ea7f9410384154cdacb1eea32aa6ed6425bf607f6788eafb507f4878bfd4b0b778f4062de110ba256596cf3eccbca47787734293703af1eb0ce831c6094cab79e15e108cd38a4695d5a7946c18007539c1ec3ebc326a8ed0963d3836cb23692848d654bb7901391b251f5a32bf00ad267578dd99e44567fa134e9562ec9b63cf79a1ffe88a129c61e6e7450ba76408be53a7a866d60186321c71789bcc9bd0f9e031abc80269f036768b4eaef61d931d692d187a2e9cacf6c5082c6aacd0352e76fb2620166eb72ec3ee396b5493beefaf12a45c770eae34b8bacdea144c371b09006cd5b2e7079e45fd47c53e3bfab172aed6fa69d3139a53a5b95e9aeb3bada5d4a0015d9b4d5ab200fc1575543fd418540459e85c36ba0604f7924bef43581052eac1f62a32816b48a51c2dcf6252c8869f3b75b37493f67fcf929133cae0e1c3cf8ac6b3d35ae4a0859fa6663773cdf5d5951a1696ddfde65fb31457dd58fed780f6b8c304fc3e6bcdfe80938a78c067a4af31d7d8796644d3a1190ebfffa6fe3e74c7b4c97bd0b1d8243698399aab9165135aa8638ab3338496fee59ab832a3ce01b511d91c1e1add4ee471123e9dcd7e64f4692a6fc4c76f4cf84fca51f102e340031173a3133c8d29c0520076e12f7bbd5a4a01a73d83a68296ca13bc0b7a7dd412e994314a9fe8877c4bce4b5fab5db80efdd03d2ef5c21c1e45e605b6e744f9aa5847ac6a94931ffe9b7b2de53686ea0262d3a0ac712cf34724dae8f5f569398f762bb399fc0d576f224b55d3b82fa78333f9704f00eafa8f38238887ebc67cea981a6091fe311c6a753a24de0eea7ddec1a74b267a6fed358adbd59ee4db45cf41d72f0b5004fa945c9303e40e1003bf40eae7646e453e5671a60f12aa489247ff12da8bc8abbdcc2eec1628273e3443b688e018c68697a81fd9e793dc0eb792f161cc8452caf7d568778d0b218eef619b270c5c3ee16ae293648e3781816916519f8d8cdffb7a899f337e6677ed8f6dc23dc24e84c9e750584599005f3dc7b53a3286707e67530726af4e0f075c47824b0d38f571c658ee4ec690b73e88994312a19563cde990f727838db9fe1f3bbbff15ccad169796d5b51815bbdde4f2a405222b1e79d907f178557a3979a5b8893055b14e921316bb29082b353a8610ccd4b406ba5055ff22518d55ef8a8c5f2f07829ef84009fe505ca0e721948d3d235999564d16a597b867df1605dd68340e535f87dfe8106b4f4b26bfd353149dac2ecb1c7867e07ea661a639bf976893f32a4902ba961e4b35630fe273a156300df089fdabcb904b99b9bc6fdd203e00fe5448c3aa75d4ba27a558e64c4c55c95522f81e91ea60495fdd3f66551fdfc9b6807e04e92b37265b7c5815604d993845b87bf0c9178b008aef9b2977e1f08569041c04ecb2ec36f6c8b00735ed2775fe9b7f97b5371268b4e1211e7d8fbb5cf592069cc0b5678b0708098403b07962508c17800458a9bc9947a9bc521c3e915a95a892f4fbba0524c37ef11a633b4fab270aa52615a1727fc819c01df2c52095f11fdae7beddc219943793b45c5e4ea5c585f20416daf6322c06c441532071b4229dba3f0967a94d0ecd2204c240c4206a9d838ee1c0250d7b0b15c537e77f1ab4de5233269ba0ddf2c5b8cb94ae7397fe4ca685927fbe5ed92c630200039ba8729f388d25a913319b920182c147150d2043c084819a4611e3bd4f8ed3bd3719def2fb0fa0572ca31ce13c45796b46b25592a3b0f24bcb304b3061161d378cdbe6c5db524063d8d1b965db5470776ac305d5c6bdc324abf49b1617ec355ffd4e106033000000000000000138fa5800a9aa2821029d76bb8807e76904f4e5e9b1036fe178379816d3f81861211cb68334ba2f650f95740f72ae75ea1d10ba9a2b5b97fc2734846eb109322142f9583b4721e5ce31b724aa7e30b6f44b9c56084302b3f36889901f2fe6164d4644b603f96b919fea4915e6f7e879e9a3e3535e9db76486633173a436b670d347f4bf63234a5189fe620c43722383f1279de3c3fa97393e0f4a5d7be1361ae4b93fcd371a7b48d8594abc508aed5e450568e01e4407d19270fdca60b7442946a4d6332ac3b10f27c0a0531226292deb2dbb7386d02b05d51dc59854071b5ed1e22253ffae87c04966c1f4e00ae1c9d244f243218c9fc1cbdb8f4f1ba847edf0708ec908a56e293eb4d632848be74b94ec7124cc63fa9439bc88a625928971ec5eba80bb865fddd85c94dcc132f7cc837cd6f28cfc3208171cc74ff53f4c3dd9d10324646fb609fa3ec15cdefe2384f11d44e2a533756bbc6d5eced5e74476486290fd45258f9b0e849bb43e2dff83e028a74f5e99794ae871d7f3d52e8a462346e7a74c581759970917bdef1ffcbfb71579aa0369defd4b040ee927bc09f26b01b2d7424244c75b46dee829ed87e9f2882776f1df41541290d4d64115e8491369393bb474abd03ab12a962f1f5a45e080a957f5da8a53ad476f526012d295e099a0b3614a15f144a893aac99577459f4f7e1b5530c0c72521fcd0df2b3f4d82a86ba41c9a42e1efbf775d52eec117653c7c39c3b108094255e13fc38c5167b056cf603826f5d567b581746e54993343ec4ad636c20ffa22fa5ef887ce2dcb5f2a7179bcf0a6543adfc15c2d37403d62b6d7b53684e035173b1a85bfae77721d271779475c8dd17ec8707ba0562e9799db1c7fa5c51caea0b9116ca5fa98877963c9f04ddb5ff1e00c06d973efd202320e7aecaa44ec2705cd160d0f70172807840f18181cfcd918973771e38357e7e3411714275baec57a1e211fb3b5c148c9909f69f628bcb61ff775a064e6dbbf9f3647ad7623eab0bb6814063135c35665c73ab119fe211e74ff5a354124d1a8287116d6b6010c4a6efca963c17c750af3d54ad5945aaecc6c53bc154790b43d0b2fa01c5923991f3e249832821977180d0522807a77d4d119677fcd09b42fc0d9df18c9f8e0572b4bbdefcea14e75e90010ee4138bcba28bb936543e812c4e20db2f525df7bb94ee92534a8ca67824051a115728229c409424c52298c151dd76fedd9095a1df7a4ca6b671aaacc664a720812cbc094ace3a575e0acb7a7bfc698a4055b4dba82998598789e3c1fcfa7feb1b6c8a14c42ba69b37a35d28186755505fa6acc9dca53275cdcf45908c5a9f998533ad368e3501e1eb62e2b66f9b6109c20e25c1514fc856df75fb72013cec318dbef7592774ee1433dec86616a4dd3103c147e49be4907211cbbe8b2ab59153b25e05fae35f749a10041761c280c68b2de31383509a46dc7362aeb550ea28981ecb323e3e75ff6a7167cd73b3bd4f797b11170f3c3befa705a7a245c83b9838086d3b80862870c6196923c5a0320eda60ea64c08f83e171317e97bb0a6cca9f0899e1cd97fab050f8a9a8709153cf8c5fab7b755e6e9840bba029cef1ae970047deb3156815b88ac15524b0d7466592756ceb4fd7d87ce612837540ab3b226d44dfe66a2651a72374b9d4096c6ba4c9cd5d6c5398f5497ad862523780f788f0b55da1400c8e038ad613753e3f1486a251156f1acce6a19844dcc021b4bc797c4b8de59bab5b4ce56dbc10a3098219884bc569dc105c60da4a50e064182f009623f8c13959d537f8e506066f16c0372f6e7a64ffcbf670ceba653f041a304085258369c082a8c94d17cb0be6e8abdc65a4e7e48ac17376a58a7598bb45e150616d5b7099eb4c5fc6f3e9074468c9425916f3333144d5b8faf6bcadd6837be952f938673fbd29f9be2ebf8e21ec379f4ea83e47549be53a3a51b8b4ae957072826d4e82a16a9ff20dd8c5e92f4b59a4706317aa13daffeefa36a87b9ace374067a03b4eae42c1a0c39be684ae196dfb3256c87962adc683182f1f6a2ddfd9d07634956719e87bb86294660d48a5a8bdf28871a0d4e8f65f5c38f404a40a7d09a6122d915863bd87fd5e1417557fa953c5628c53df63bf8d1b5fce04be5190823f17ebcac733f32b7513ace05b9a7dda60be86c079c25df7f5459e304a599b62952af80780b1330ee92b28fe690a16cb1ebd6b3dbc402d033a88baa353d42d6316ad8a439ce72fa57d02ec91baee848956ed54b416c1b7d16c681d1ac6daf1e0d3e3b33ab83ffc5201f063542461be7af7a4d3f8cde347c8e51252ed4e4d712959ea2c23813b6ab269f9adfd6918c28469149891943c661587dfe710fb2cc50b261be87d1b1ed05bdac8e830f683a60bb1d5ab1bda3e59ef0b3943a665575a5a428e0ecb9ec902ae04537602290db479e736cf8257fb73fcc02dab3f63847619ea1a76f1551ebd381bebf17f6985553d2a8bcc21f384a47d2355babe622ace2e70bb91561eb0002e0b94c9b31137792fbf8dc002465916ef8edfeb958bf927ddaf7f5703556f6eb547cfc3d99038a24430a609a9e443c1ffe581c00514e99cea3257ec4324e551d8f83af5b67bbd6e61ac3e3175b6117048088fc916f80ca948500f77707c4bc54c9799c68b72c381a3b14e477234ad787f4b23e1edc43d139fab5a5f8124a3fbe727ec68e4b0e8fa7e877b5712cd508773babae976dafc01ea98183a3a801ae51856870b4acfa0a20bb6ac10f207c39d9df40bae368533bdb997406b95fd1fed9c6f85bd8430c32453383a3b6b62036e2ad4ed910c941a783b612a8a77f7a4db0c3f75dc88884d06966710f56d71106d7d4124e24a8b5f42dd2a0502554729be7dd41ca38fc8df69c032d4cd4feec93d09d00a87a0014880c26d1f6418e070c7c1f25165f253792819a3b1634423d177474dc660a09188f13c8ae10ed5084100ba8030bbf47b47d9e262797138878372aaa20b6d866c12618e2065642cbb6ae2eae49e41f1896c89556250f8d7d9571c7dca62768cf59c3bfe82ab1a107ff8d619bf17b8833a61b51bfde48b85e705b1fea0c151230caff40f8a9c399086c1afd95ec33db92502e3bb3c7ac5d0396b545344ae9aa6416816e65f14bc2d11a07a0b1c8273fab2fa98238acf967d5f7886a11b49dd64e1efef50c5cceb15914ada42af346fca2f8018736a847d7043a6d52134b4090780cef79b20ef12f27de40c81b04c66a699160155f18317101f39a74b329fdf3ef4d274d1f3a5439fa5719eda1af6d319e460dd2f5f33442abf4c91b0ed53285883269438850f8281ffef5954248f9ed1b513c05ac48c1921ff2940a52408468141d91f87a647959f1975ab8ae5c54e0be59a2fe8d0261dbeaa2e4aa8f8bc1e46ed2428eb3775bc5f12edff7861316ca3abb5f3dd60921c0e51d7dc2e2d719cd4cbe6f64b83722fdbe0ec396a6107f810f77fc67d85d0d3ae5e01deded7927edcc00c625614a8039ac13a34139177c17146690ef7cf9b594f34b3fbee93bacd2b077a3f6ea045786b0ddd882b8c0bddc96cafaf6a82f2e96f4dae0d0f73488edfa8e3843c4310fc86db52476ac26c8505165bb28df08b2c87e39c74cd2df1c55aa8e70ce9008934f0dc54edcf0e532c17038cfce8c26921f0382d2c324aedaecb452c030ef875e768aeebeb6c7ff865201a065b275216996564a1b272d430ad4009d793173c485dfc013d78eba6a111bc2ebf972371a088d95b04f791e19f5bc55e684ad7d9796bd1f24dc00ba35e0e8379a999ddf682e14484ad513eb8d8d839307e2b764677ff7b628472e7253889d8e69317566ebba94231ee46874b20abcaf25a98b318e5699dcf934c8971ef6fbd427bfc4c1b66cc2983155eded779433ca233f4dcdd34d2524c6c3308f561560d6368ca58883b939f775eb06f8b6e2f27ec12eece4e0447f37f7091ff2254ab6a19a2ba57a694e317eb8e84a6b81e0a08533fd946b51187d2b22bd81a33891e1288b41043a41d6e59b0668ff669c8240282382c132e952faaa39e5482434a3acd2c04142d9cbb91436af617f4d3e5bcc7e77257545a0d790fd041fbd4feb3fa55f7e63e5d06e1dd95e5a665c0f6ec3dbe051c347864273f4f9f1916f927dff18dfedb4fdb0a503b0d8979affc6929ef95d2f589cb2c95d71cd9bbf18c942b9cdc761b22028fdbb0eceff131c5b142696631079bc478c14ed97373c8f8dc7562153349288d52e6355b41a3ce38ce66eb04089fd0142141827a78631b3785f656773d319dd12aa4c422594be7c282e65803c734dc4d6458919fac94b3c658a465b8af63e4f85635e5ef90ef720d230fb3d639845353cad740efef67aeb0ea8eb8b48fee33ad307651ba8c0724998ff791700586d055c7fad8e525a9f3f54305f0ea0ac0afb39b392eac48e2687bd7638127fbca3ebd532dd88ab15674f0263fcf42010a660954c187e9dabd55ed86ba1089a8b430b59406b13bf47179180842845accaa0c021e5e748b8995379e98bea88b85770a9163cd5dc319be3c1d28cdae53ac0364f8d9bf8665e8a2378d2ad1823417fa2522f08135c562d275a06afb6111e733560026781f06efff3d8f2fcd1c95bdf63964dcec607a0f55972d65713d0efb3423a050e56a9d3e2881119405d599bdfc951ed409147245c59476b31e1454c676a7dd79ff7f740c719b418df965bd9336c3fdd39e84f32843b89a416a2ec2e25261690ebb74370cda6e6acc7bde72549b0e68c546cbdce72829b7fa421da283e06de2b29b30d24489fd3cca6d925a225ef10cb1ee8409354d678bc70a87903f8a21120394f73d8b28c2b42fd75b48f3cc89f454b106e225d7eaa6127a8ed1948de4f467afe63708de7c6b52241ab24da3bf3c9dc48fd6d5c7f67ece6583234368e8428cea794ceed48eddd83d3f7d3ec2e47996a3c01e8e8617f10456b4ad4cf055e140db74cb675eabbb2f8557c23633c889739cc5df2796298a82dfa71b99ca57b97225acb10bf76d67a03209bb35766ab64e2e554528c4a9adedea0f83a24656be5f22e3c472c4e22f890a323e0cc88d3a30269b01e93f778c3816fcef8c614398e90c93b0b17ed04b8ed9ed23e8bb5eb4628a7219176e6a527c4537ee103d353748f5871c737aed34e84352ac80e577aeff1f9fb7ec25a0847f7801e7eb7e6e8130e089ddfec2f8b0f80782cfa66ffb292aa974d9346d85a04124f55016b0fdec6545b65f299fd216b9030b1083421895522a150ad97dccf5e52e780945bd88d60a85ae290d4393d06c3a57a2b44ee882a7f62457a9adafe1b1b60edeeba7f39ea7e37996fb0f71f29138d347c92508a7ed44323df620de985e7d7327fb0410f798f46250262ec9dc3d2012153d9ca022815895fcc38484436737f7c9c5acc4cc27a00cf060daa0a57b2498e8766d882636fc1f641a0e6587e0a67c57f153fde09f8d277e6dfa8e7f5b80eb0b1d04df6b38f3254975fa2d60715b23ad3524287b5bd2c3a69d00155f831f7a98862bf1fa3124ca883f23ee689d1df17a5c6b168a28b9de93581642355b9b1ad93c1e4b39dfe0711c56ecb77ea080bee112d830540a1d7502927f6b3a00b89b0210ac86540e8c102cf9bba8ec88d5e215667c1179c72e65875fc136f28b72306090a78f380cb6e70348d43a5d2d85f4cb67ad5fa1e0586af72be188985473ddd685e2e92352d9ab9cf6de22de371562c13eeb8702a6b99221d7e7893ca9c886c3ade1a2e576cb9b55e3fc0b1b7e13fe6c7766c49df4ce8d584f91b37b4eb748fa021f20ffe714312c23aae0bb5512f3dbe68ebab3619c17ba0e1c31b5edb06ab5bb3b67dbe8394380f6302000313f02e174b1d8966844f6c68b2c276b6d2d42a12676baee7ca1181f4edcebff06fa5fbb4192ecde9ce6df21bc0c8d7d633e03e337b54c51f0903f39a3c8ae197414ca7fae17b619f174a729cf96804f0c5eccdb1bb7387344a1c3c1ae08c42d1fd4e10603300000000000000013e6bbd00b3fcd03c5543971fe7d6d44efa71f8651aee0d76d219fc9df315ab30cd9a3ef5eba08aed12ee13df57477ef3ac9105c4acfd4b998abba12d216b7095eee5917ed7a8e2c1cbc879a382c0318e325aa248210791d0d8a146820e2a6ed4567df099eb74f3e30c4de06a923f63acadf9c984d7f6dd00efbd26b1e4393385636176b66900ec51287cf880617fcd834c5c0f3535ab4dd07f1fc900a79ed938df03d78d719e0eebb2e64efadc610f48cee7a67efd6137eabd5205d1e15c0918a2a0775d10c233184bc018c2efd0f0209bc586d50a25e4ac2a6f48551cf1639afcf96da9a38a75eda66cf5ac5705cb0002511291c65ce6d4c91a9afc29c1b7cb68d083eb890530a67ec5a3cefc0b6241b141489ac31590f89d0856ac940e17409d948908aa138af1a240bebdd9899a576bcbe9165f1f70bd81074fb1fe3d908a0e56efb43ef4dc4e362cf6e54f9798b0742607993bb79a670f60c5d5b5686bf8aa3dbd06786922bed7b1b876ec72b4f3d7b7e02ce8e76855d351cbc6ddcadf55fd3daa5b5a5594fb1a1eeace1b4e8fc7dfc97c47d1ddea3fa3ae0c7e6740f6f0ad65a22c873818f90408be12a2d8cd48ad6b8684a5490918bf531dc9e39009e72a4044a6d8e0b0499e0467ca916a4ee23c4c26367070f9d1614780df1ce534cb5d31407e1189c7853ea3b92b8efcd647b6011dd75f255441fb99ac11d0d2c66f4fde61b61f1f080eb65165df6611ccd5a4689dee566834a5a2eeaa00a61bc6c34b7bc131e4146cf7742bdb6e9ee3ddcb0196e2010baa1e21a9759d048b5f991608b330c0dc58eaa555989430ac237f0223ab993f3f60037cd1f691ad7dac9640f1e885cff5badcf7912a5c83816b2b3b75617e48274321112ff47b55a03b49344d42581a72820a6bbbd462d3bdfe7f682cbc094bf1d644cbe9ffdb72a527148f7784cbb5a49445f91bdb7eb78da4e4c2a8bcd3eb147ed8595dca2249052d7f1fc605f7043b10e759682fcd9b64a49948ca2065f3c375de69e110bc0b02874d8d07515be346005e71ae7bec22daa67c0ec08067912cc176a65903f282526a61b3a5396bdec4737c7a9a01048f7734af85d9490605063e8eb9c2cb72f8b8ebebb44b0595126fe59d447e0d2aa12bfded472b176e471f21d10b0108cc48905f5ea325f6dc0b74227ea431cb788811eb123b6e2ab5618ded7e8bb2d4595ef6843ca9a1fa08785aa9d3e5a46f64efad9c3fc0a87e9fdee09ed96feae0f0b318d2815bcb95e5f61bf30257f1c063a9d01eec84796154bdb3ff0d9f39cec67006bbf69b2611fd352afcfff6ac96c6cfecc4392e0e82be9603f27adc50f9f464079c6668166692c3a9bb96f2ec2f8874b2ea7b3b4df303d34d3f92a0ad76962bfb20a01265888a80d40af0c37ef3cfdda0b320d8f2e278c3de2847ab4a778a2778a134d50719a6eb274e53f4bc4826baba31ccfd498663c62324333a337ef9b9bc3c1d32931f3a69817b16ae4eb4e6bcfc812025c5e845c99fed64ba24a302d3e881cbfb9a03882a2e1a10202d673c5a4d169d715bb14f6974a4c55a64a783abd11e0de6bb15f7d90f63994431cfbd9b13318e341fe750585c32c43d18db89b5484800720796d92c9a106f4093d63318c0ce722c976749d3039f4a8555f93ecc221b0ac5bcd13037b0a6d34b4b7418e166c8465f33f89dcc678a22ff67a1e3e80d566a5ce4ab3a1cab12bbecea58b49352d74b11b2688fd326cb09dc9f602bdb70cabfcf99e2f14eaed3cd3b5141b5290b2ff5300fb678f431ee24d3484fe097d4e2db1b14a3be38ef6685334b9adb28f237e4e9b8de49ae60edbebb0c650f9b99ba242f59a53dac327497ff3d8f4dfcc087415aad18f0f903b7e88f67066b360b9b8a56ae3ac2762e9d2bb2da17f5e48a316ee86338be0d4bab4075e9e84485563b587d766e883ae3832231e9a67b7f591e3c8062f8f9cc5ee7b66c5853e50ecc6ec9f5ee4f3822755fb436001a7b4266361363e796557e1f5280b0f8a7519b62233250f4fe7c668a4a5a4b567f06cfb3dab497c9a2794a2306e747aed3eff5c6aee13c1f339c80895b7ff824878910d4b905a6bb1d27a2c933fbf203de1dcffda968c4109c62d9c6bd6330babd4e0ccc3ac90a70bf99aac6df777d70768d8f13f7ef73703ed7a93d780e55d1b3d219f30060df68544fe0bbad3a36b46cdcc3f5b5bdd927d649440a544761c06d5a29a08a5915a833e58f9b6210f7954b477363d11c3c6e530fc6c88bdcb2d66f511ff96658e7a579faff55ca29ffe5650906b710c53a751c811f318acf1db8620d676c4d1c95e162dfd88d32ba89928ec55b2e43772ca29340106cac2797ba93e3c2a0a73fc17a761125997e7f4d0fe6629bcb09a3a8cec45e6e74d9cfe846684fb1656e92bcd1209cafb905493967ac84b3733e4a0523335d6742e26894b8be9f327f7497ef0fa44cf58c4cced064b2f4ed34e0583f3870964b5752aa0d9f3ce78592cfc56482202530ff465368732f7f1fa3d716a17362598bd76dc1dc64dcad320b1f9abde2defb05e1a6dc4283f43c348628cdfe3d77069909fcf74848a196412b461f90f36906eb0b87df564eb8edfe1d847337da95ff429fb824836fcf402d8d969b258a385ef0654b5593ce1308b0f2bdd9ecda20923d811b522e64c28fc795e1c2b87f3b0fe49276b706b834a31b1d216ca4f0adf5acbd0c3ce407288552fa7f0c39f26ab32ab2317208965263d6617ac87b910c5f6f74bba5bdac0556a1dd8d115677f0b82da29ac9683c372d06616a097e354ac29a838932decd3fa19ff6a6c182d00e1d8fabef9f24124286cbd15570aff451fab3b4adfcc58705f4ef9d7615a24709903df188ef5b8265a06442667a6b6ed67ab3edb5c04d65ff5e2292cc6c3f23e3e25d1eadd8794c062062c6377ff15109d8fa233ee9104bb192222e9a8546388a304e0453ea699d944a77a93a8fa05a1e33038c6833dfdddbe1a26d00821e0ad6acd3cb252c648e1f5ef28726eaa4a261a07828583737c965f1f148f8f5e4a9f75ff2042e9c481197a679744d1e189cadf931ecb7a55165c32964541235f171205f95003411e0371e4b2bdf1af62b92dae419e02be72673b172869440138491a5d2772ff30ce2c5f2702cd2d3cd68a4f19f18a790b29c7623cc18a58a11a5835d7d71e025a354485c4999d4b3d48b70daf7f7d9f5f5ac00db0175335375f5279f5483bc4b9ab58c87fce062f34a61ae3970d891f9ee873a8688bdbb01b5e481bfd66534105c449badab2bd5102c2654b2078cce3e08b40d0a54834cd9ac45bb71fdf81e982540a9fd2f643f935cb4bfddba0ab7005663e2c8b180005415b51bde44c282adc38e79696d5a30fc321bb9230f2cb63d70a818e83f7587681521cfada9da95df1b6248cfd366bbf30a5b4a45fb33c9cd6963707bd9cc7868484ddc6f783958da0cd2bd417ea60c81cace2a715bfedc7dc39915b375d351c6dc27c3fe0972a14c3f0821830006efc61ae89b9ac04639f491418ccfd87bde10932b5b1ab4d3ef72cb47d53fe03ed671059d7ca5ea9a856dab67fa0a8e7d2f38819d82ea8e5b42257780f21b303ac26b5e01a77a68a1b9396250770fc71858e2a2105d1f32b43179a2b9de6c60550def39b6123b0414faf661a23b64bca3e421ae99799ec5bd824f812843d067fd167255976ddafdd63e6170859a9f4ab2c34c26ea2f2eb248350207de653bc7d3307553301ea0516aa8cd782dc23094eaa2790229fc706ded613a0451957662945dc5767396a027bae1274732834cce5dbcf4bba0088fec7155dd81ecb14d14fc8b08e1ba419027b937f569e653b48597d3b2682e67e9fc9f41105a0a6b6c9bb55f771ff747a4f3159320cbb37ae10ac8f18340fee83ac1a134bff14147003e3ec86db22916aeb0de7dfcf37f492ff3bab7cbd923682390c597beb3e20cd8173d50fc1a1f36e9c39ad96983a21b7d8c44a8cfd32f87db736df0b2ac38cbef5a3f888d25db819657f3d224aa6da92969133e5780951a9cb46ba730297744d53ca80e04563b5467f9ef87642ba54ab1d426258c30ebaaffb8fbc8f0dbff7f29adac40fe7e950bf43dfed05ef67db46a9483e3edd705fee5cdfffc8bdd818bc60c92c0b3c4d010772fadc5738ccfabed0418bf2449ddc4ad347e7f48491e3f4921a6b42b26e9ad3cb46469c1ae75c5aa9c75824322c835f7b69cd4b2270439f2f628b87399e954901260517bbf9af03f10ee7f963766e1ebaeeaa975b942e77acb6acda06b427ceaef1c02697fcb9b74d97d9326e8953e1c75e300b2f8aa32287ebcf6627631d1aefcc7ef5a0cb751bf8be3a8092e03ac494504e1ae9721da86f070824a8309532dd9aa88adbf54b8385a647e362928982217422c11181c46a4cb03b46cfbe7ca771bc12cf3f8ed9441a491245bd7cb6b57814df1432d030285c5d20ab2756da62d4ea40b71b5e356b88b9814eefd8f952da762c2c1dda56038fbb075fb8e73ef7b8022f52bd733e170601a98c72a6ba7334b1a23613c37ec9e01245b54462c3b05a53cef84a0cb814fafadbbcb49987688b15264898362743455e10415410e44da55dbd6d7b896239699499b840707fca75b3fd4abf882439324142e5cba81b694b5b03afbfcc71366626c971bb86095e8b9211d6c2e4ed4587b93ee4b407e20c585b549ea0a827bc94d53a122087c9c377f1f905b82f537da12d052339826e6dfaa62638777ca29d2b1db5a3ed2a7a017d22b3854a84b3818f529d5b63c634fbe13b15b95da67ebd9fa18f7bb512cc60b4083bbb8f167f7334eea1df32ae239ca542c2afa1e4cfb492485db974ce5b86bffa33f8fca963e1849393b9e789620560f1b63c15181f0c14a48a15d6cf6d805f8881afc43b8a288e3e5023d29876b7395c8a3fa14edfb774b84f1fd63ff1da7adca77baae833f7e453b9e645f7dbb71334b90baae3c29b24bd61c8eb6726fe860d96cfcb7218d4af70fcc18a1a761e05dafcdd4f5d7a9f7650ed5a17c88fa370d9eb75f629cc887740e4e101cb8f6bac35a395a7bd41b17edadf4453be0d6117e996be703d0280a5ded3f15b8639dc23ec85bcf5213620f535bc65e91efc376f13c99572c1c136ea70989ca3861738f081016ed94533b835b2436c88e905d3c09374fd1a1eab7232365f0bbc9ab786574cd0ea30aaebd452bd3204504a2f89e0cac36f41ff387b69ba1a7f1efa0371ff3a180f5caec19e5b3be68efaa244b9656c7370a00b8fb509bf1dc13bfb93f1135b3dd9c148a820ac0b1ce7d3b252f84eebd4a9ec9bbfbada54dbf38495a087ec641fb3d943b5b4a12d2289ef9404cf5abe839044d4b6b018db53feef61f6d62c88483b76d4ce5451b551a6a7902d3bb98b7d00681b6cc2409b40aa9e74cb745cc10f7b3b3e45bb81b007d6523e7436a745390c63aae4543ef30a043d42567a5ec437be0d916edadd6012128ae40d82dc332aad95ed8e84148d1f58640fcfb6a288b3d5b70e7f4a62562274f0474d75880f31da39337f91751d2c9143f54cdc055c7671e1e110a26ee70b35a8ea9fa6e5d9afb72648356220ea4260c5a52f44184cfe1d08d6a31494c83eacfdfb3e334cefd2986f23af97f508c08e6db9bd8df1b382d92aea99e7151a4e0879cf26628cfc28136f72c197eb3f6edf2b64cfa190f218b4cd8663d177c477eb618e2ca1f5daae819acf332e90bc44781aed902e73d41948b162cfcf33a4eb6935f29cc9b10ae3e48c149733f934782edab57e0768d5f23119b8b2e85066fcfbfa533236f9371b67d96e5e2d26d3ea5a87d19d8241077c4658d4d602bd314600bcf1f1b5bb856038a59668a93681f065b65e138ab6b4a630200030245277cfd3145e833f58516581106ec228263333d231e900be455367f2413b91cd4689ab49c3a4ebb2833db5d4f368cd82099d5837b05617ea035a55ebb716e975202ff49fb67c86454f04b2b5a903edcfbed720ff26584b6bdfe35f2b47f37fd4e1060330000000000000001d90fb30082cdc3d8bcc31e71003547e43c0fcb9d4f9ecee9b7ce283b035173f4d8ddc5e8b7577a9a5a8f31cbf37fe2a3ec739963a73d4559ead97fb1a9e8f5b63600d0e052fe0877262b0baffd22f96d1a26a2172665895aa79253839c89cf9753d5cab06d616b38bf143c0555ef907c404d0f43e3715876f61b3889afdf9e3bbfd73917ce3814a538fac53ce7becd9cfdeed8302530d4e66634036237b8d34c41d35e42adee673d499c65792738b50e45baff5f091a3c9e0a23170bbaa7626ef45b4284f1a34a80007decb996daa061c98582eff5067e02a4fafa5ab9695869df669c66a0a7b69c9c63cb218ab4ac4635e10aaa0256e0f0e12459df00237afc406b10784ee264d7c8108630f12948a7367852eb2a3682705b0623ef3bb9f46bf2a7ab202f6d1c4363788abe05fecc5c6ef67b37b4f69c8fdd287d66e46da6212a6c3b3cfaeebd0164505d854c9d892a61c74308a01e3886a483ef70894b3e584c8ec005920ba74642e7f69125d7372824e426c1656cce141e5b074b713eb4126adbd2847e9ef2401c48cce6740b86083d582c4dc3be3468f3cca53b7005b8ae6ae5ceee4a0e2169f7754fd3ede4015626422830ab42e4971b55e04943d3bef97899ea321bb97774054e8327a1296d3f13d46d60540818d9d010f5fb0e04814b7bbd7bb93db28c14d2cdf77576d8c706c7eca784e8c0827e45bea9fbd351e3b3b094c6b017a3df0dd13da6d226e43cc97d8ae00d289d61bcd10a7fbc2c0f6691a4693078a970a6a7db4990eccbeea4ef42b1289d3a0e93a415f9be06bc4fb9b87d78b53b099aa498e17d0530c30bb648a004df430aef4120d0ff4725d2dd57da7c7f85eea4733d51a16b082bedeec2f5824fab8d9ec80de220e7e97da626a85a421924a713c3de220e84ebf215c0685f9ea1f1a2ad9ac6731d0654be6b43e50e7b1de578eff78a27b96f13fd7d7e011783c89106ccd886a695a554e4cdfc62c60fb788071b992650f9c66a3e03dea51366f542a9b9cf3cc778281915e4501f9cca2813fea036d816ce968700d67f3079988f3b0c8994a40d86c81594051b7a08db7c270072b6c355073fb49e40112d97fe99d1781e93bf4872e1950b7d8b47edd12fc767daef314bcf9f8d5c0615ca1d06b29d76c0e7b95b78514b9d0c42415f6be5ec551fa2f88e97a0e4e458b7d52a9df72787a7e66d2122c64389196a1505d7125c03f88c7627271f140f1787f32e6181dce9cde6eba3ead205d46d7847dd592127efa370bab8dfbdb32a2476005957fdd31c92e29bd00a82ecd66896ab5e0d62ce98dd99a3a8e38b9d1cf491f62c5e526b85722bedeb6df7aafed873839bfd4a5e8c1b53f4e82f8bc2e11577827a5900925ec9760e0f148d1c88d2f2e5aadf3da38fd378d43135495900fbec06fcd2a899e2c1cbb88b381c9da19361b3f6a09c3fb7c27e9fa7f51acd533a04c8039fd22af83ab6e1a5f24ecbec7e73ec3754142ca88b9551bbd9ab8aa42282a8d98decc2eafb3b94964667b270fcd5a0d1e60778cdccfd07fcba114738d961cc11a58434ce7711f7c2ee18e480c921ee54d4813ab8ce76f41204dde145518eb797c4277b23757578f6e955739d7aa7ad705e24ec942d615da533b3cbef100ab05c453876a41f53fcb87e6525467800f2e527c264700cb58cb87adcb262f01f2c8a44fa506784e83cec9dd8a7e4f832a7789ca45e23d1d470e8ce8e6bcfc3d64ce96dc6b891864f715c90b821eff6436e19e052fd44ad9254e2b2e0292eee61452345f836a9570504027414b427255a891807fc4e2de1cdcb06ff346f985b5713e59da6c4043ff63b43672dc77319ec4efc67572b7aa77680d068494fffc2386c705dc1d4de592966627364cf3d27ecb1e2c917c801f080c9093a5cb623b308f6436dd7ec6321085ed697096a46fc86f4356ee193e13b3b982968f07a278ce6edee8d71b683d32646ec655f509d6f4e81163231fd414d5b53cfd5be3459a3310f67d3b3e4d7618c3ebe2b19d870a300e702491a80db684932af196002c7a0313f984b326d947e4d342b1c519804a8d1e98c643a9a2511c669a04bfdc8c8bd2abbd4312a4d7eb410948e5ccd491b2b587dab36e18a666dc19d705f4fc4474aba0474415dca00e4ab57ace0d177fd4b423e873bc62022e5cadb41f3f34f757c6b05f8462a493232222974bae1ae0e8f92b2e1292a3629c7ae03c372715efe25522d591a365c1e34c4434cd0e49d9e8079fba0d25c4ebac537489f652e4116bb1ea76dab8295797bf0fcf8362a20860ef0017817c85cf7339971ce8159a19069fb0e2cf10c71d826726bab6307e14c3ada75fb4fced4ad6f251ae5b0ca1262c1274a178f10925475a1bbc9834857bb84200f0bc9b7aeef8131436fe1ac2c2f4f59dc56e01740a172dde912bafa2dff55a7d7df20e4a3d050c4e5bd1dc17b8d35e1b431515fe78b8ef4b63730cfd62f01ccad9c789d5feaff2b976a290defdbb8ca7d9e756670fec75bceaca1fd4e3601a4a73c69eb7d04040c66da1111131fd67ffba3fed9974e640955e0e428315896eea243d29571bfbf912ebdcdd61482650570e1b51d086c167935d34af1d2b649454b9e5c554578164f28a5733cbeda33ab9b0e63d21c445da1ba1c4aa7900f304e70819281f654e753fba245c3f86e3df6184a6632b306a63ca5adf65a91e8a5499b72a31755ebfa33e939f29b4610d970008cd2e988052854f0b63a0ceacb1c78edf8a25ba79628770e0ef27cebacc42f518d39e5560cc16cb7ed5e864e33dec817e6401681ba1348686a99b1a56ba8a9d802a9d0a8b80998d6095e3e810a101aad45a7ba13d46857554645a0405523058d554e8bf0518de32d829a5b711edfc5ddf727a1b31b45a31d1860dbef29522ecbc44cc35b28fac20ec98930e6e00d968654ae73a88d89beee6014ac3287c73676c1e8160b001076dfc9d7fe56ce6a64860d0a40eb5de4d4658dc4c6e9d1beae578cda280a4a8ea8c31a811d0277606a34d0c9c06c504dbc8f4d936419bd11e6a5c0cbcaa4e099bf0162aaa6640f0be793d8436c3ccc1afe0eaaaa3af977a68163e4f43da722bebea72607e4c0b04356690cb6f2d14a079aa13d2c742e82caab8a0e0df28c3e567dca41900518eb665bec775a0948c2c728c7a4d4f943c3b376f054598e9ebb5b4ed2c368457535404cae940e421560fb238507d1b012783845f21f859e70ea7e67a445b21a3f8f2540cb1078717111253bcde10f6f6e2f20831aa58d2ce549fd0bf975fb680890fc45b8113fc47b721958bcb573ef506371ab2ff46a2be143437eec3817f1c2bdb1130d449e8c9618e32ac5f7e7e3827ec23860eb1c0b3e7e1082fe2e6d9aa033ba4940660dd3bc238bb2aae83db0eb525ee9d73cf55c156f6f318231ef8559b3b7a3252d424902a9200d992d81120ba16f869394b1905f28326c7feb0456ded3b7cc08395a951d3792ef0a5cca534d265c758cfde197638aa3dad8f3174e573223e04e0c454375cba007b476587220b1a9d970d271faf5ae30e9629265804ddfa72725f65bcc200454cb9d19dc6c7bf256f78adbc35d505830f35e1ee45ba935f5196fc3319797bf0aa10c6c6510a975263c33315da6fbb2e3bbd7be1b2ccea66f98feeaa1415ff695e4a8513b0c9f0d3b1e53ab18c647c819f14ce91c3c9908a5e084d90c3381fe4aabb2b2b91ed5298977aced4a581b2ebd57f27aee01ea2e2f7df5467fdd785b3f9c1565b5f53592a7e9f52a194cd8da97fb9ea13b89192e045cbc60dc6867e8b9629bf85ab7dd1b74255383890ba267857585223ef8cb7e548a5555e6254bb8b664013f8d55c8383f90fb581f0312f351721c759999bb4ec59e01aa8d31f85a95aaf68f09238529a34402231425dc6b1e750ad5001022d3b985cfc18db423776c943552381c6be6a4cdd27e06d0093855a1069dcb64973cab318939a9d8fb7b789ff7695541bc5971b234de1d67441e0b260c2e0fe9a149282f682b18118119d83902e3f76d25d7f4e258b1334398367912d6dec20dc55b988e16b8d64d9fb50f3d9c923b7c11a0e40b392fd0928a5e7c6a458b03f31151d07bc9fa32bd618fdaeff6bacc741e50a30b3828904ceb6bb36f45ef70850963b1c7c84a9c64adc12f3a7ec898a52909a66778b7a0c385b7ff7d1e4909604ca7e9c42bf7513573c4446b20a21fce73bf8e62d5611c0155b0c882790745e01ef963de994231c2a2b83ea1febc26b4b6b17cc60b93b3602acfaa04d6e32ab78f807aeb217ac9fb086ac66d4192003ea4fad837833375d4738047241d99357864a63b01528494246a98f6fe62038b7519d6c29bcc54ee9a9f7f822d6620fb0a53593d259dbfe3da4fc359e5ea9508ffedf3787190df4b4337c7400cd28140b4425f230f3e103601ff06100e7d554e394d44ee3244b9752ec26838650cd618aa8f90563fc3c1f80c11f7a5ab41e04c3fa857e8b61e3788ed36a9b13220791b2c9d78ff558e6a92e8ac3448d103bf30f7dcc63401eba46988c40a6500ab54e76d45f4cea3c51563c0808a47834694ac2ba9aa3a38066d13e8aba1f102a943e4b78b11436457054ba3aa43ef5220e2cd92731ad0a8628f1a50329947e18ca7068da5995b6644e4704c760897c032c2a8af615cbb147d9f5a55b2056094ab762955afba57307f60798bae88d50851503bbb09775123eb18ae85e02476da674e60704bfcc21e63b30be2583ad0f6dbb6ccd09c53010a01a490c6c38a11f70f6bc3a7b5913de871d331e7b5ad37261547335b0c08ef485710b02a972ca96d39a20f996b2de57012ba2c0286374ff27815bb3ff41339ae2532a91bf1a7e022a3d4a459e86946389b30f7ff4cf9e37d2ad81fe74c17999aedeb7e234682b78dc839bdc907dd55f86a756320db6de847e41851ff1292a5614db92ad1e30b3ccda9110d67b8547efd5a8335119ae48bd57737bb4497555c3343fb05143b48c574b539ea60fcedd412e48259a24f05c55d945585946374e55404a056cfdb3c34e977d0680f0075967bd80c284697aa1c92ce1d004397a4b7d83141a438cde88227b129c77284968def22019e1f6827038b06ea67ef84abb1017ff3e604ec4a86f223c2a14406b347d9f4b409e8684924c9a9fc5c6167cade54c5c45d6c3717acacd8f7033a5bcf2c5e9a360df4d927d40021c3139720a23b2c412e53569055fa8348febbdb6c8779617d1e394c295972f14bb4d43a79ba2c60fe5b097fa6c12efff38c0c3df18977a03982687c64772289110167e8b3aabe2bf9561f5175c665ce346ed19c5e026e9ff4cea0ebd45252724031e53885b6b5b72a1eb8e291bca5a3ef8f65173a6f69d75356d254cd2d5474c66880cee55db7a71b3053b4645c1fe8074e5f6f9ddb9db7eab1d17a7f97c918e65c2f649b656dc3569522cb633ad5ae65ea2b31b1a6174d74ea1b8dca8d91611fbab42f1ef4e5524bd687d9d61d531796ef6c715fe741e10b8244898fedcc56a0babc46ae82ce1bef1407d94a3cb072df6e6738b47ccabce0228e1a9dadafea96848fa34887898bb3b859b3b9ec9fe7a63b380fca32798d377d3ed7c93c713b83947604929495a04c37fda61a63d3faefe8388dc01bf5ba165f0a257b56cdc466817221f611c5c7e93694c715bf745058b5e3ad16a0f8eebd3abff381e2f062c020ade2c0da968231ae8992240818653162ad941df0da03a6c874ec144da06a6c9ccef17f3518123e97138db4d47c649bb87ea85b7ef77a1c9c387d013af53ca8651c37fba4434714ad3d9f30cfaf7184bc28a1e849fe40abe0c67f6e63f79f2463868b933ab3211d47b916913e1794ab599b66633f6521cd56cdc9945f6302000347a0a7978def0da3671593c64dbe50eef5e39d674199a525ed394f28bee17e4b039d652cfdc8c7eb4ab59f8ac70ecbc04a6ec60471c33a3b09b988fadbcee17ec163be43c0cdb5bbc779ce0f5002f16007f057bfab6cd38dadc529627305a678fd4e106033000000000000000178d32801a33df71838ad5df080777f3724cf6aa024849a5aa560f8d38b69c650c1de58cb919f6ae215229ee8802466d8c577103eb8986d0483e98c0898af2075a4ed0c4e5e019af4b52ea31e4c84cb4eadc5b1a448894e40cf363fa36476bba4c9e1d2cb0b7744cfe2a275cd32373c6656e2de0f3b7a3e8be08bc397f018ff9665df1aa10c9e966815eb4fae21d411a3100d4e6a506931d97c3662161c007bb028ce01ae737c7001cf23b476490e8654be568fc1ce3852784ee38859db69d84701865affd9be414c8c97cb197c82945e11040ff9b0306b2c28a1092c938bb61a06e2ca1e9ff27c764f0d998484fcbcd1bd59edf8a6ade43b7b6bff2ad1fab1a2efc467d28300dea02de23e26b49048762e921c5cf07ea68612ea088bebd8a0f06e0df53b75d714998e79b922833ad2f28510ef306138830a839799359d81c71a9c801564db7a304ff35df9429f8bd371e34045e3bbd07882e876255dba8b7e50461c85457866c773dbd29c07aa72a4dd28df24b9d167f3cbd4f973f7728432d270c0054957a93696ffd04b9f13351b219e1bd4d37ca9ae70f251eef2885fd46107dae7acf53bac873e54d100c84fddbe7e9bc62857b07672adfb349c61dd13e873706211578dc661c3e32fd7b96919307c20aafa7c8d913648117efdac1aa641120ba31bb61f6260244fa1db945fd9497c6675c702788713a91a8cfa4328261dd267ec6e331dc016507af174f58e594d0311de4d59b98de4486b6afaf64c622755e36591af26d8e1c28dee04c3cdbafcb105232bf72a889b8ada4e44a0c53ba7664b843e93832100e486de29275c0df6c3f4fadeeca404046eb020f90be37761550c439453fe6b713332bef98a8d70e1724ce82e939f0c439af20452b2ec997faa52ad287d84a98716f3f38a652aa07464c047c0e8d99061ffed1f8efd93cf2e564b9d6c52767edee7cdf45a735ade6bc8cc02827e934acbbbcef38fb780797b4ff4aaec1cfd58068b90380b9c7519b770c1155b484eeef764e767a8e71152bb3fd1e55a8016ccc5ee1c10b144885f49b6d346afcb9582afa432d8397391a00100d0ab7c7adfb7707edebaebc4f4a478f0868a4f0b0097d131b6315e1029c6fb8d2cc92100fc306ddc86ef9f48458f3c046d5d89ad34fd31fd3e6575cb179487d17534e55ee2870a59a805c32c7b0df8893c13d7d3156898baf9193e517c364bc7a88deae703852e2e1b2564d1180d32e83501a95ff61dc7faea0c7b29c86b097c3900c5ac3cab6bd4e822449e49f5d8b29b7ec085c24876ca55aa535fc8a0f57c696823ecb71217db978bda14d30be6b922547354e269887f123f7f7cf0c54a62d6f669b8e8745330d99b5f69252cb5f06dfd6ddd78476798e612c4312dad7c2cafef90251c33c401b7279067dcfff09d6bf441dffa338b2ea7b8ff480d6268b36cce2f78fae07f013fb3bf73ae40ffb1ade80db28151c0772082cbfa813628b34ea8cb76aa07ac2d51421d4387c0fba4231aca198b703ebf7f455ba83f0ae1aad40f345885e54f50ef11d6356b5428731803c33570dd8a4e7270a8c9fb27929839fbd1b2b048270bb9ccbe620910ea5ea14b07ae6f066ccaf193332b3ecc0a7ea5ffc923c24f9a26dd2d35060a2869a3959805109c8fd453c2c9c97ac9f0eef632a0b39cc075dc6d0bc97cfb5c2afc9d632010444783ef60a468ea03de5cc0dcc234045aa3d2ee00999c2e9e51ea98c864afa6be5cab2e6e695dd8de39a7d78062ba37d09e92a9933d8ba542bd8abc3dacde32ab8d7a3c8dbfc373eba9c02045cb2fcde978c8e30fa39ff5a9122a21b23b11570b70b1df0ac36f0476ebe4f1f77cb6036d75151eabff5110996624d9b638198e5319b44483351cce4b03367520193e50c2e2ec4e9227985715cead0dbb6e9825db7c8f8a44404974b97383891c21c9dfe9630af78024221554f840c92bba1058ab92e2f2e1620b9290fc2d19d4e88e71c2935378b128d7ce3112b8993617eef37fc710f7522df2c23006ccb6258f32d580d96011415ff29a308118e3662625abc57ce33df203994c16cc3199707cce3f92e95f3d1c4597af790f8ce67cc3d6346527b4b99b689630ab4c21118c09cfe8ae00cec82d4c310f036befb3c3d608da2237c28b1b615c0ecb5157da7d67cbbd2c686a6c67e413bc7258fcd25cb9808007cafa9b92322f2e431d42919ee5896bbe47cbdc1efc1e97472aac7dd2b9a7e3fa4d1ee9d47d64fe851fa5ceb40d68200c0db931a922952411662b99d4b36fc673ba6d83574e9e50d0c4051a89c5268780403e2ead70d460f65b7c1b5500112c1f4a21bcef58c4d71080aff1281a14e8d177d776794cfbed090eba96d06bc624178cbed9869b1f18c31159cbfde5a87665e49f6119ea80c4d233062d26bd33e60aa4c92bc9abc5e8aabd0e54d8b1023adfc86dc0d5adb939b785eefe37c2fe76907662f376f997c98f938e60a379aba9086b5030d5574b671bfb6380b771a59084ecbe98567ed2578ec35a9dcc838c75e9d1e810d7911dc663f59374b9b4ef11971284e65f55657efd66827fcce8c2753e65c56316c4e1bc14cfb701766223269c1f2f3edfabda44233dcd4d07a52f0b661947f902c56a149382f958af1bafdb445ca8d9b9adb89bac7078994f65a097f02f2dc25476038d276a7e3b41913e04da2c549c11c6f0311e8a42299fb608adcc8a279b9127d7cb933128e6bb87b5b4a267b3e742885e9c5ef3373d9464820a458d410125f473115f0a3637287ce7e6a90d98db5396fc4ea451ab1d2b303d4ca022695ff8062565c25aef1fe0d87b56e2e0ecab1387ff7374f1321326fa2497690d890996968cc0c303d849aa36aa7372a72fafa82f68836c4ea9c57a9abe53e62bfd224df33c3c72cafd24905a807944fcb01f39cbd802c4f1722121799f484d105cb08d48182fa7bc21332e6cf1a961a6609e71ad0291ca810003c8fff433d4ba2215610c042bf52ba27c7331d7c3771982fc5452299fbcae95be68e4640d2f230b69e24456b861a81645da14a823f9a13a36fe923644749d29cbdf0a7eaf33ee3802aa7368eedb28a6d8351004668dd3f740950cdd4b1f64d6c893c35c1fd2ef937d7b2153c206e1d43dad7bdfcb824d6971ee8f86e7bee7ad7adac8b3700b0a46688a1ff4a578fabe06826accd4af71ea96fc6074fe37d10405f66904b7b30e987efcafe59ec473155a56f14aaa3b08a0db8d69c2c0f02e423d78c8e8265864f1f3f52457ed11f898133774cdfa385baacbed9eeae0ce7f024131d35079f58fab50938d985aa0ffe97ba457f263bd4f707df1c1463e636b5965540b10204a38439ef2405f298bea5dc5a6111a7e01f92df0e4c782ed4af2c0ac965f470ade15f3b12c580b8d6293e303b0751ce6ff7e3c9fdb2154d9d5ee7ce4e00cc372d1d9269c0b1774e809f45a089466f7923da57edd732a0e7b7dd22c5e96f197f08e71ce82551737aca6ac133ca895133b39c963058f9ed50bb860f88efde9701dbb97001c933954f6416dd62aabaf7e1dff56a414d00a26ba6cdd32171b5d3929cd4c46b64c44b961c46a131f69144e8392d38e57b81a77f803ad97f2ef4c8991c109c7bc24e5eae62cf6d81773804741f0591076fa9e4ecbe7809401b5747e94deecaa395e66564105bd2dd1ca23e38dc923b87d0727117d15f74437c29924e18722fbfa3ea12c42241a8a3e06c445d634631a709012a79c8f2d3447b28afc3795864bdf76eddc6330f350d09b60d8a2d463764a79f678c1ec67c6c33204ec8be18fd3a34ed719952d995d6bc523b5810f7be55ada28a62e968049979d529c078c0e50de151df24d668031906a2f26cb7d49ed3379aa6012bf24874f9ba5645085cf90288e3dffb9445fd86814a86c14ee8209d5864a2693aa03caebf8b5985c6eecbe4247facb5fa0067391025517477bae6447f9fc8b9ab6e81137f45c0fac937e48d9b7ef9b6b95ffa21bd1014be6462c3ed5301c156a98dc40d8eb79406bb5b4cc13007a8596159ce52cd504248fa6733cad4b139e70b5176ebde6a37555fb0b8b5feab24b12bbb4069c7fe4255a9cf4567227a1b724154fb17955e7e8ed34700030c0bfdc3f547e868bf438536e1dc646d00c3611c9cbad882c534a9c44f868a8765d0c52006d8582c4e326c198df35a57c2129cfc32058e054027d35bf9440e210990395618d70c634737c2eebe5f2f9ee29e89a8339832d5c47086bec30b4978fe0b0450c91b040496ca1b7bfe05dc58f5b23bbbab7cd4ed32f5e2b3daed9e3f4f1adea74412298f97db176c07720186db2367ca7202918535a6f103da4dbd428e83fc059789682cbad82332a625555b2923368eee47a378174b04e2296a3301812c51fd279fa8db4f06d5199106fe438817e9e31a6fd029fe542454996cf54d5873511897a01832c5372e08c765e2903d0e51a320130a3c4d8004df1e1b3eb05f034a535a79401be1f8672d05e5465e0bbc9d13780d0b2a91e8d991bd4ccae3fd993ca3b50d5521b969dd705eb1c7877a167287c064fedf91fcdb58e008b69b079111faa69de2d378cfed92d260d35f90efdcb4c019206ec9de90650ccf0b9c429fbaa9ed42ce2e4738e5fbf35e0d136a4f5cb31a04ee36b9bf8d8ff2a42738ff25c83f13e4c620e2fee18f317f70d2e92fa6c8c448f848b1ba2ce073a109632b5a3837e3e957b1b1c13919c1d3a90ce8fdb794307eac8f153b521538ced9f48272ee2f25fa19ebfe17849f3973d38515109acab28e0228500dda96d8623d5d489ce4c690cb5e8338a537d22f9dee7c509fac9e9016b5e67528db771f57794134c01a38cccdf0e396f33ef05bfa80632ffb32596b05b8f60745ea084112f2825b63749d0e36b1fefe288c70f3fd91930f8721bb1392222e29aca9acf11d74b6ab76a9e7ffdbcb74b654de7b9871dfcfb851d84f9258172522236f46f2b2c75716d801433cac66face73f424732dcb7e6aec8f3b888fa0e588ecb931b1604081871087ad1977e673b375fa1feeceddc18321a5c10b981ae12102f0bd7d2dde2671147a511847b528552478c32b9ed2f8165748b18a6721b432742ca9f19767286353006a63230afc09eb3111a3fc61913bd3491b4998d3fb658607371f8d85b93c0f10a73e822af3c2c1d9f584a5dbdd12c9f8cd5aeae358924bb540b66c7b6687e13e760fb24ab4dc4da168b314216c77b432abe55ad5829561539d925d067eed6a6af532f79943dec71dd787cdd22735388b43ec20fe7cac0666d4bd009820e65ebad70c9a46df567cc51a082b3b06d31b6418fd41ca084f72f889bd5618ecc6010371211e90b6fce11e90356a72c3874ead97591e63dbf1cb15a10f6860ab352e162dcf03c0d5dae274f952d2fb8b29175e88ac90bf657ed63b508a612a6e2343bbfb1a3ef4a84aa6b94e986c52c7311f78d8ad1d9d909f416b5f75a3382c8d98916d062280876c1f0e6d1ba8f00a64c09589e2fa8087f8aebdf90808fe68c9406e76c0b49d9e5c5047ac88eb7f68146c09120065fa11485cdd2efff5dbfebac93533d36b86afd00ab6e71d807cc9be47ecf24dddb3c4b4f03bb64da8d39ebb538647403e3031ae788e81892a0b6bf2bf7804d0f6a1afb9bb5a6bbd8d771ee112f858c0bc907e7558281f2f2f35c547cc8b4dd757e06c381c1ca71dff030a7c4be8d14f4b779a99cb8b44e1d923c2783b24682d11cd83bf1d9193a9e34cbeb452568998b313576c5699f38126060bf7ec868e117efb063c922a1b3aa45824486f44cd8eec669c810aa34e2e10f83df786759a964a9f79d849ba9b20bd9b82720264138507614a4d414798b917238c85b59125cd98737a407748f466302000360a02daeaa4d3dcc3199cf9e55acb1aa7ddc745875a6f94178e2077487fe83d313def8b05dbae094eabe5e1f5a609676feb2b7431f0ef021b35684d63df26844a47147388bb84fc5b064875f213f02530daf959590709d85033fbc59033c1fd2fd4e106033000000000000000153c3c7014f44b4b402ed6fa24968f3fae1673912c23086dfeef803e749ab81625a5964c44b48bfeea92dd7e1e60586483e0608816105b31a62a7adc7c3d4f25b452e0dc53b8efee0433c5e0fded9c38a97deeb36ac70eabb39fe1b89f5224a743019ca077052fadfa469096d50eea21808d08f5a4faa38a08d1bc90633023039f6de3e002611e9bdaad2b573a99f4b0780b3fd2e19e6b3ac6e93e7bc09aa4aff0d19a76100edf714a3b48035d82df2f62388603256f9c2055f802aac51607db2c335ecc476a44c6b6885ceb1ba79c994b6e8def8043c67c7e63d7ee73d236c51949afc4ce7d78580c2adf2a129377db99d8b38ff9e026a2eaeeeef4eeffc71ef426459713123d57ba9535d201292f4881e1ad0ae4ed59570141f320f663438fba24afadacccdcc3f84df5366e6133ac38a87eaa072b5ee362f053b5b609218c87119e530412ffb1e5439e9bddbdab7ed97af7666ddcc7a8d3576637f0088a126d8bf0778def345867e4733ffce3094e0265809faca89161e2c6bb3fbc37324549030bf8c07cc16735aece7e21aba4d9a21508962f5f823bf777a1d0ce692e0ce8ec6c14c3a2ff2706201596ed41fbca4f6da33f0839d51f1e8c8a31621e358976d3db640c808aa248329ca1463aef215c1a1dec5038d040d8e6d251f646d81c49861f4245aceaad0604947a6cb05f4b34253ca4090d997541b91098d8a7706b3e5fdb054c494c33c09040706d572787126d4ec4ae9dc7915d55737a3314ac181686228c6c5802a88a0a4df72aa7c8ae2cebb4afd6141ca997c3b5a5a04becee5936abaed832676504979a7df9b8a0b587ebf3adaab03dc7fa868117ccbe561f0796aae91c1286940effb1f8ddc0368aebf9c2b3a7c324cc3f746e14ed7e18049e4963f5fa0b05dc6d60f22dae5ca92bb93b880374817d53d1bab8b4c68d8d8e0e42858070bb90775388134c3c109bc2b47dec0967b4550bbeddb40c93c9e3fef221c0a73c4f50e33613958ce82b821823539ebfdfccde90d5cf104e819d6e847fd6c88d6ecb8b68e2faaa46d07c07f45130faa954cf212df6e6988ec94b72fc0788166a9a5307cc7c8b4810f6cdd6a92b97ada589a04b759d8228f9cded0dd58601995ee69fbaace450f3e4ddd41f85d75d8fbc3710b2e595403d3dff1d3305dad944892ab70479ce7d939d49ba917fe30471b37fa760acca992ecbe6c475672155c4fa2a9e6f2d66fb99b9ef99c9188ee8678b5efea505aec3c8912300f2b578badd63a3ed2c6f8ece99a5202a90764fdd892dbd58839a827b75899ae3f542dd4ee4b80afae2349afea33656d68b212687e6f267a3e7f02641d7fb4ac3a04fa89684730cc395bf186ce081d8d86ecd4a3b8fff66d9cbee1c5fedcad8e250a045dd53205072fcc4285729ce279ee4c19875803e962a1a6dc1135f3f00eb1e0e69e15808d2c889cac52cf177e027b745db026e9ab172d0bc421cc62993bea76f0bf8c6f0116029098d4f0cf26fca0d5b002c4c47589c2014e3abe8e50d676a73c387ed84c1823e1b61f64dacd094bc42f0343b869f5f7c23a9101e7d425f8fd8c991af6ea339762efe3a1d2cba0a6c08c29ae2cb6e93ff79ba87f289b320457b587f496f400f33eca1a7e575082204116a9de9ece8f74ed2006f3b3cf37360d1f281ce285e44ffe48b0a5cc01f9210cb390ede87f0137aaa2171dcdfedaa02fd960154a0a76e04295220db9260cb28c2ecdd32943f7987abee4e55629705a80c40ba46e624beb3a23bb99abaf0fa0896334c7b6b567dec9e6c3ec4ea02d46fa463847076b58be9960a093cde08fec75d569c47d0f2eb688c8d757b8a4c882af281ba3be4ea407aeafed307b80dd66917f2648cd34e0fad578e3f222c33da2f80ee087daf64fd8bcb30e7e3557fdb21c4f155c0f7a2d4aca1b6eb1699029576ef93c405c58d34e90221c0203a2f275747db4e9a028c17d47ea9d576198be3d3be82fb7e6cad1e67111c6739523360568f11df7a4a906f3e4f41b8b2b574282b9189c1a765ee80ecd85bb1eacbb8c28a4d4749238d63920f0e17922c6ce2bf53feed286b8caedb9dc70dbbd2f2cf206534e272119617dfb2783ca1c2ba646089b7f75494d993d032227bee83153482ee6238190a65e08bf0c570e3502f20603a5c634749006e9a56a7d501c18e367512e9d9b02b32ecd145783c5cc07b6fdf254566205d4fe25b357eba0aeba72220153ea4fc1d44474322ab9fa749951f04b7c7a22174b02e82e2bbac157b84d83fd2d99509e4e2b2bd078103f6d1d667fb34e574bfc02a75d3178639b88e2c15b7e719a5ee8211a1ab58c28b81aa78d3e393a92997cc9c2fbf5cefb92d6ae258643d9283e1e13ceb528c984248dca88cc7db26a04c164db90f3bfb9b642d06cecefb23eb2f033c67a79e560a19ee01d12d2fb6f4baad1549326f7977e733b57d5c02c8fe60fe9f645ea7c59c587fe13a599baffc34be7156bd27d0ef92fc1e6a1d1478f4ce15aafe629050fc0ed5406465ead8b9828c9f1aee732a2532aaf2bf89d7cc7e9cad576243d5905f6b332e46f515acc9f424962d2b30bc64cad25bb761a64dc8c93723b97a0690e0f6d8a4ef37833df13e6fc8bae3f023adc4368fa08cf842eacb2b55a9bf52346ba02126e1a2409d3d54fc0b8f7728e8001d7ce9bb62e35c65098e429f3908565e7c112786f31c83a20a88d42254cde5eba3af36db8ea09ce11c2526d89b577b40c977d9039cb119b29270d3e45fa3b0d72d19b664ec6882b8f24fb60a24ed35e4140acc9fc8b580f7d44694c436e4522b59e6e861a06f061d74ee028da4a7d5483afaa6f105fb257bbaa6181b9754bd04e8810e69dc8c7872d3197857eab1245917e95ed911e4451a0a17b624f3984c92cba878b58fc09f1e1075f79e81539d50f41b5af62a12e4db4633d2cee1a91a507ce70359e88f5f1a2e6431de5b564ea2e7dfe130418aafc047e6facbddb7d9cc5d0a79cd981a9c1decc066b0fcd74b89e8bb0025c27fb4be2d4560ed918f55e58e74dc4a606d14cd7c2c60049c94868ddb26d58a8c032664553d644d6838b0ce057f265b6241bfded8933ec36242a9494cb24997ce061c12cd42026d771e6282052718a9a77cf6fd0122c0c324e04bf7c838849da16bd9eefdce831ddb92e19679bc40202be394d1f25b1a356fd7d23630da7860a5277d155057bd7a26910534471c68d3c24e8859a8fd79509e82ff15eb8027668c266d034ead5708bf6924a0f0060c39c74c030f63f7dd60c40ce7d73644d68e4589f20316f60032a6fa49dd7af49d98f9752ec33e025c4c982dd82bb9ff7fe4f6c55604bccea4f5bfeeddda2762940352b1935571d2f9b8a06c959a688b4c39b62dd35bf2148ead8ae52852302de27ab91672ef52e8363304d8ec6429bc0f10d920f71bb85c4393911e7610257d7eb23e20718800ed8c4a0fe484f63823a04010e0e02591434967b12a90c0181380bec86c8314ecb047d1b37f8a0351671a78168575ac57a0d3ca723ff7f1f6c5bc99eb9309adb0b122d6ae81caf5355c2bf1451f220a77ca6741e86b15a98a4912792eef9b05d6883aed374c15b2265daf6677cb7c9bfde7bb41c6170d87d3b1a02b3bdd99ab7bf95568722d64a76ae42ccd94de186a77f81840907bea8366cea8bb3860d82206bd1ad65e1f6b6b85c103d13be6e42fdb0412a65a1b6184b6c7da3e92ab2d0adf59dfdd9369eaf1510dfdef16e638fdd6bacee1d52c6a5883dac6fb749b0cbe88974e80b1ebef364336666ce77094348d7a9e6383eeca4d31f76d04817ee118c32ee71cd2dac541108cf6a399cf4d4c9eae04fc40d39c81114e94dd2679a9fe96c619fd6c4f8762f43099696a57d4405b4a36692b242028ac72464f1267a6e0d51dc80c4bd5d88147716d76a3d2d844dfdd6aa60810ba049b4fddf96591cd01ef4891bebbcd3882f87891332dbf6361296fd3357e7e04a9bd4a482543ba7d87cc034f4b3f082d128fca2b2500b0fdf6862c5d529d80b952cf479a3ee483bc071b3519c9194395c985584a586b311a9f58e0e76e765c8cb5b0010b62b537e03c1a72be6be6f39c73a5f68e6d259e805a903b61464af4c451c00f80b7debe7aa969dbe51061e1e66669671b55fc12dd95d1b4f3441e1c1d0976940d39655e6ed1203d0239969191eb520e637a7264d849e1ae2a19efae945378dfb0aa1c8667d47c1997b08cbfd41a45ec056aae76e52f7d57dc963449e73aeb58761681f0c5bca164c16fc1452213e882ec95f98d935a9b836d491cbaf4ad755bdcbbfea00b4cd97c036d2e0d3aadc39e00e417b3bc6f2ea0b10ad612760c9669b88b0115500f64e227de7e828afb99556a1b16c292e0c22451858b3c9b5bf7636435be61db14febb925440cde40166fe5f8b7b3ba2c77a2fe694ef7449f80a50c08410f158a4b238db7d3fd99639b2efe16fb32bdbf2d901949c9eb627adcc649644e72ee789f8174828f159f548b957aefdbee4d11924cc567f02330ed8a9536726582f3e7cfa5c9270238d6d827ad3cd64cf4c56be039cce0184cc9744cd09a5ef7475b7ee90f1a4850d1e4b8df9a71abbed87a6032408b6e7ccfc498f2d57d8405b1758570f426bb2ceb12bfce967e72daec08c2479c9c49193f3f4bbe7a927868d30ff1406bf51ed99dfe7bb77bdc46982bdfbe14528ff642f4370a89b53a3043a639a0d9ff79b3ff0b25adf406d68fb87c283185c0a307ec55eabc68cc1cc0f3a2c25ecd57cdb14fdf89a645eeb26dba74b2614ac6f8980c85f045cba0c8e6513e3700119dfd6a542748b33e4cebc49c32efe55f2517ba58ad9ffc0fda64eff02588196fee4912a3353a93a84d13fdeed354b0f2a92cf7066b147ab7a7eaa067aeba65c21c4092967b7d029d0131368aa7ec05eca1124f97572bcdae062818145d41dffa5991a346b7efdfde2c1b27c3617a362f28a48eb009944402f4c69ceb926cd76ed2243ace4553febabc3a3a76bf7b7e5f7f0c1a1f1909353f68429f7f0f21b6f6348893c0cef20eef952c179fe949768c3feb0644eae60bf13e0061ad7db09950b9fa2848e6aef43d6d6a0e7aec824de011ce80ccc0975e84a38b1dde2a8a3fed2c5806f937026553adbc035ae305c8a8e9140be9ac705396ab9c9f998a0f4e86a1d9cb0da2dde48e0826fef0a57501cf3397a546fab9f9b5bafdc6229010123392379f3810d47d567dd972801953f3d8438e0c232038fd699d62dae6e6791677a3f8cb64a9dbbc8a31edd54ec9b042a0e50a6eadfe4b171272fc7dfa33cd0b23239ee93a10410485d3729df8d49a5b6ec3c16aa106c7e331b94ad074c9fbfd80370bde9d6af943d88ee6e771c05a3035ba114fcb5c6c488a7bb5fa59e457d275e445c2fd68c0f30ace456a52103eab5cf881f2189529c6a1b5226d6a0e6697df550a50baed2a5d59bc54b43e9fcafa255faa64a8eb20b586b27d9d220dd50fa96abdee0fec46d3eb785c4a9835c95cff9128c0a0eeaa508b33f7599f5861638b6a48318a78bb4cc4853a3922076abdb2768772f47cf95251e7dc26728d91de3420745444cd0a136ecced532784536be30f72b9314a0c6364e4655eec3306c24113772c206f22bf6e698543775ebb67f4a9ef87d3a45451ca7071a5058ed2dd9cf70a7e5411b11148785c3bf9697c3d77833c846961d525f48ae5310f5d811b77bd9bbec7031828969f75f304a58248066c01faf2fce0eabc4b366199d400d976df4ac8342cd244e2b72151e90414c75e277c311359ca7928824d8cc926abda5e5b26a98ba1f4a20e95ed8d4e5603bb0d4ef50adc590c021adadd000ce51a3cf48a7e7a2775c01a5b202a9c4a649c4ca73b3357630200035da4b6bf1799289b26fba1974d152809a7eb87ea87159f9f350e739b7c0a9c8eb849525515126fc76d80af8924b68f78808772bd737c5c2520bbe7882c35826f7372cf2d9c074941172db04d823ca40e3c9d038691cc84dd96798b1494ed311afd4e10603300000000000000012ec49700618f294d05efdfea3d9e825491ca212ded6d798f80f7b1aee8f53ed7fef4af055bce1f3add0fe22076dfb84b4c8e818efd8463ce8d88715e12754d2f12375f397926d9e618bb0f59f0d04e13711c4df0cce3f38563614f239cf51f56e3f5406b702fdef2ceb706d219682c9facd1ff1b0ca17fd88f5caa2a50215757df76bc6a6313891b75c23bf5853ca419bba3ce780d35057809247e2fa00a5d9cfff0b171e301035128585def66662f8a5f7683ce89f42497f28997fbaa6e7500b98c32df37866f60feb97176df5a1934dafb9799f6485d6a4e340f3a39c0e0c6dbeeb35e775668734ddaf0da818b520303ea40b7209fcc9dfe9b9cc9ce8240fcbfdfc4a61bf8d96d6b15794867d2511789facce3bafdf3849cf54cb8b9a6e19ab2e8fb234817a56341f321856db48a224855f129c764e33bac5cf6e3fb1c53563b1bf4f279d5e4df8647dd9b06538a7b035c38fad1e1b798f17f92a11c630e3fe14c2941b39155fad69f3eb802f6dbdc3f967e8f2cb3407961c6350475f82a5b741ebccadaf881bc64494a4523b53af609ec993cf583fa604dc4be4d3400af25fcca5c93d16605870040a09bb505ab6cd62e9bdd0cc2b6effc1820c8959b6cf9e06fabb7ed8c4e04380b51b2d6fa917b2ed5439f43861d6b39301d3a42df22c18834227b442576bb4a3dc2a6ee508a3b2220bd3e9f38e80fa491a9d577c191f8f41ec820f9a9735971134e7ccc877036a3412ad9c64e77ebce4fd73d6755e46388cd86fc2a9e5c3f39186b215196c8d8842817543e722ccf7ebae8b1000383ca61cf2b9fce389d2075c9eb36a6bd535068fb8c0f8158282a587bdd95d00b49c414df56cf923248be7c84d53b80d31544686692cf7f11bdbb3a247d779c6069df8900cc31b6dadc44c80443d980c0b0bfe4bb3a8faf7f3a2b072b3d68d433a6f401c10fda158638642640aeb9899fa1db2a2b510c4f67be45098e316b38d8a88d3e657e624f4c5d7b8f817dd6d055f961ba91e6db384fcc2a9c11d7d6e28c63ce91db1a52d11db1a84cf2f4696a8c3b61cfd280bb80428e26d6ad420b1c29c2b3e46f1093e90bb2f0164a44a74580339c7416ba652fe913bb72ca0c8e965c610295364ec820247be450810e70bc0dd868cdcd8acfbede016c9418bac5b2d464a7f5bef84cad4327c8dc32483ea816a12b3261fb78fe76ddf7185017f4a1e009d2646eceaaff11f180676b32c95fc5cd3020790e5542941cf6f16bb83ddbc4acad8ea7318a8a6c23df9ac229ceb2fe5c5b29ce1bff34e85e945c31ce838f51d565ae15201b4662859392f1203aef731ace9156d0df20a68f58b4d58401ff7ecd44fee02806951b18c73787e365d6dcfbf9df2af8e31b81eadcab3189f125f038747cfc122256e5582eeff56595064d378cdcb0f7b80188d7e5fa3dbb612b961759b2ea630832de5da2e6fad3e93284313ff6573c66bcf272bc1149c4c2863c475a729ce8e59dea0bafd9291e9ecb17b708e2e5add4430df0f24b3cc8f502d0549c440252d1123684bd1b50cb4afcf7a2b789ebc177907d0c7de8383a3c91ede3ce20d5ac3a76bc2c33c891eeef35982eca5a6247af917b0a470324e0f48c17caf43050a11b6847577a540c9f972c48ea616cf24cfeebf6b4b3156b961f0c203b0af7220c11f2f18d89ca61affc6fecba0c82cd5b9fb2a10e1510eddb96069c598a08a201e537568ec3109ca5fba7c8e5c9bd80c12bb7928b2d5b72e591176904377356b9182405fa837e9f78c3d8aed0df9514f7b2f638cde70cc5b172b287401a6253b5dcf75c0788e2230ed64c7cc34d400d608f81918773976b431d73c3cc6abe2fda1e0d429e4047e1a4acf620d0f8d8580676c5a250656c7859699d986e2081d4021a5985f5e31ec60c56d77f7189b56181eabf3c6614cf5c7bb9a2f0eba66cff3c48dd6cb5f7fb1336d988820d355015c1822254cd29fc8dedee92df607e0ec8594edf71b0f2c0d20c7d2d6cb640f83f4d94156ac4611da8cb0055253a8c848bbf13136d21fa382f7facd982df8444595d97b5dc592723402382a41ee504b8595749bf7ac09946a56ff4da88829acac409b23bf86c107c0d50c8fdc38b27f4f626ce0d80b5bc4ddfc13ff96e04e0583df3b574b31836c34305bfb87504654744c1e0ee7f1ce423e2765a564dd06a093a48a62cce18d101af001d0d6da8e0794b1a6309634bc1356b3a889c69c545e385c321f3ab43105e0c0f59fc51b84697a28142e9944ccb24b89b4a2ffa624b024447a775b0f66bf305e62d6bbd95341e4a434a5b586117f24c76d2fe548432fb064ba018ae3d894c5f26e8bc165e552b8378aedee5ac4b315f1d9c126d3f832c75e1138750fddfd4b63090884cef823a5e5456a335312cb6c143b314a7c915f5c8de401bf88938b5f3aa25c5903e2579061a0397ad84db07191aa4347985278531da7852bd96cc45f6f67fb9c3f699681ed5cc1730ab37ab08122da80e73cbe7fb0cfd508cee4d9d1869b3ad9473bd06691db287fd382367ac63ac97f5a11e3cef276d85eefabeec43e1080d3b553ba692b00989d1e62b51b81cb36e2495360e2e138c2706bd1c78a7db19c84fb5ef46d36437a5a9c3e220b4f83773df032c8d0446750a830596f95254252b08a9573003fea296af5388494664b986fc863fbed536fd25d1ec0cbd2f7ec533594f3d64691e7380d42de1730cb05ec5981c0a3e9cd5ec6c61deae54e2a781c085f6ea87d9a018ce40ccfd514b0bb2e4348985b3e4f19e5a0100d85762f670c1e0cd905e89a02e3701d946d2f1aeca82cf0b8979aa9e7716c0ee485c16da08476fd9fc2517442ca99f642abad0d40980cfde170afd4b0835f57097eb0352f717ab4c50655462b196ecd3c681d33a0b79033cdb124cd00fae2576459d77099bd3432bdf1c85833f4647b0d8f8bf191d70df1092293da5406c89aeba92aab47c0989bb0b86002abafef5ac690d03a85ffcb67d4196e1531636df366f4a332325387d4144472b1bb45cb7c504aa44bb43e0d3acdcb275845c0de323de196e877851085d1829b4bddbd02e561deb2a029c4fe8dae1d997fd7b7ae9db9fe114ffdac857b83d2c71ccc6bfa561f928e009ddebded2834061b04b1af9de0ea97cb4e0604d366786b58bc5a4d14c00e3af2f781c12121ec1e2dd000e5bacfd9b3a84cdb74e1290ef4710d4578dc3eb3d1718e609bfd663741f9e4d575b8417cebd4d2326a8703a08459125c4516511964fa59dbb0bd584188a73f7d34d2badf0c059687978df2aa5d5bfe179c0fe5c2d9354b1fe9ea30b16cfd59e35d498d1310eb4dd0d52490d488bf0dace801b3c35a0b7b484506834afb891dda4f35c44e4a6c328d6e3781490b53a0bcca409f5e4b00197413a2243d53087d218c1f9846bb789bd29e54d30357579cd65eaac87ac4eafedd7a5d0ade0fcee3cc8cb126dc0f538673e1cfbe2fe5dc29a3424977d0711d2ffe7e2536badc6b55cf4165ec8c6d2878e37e196993a1d65e15d6a72a218806f230227c4f0e387748752d26b93ebb4e69bccf2295bbe29ba4f112d35303ce9c43a415f5569cdee77755e64f99f09c10943aa8af89a572bd7b52e9488e364a8266e080fd580f36bee43d5d5090ffdab5f7daa3ae948be9c7f5697b68d84817ee33c53558ffc08c9af1599c4bcbdbdc5a28339d1e54c988fdda938f0da175cc8e27ff211577677cc720cb4b5e3f024c76c83fbcbf75daa28a3a745f24fea00340afb798244b3c9b14753ed769bcb2efe47891d2a513c3e4e98b59fec6309e526a52da5d009fecbde224f595612bb8fd8712ebdf1a62957e37d471c739a3b4f059f04361187ccd292b783d7d250ca0b602b4891749c30150b62ba01b7b84c6ca2de03cdb1f3a974a94408d7c487f2048b3bf08d67094b4fe7d9f0243404f6caee62a340837ee76c7d9d95c992897383114085377fb49ba6c7f8c2c40e8d97d2f5b661ac2e5fc61c0114ded7d38439eaff54c4bbf00528b7e99572fc42a53e2036cada085285a5cf6c85f2d7923e0cbff49c486f06d59d635974f67c7579095d599b0b6b9fc7ab2167023bdd36577179c1346ef755704a02973f5cdb22b72b404e81440ecf83e63c49f17921548739744e45f755e9b9b932369ad0edb4173964e1003186b1ee6d9dc755bd10b4e72bd7eb6e6e3242a9643e1f9929ba9995dc73568a65b02c4eaa8af15606779a1a69f5fed773da273fe85cb72d1df6594d4954b5bb838005e17e2d8f18b963a417412c616f346751eae2fa15b0cbab26cb68bc7cb745efdd1dbccddbbf55948d3749b8078b6d2b2929e09c91bb1d13acb184ba91b4fb9422c59ee785f26d77e14596d459a268a726da71b8ceaae00689b4c8d8782cce15f1376e8d384ccb45ab415725d38bf5011599ea568a835bbbf0b9605bf3ea7e694b3dea4a434b0fefc797c66f81d66291257964674bd37741069d08be03adc5646f93a172bdca6a5b654c62f4bae6a35c05f17e9151fd4a03bd528ae7bd0296a5d883bf750fc4dbb56059422394cdd77bf470f0d33ec7c7cc9a9cbac1c8d2fb6c6a91d6d7017990eb2ebb5c4110b01b5fb1752a1953d8228c46b3b1180e956b3b050077a15a6b93539e29f60f0ca37e8c47ec4bcb33ad598e2a85bb29b259928a6c71a0b92f35bf5375175bb3caec503f917ff4f9c232146ebb6eb855c4cc39a8014c9411055f7e558df84c4aed1ad71afea0d1a3a8119dbe9f0bcd0bdb80218bd00f4cd38108df9bc6c0c76a5c19de120cfcd9351584693b6e9468a26b26e8e0269c5c2a45dfc0a47366d64bf8f5fab5a5ac26382151bd9ed3511ef45ac04ba6ab0dfd4c9c213a6807536c21b56d2d7f8e51379e92b7f7ba2f7c0960637e12d17dad9b828b4e1fb6d01a7a73f0f39572fe9a85016362ef98d332c589da7e1eec561fae5a75a50b3e88ea10f755676f0a6cef0a7422a06e39ebf3d51baedb93cece379160b0d063a31fa93b81c84b57fdfa946e721707dba0d459e9ae474ffd7627aa071f342cf6abcc9ab92b92675a54fc60c9937855ca0b9d0e953d181ae9bf24498fcb7ad313e2ec06b0eb00e4d75b0341b67739c5c80f22f9a181b2eeb1ec8722c754a31b8a16eeedad6b36852a1d2fed47eba12b95202854b84f68a4cb13219072b25b9850829fa176276f2a4f087ddc74a52df87311fa0201c1ee2af7282427cf4240fb5cc35fdba2b1145c84166c8ddc907a0e3192825a94f4e7ae90ae3e675f6d13b8c43c2e14aafcd5350a388b0f3bb403b395decb8facc2019e21cc36ea809871e24b9eae8ebbafa79aefff7c548496df9c233432509151458617018427bb66554d77ecf7f5ee3676d65ef6462d87fa4f0a5cce5942ae172159168ee232ae345d59294f9bca9ed887ac72036546f087cf2989ab3ea84a5cc534b68faed59f121d032c2c3de49764ed492060fcec4e3e6fd5a239ce85684491e92d08d12e1119830894c7665fc173b6affaeb36c1859cc48584f7a7b92c95e26c963dc7a128ee788b18407c420806677b7de70e7c3bc2063f1bbba2003d325d67d60d936653b2ecdb9caf4444835cea92455fb7690bf1a83616d90d98aedad894304f3617e0dbc6022a9d07fe38a32034831cf319d3235450fe58188ec2a6e0b18c0d009dede33b8a894f9af1123adca900a4096a4aa78dab53d7d3da0c4ae0973cec8704774d08dc6a7d0547175f7a410cd834883f2e5a9eb7e79ee23a539c31fac6590215b6a4a69c7ed1c686ce08c14bf3c2d302db6959a6f2072d86bdb857af8833fac98dd67fa7f63384931ddd20bb7a6121e2d6bb722f2a0f13f525a6b5a87f6acd80bb05740faeb45b6d9b4f6d2115674507e74ae65f630200030e7c24c00ce389596fcd502c084e220a9d3ade25b3dd9951c6e097219c47d25a0084a6ce753affefe384bbe0d4287760870949bff9e3906f5ec7aa4214df65dde4842155a4c83d70cf3196cabd4a90d3280ab8220af6e1af3efef092d4e3e412fd4e10603300000000000000017992e6008992faaed376449d93d569185acc2c58fdeb3bfaa1ae8f1963ffab58d515bbffca2ca7533a6719910231580cc13b1cd39ef07a2ddc52a36a968bf4101c071af8f9630eb89b6db3d26fb7fed0e9b9662155c519826aaaf2df8c64713b18a83fbda62fc3dedb974a844704a48ce0f99a818c9ecbf14bd0f180df63c3bd86840e938dd27011e2363d47f3507cd69acce1315e592563130a17ca626bb66ac69a154cd28c01ec24bbfc867d8ab7771ed00047334fa280df18bfb8ad1e689a9727ad4cc6024e2eaf1ea1f4fc91152a99a31b332d3c52fbbc84e4d9eb387e364b207aeca5f75a4cee87573ae3b36054f7597edcf63a1c849b5f66d0119c2190aeaa31f4539aa8016068b97c89a1078747c4c134f51797c05f3000f0e29f4ba581c393b16947afc95384a124344ab541bfbf1b147659a82f32bcdd2e4bdd49ceed79a62e4f583e91e84f04a097b0eb063d6cf5d8d106962d5edb82e0c322b83d6356778bbfa566639cde4608e6d5bde47588dc0a895e25f4bdf368b81ae99f70c3de151f82b1432af68cea344148ce40e86deedf720a3715da2447c91d42660274c8b70215e2dd48e9a96e255d89deaf775dd1be27b31d6e2777b99c2196a91a44d531d730010bacd6d2ad35650e0b958224251761bb6a1a610b6ce8deec69e446424b0b688a96a46faafe98d0314443566322dda78387097f0e794bf84d097b5f8153fb60358ea889ff3dda17ff140b99c112ca231684788ffcf37ee27be5ef0df4ebbdf820f30e8740cdc6bae3bc85e4d6bb0d8c0e5ee1459c859f8187aa7830e9ab80e48566f298ea514d81440d0e7ccdc26129387126427409dcc84861f6da0a2c6c512e95f5af036af0e407215630eac4edf062ae4cbba537c23b3b309db43fa67b3e9ed37ad553d3b1308ba95f6405f65c28c110adce64092609007f31a67346a8ccee0f5a521cea8b392b386681398ad5138a281001d509f0714d31618b42f432b382fc7bd2f3231c2b1c1e44b7250074be26dcd8ce26a566bd3dbc19b59b5561638a26784137eddbb90e8b5e38f7424a0b7023fb91210e9e99ebd579b77b113ff6d925e0d607501a0416fbf7cdb7ad627f714c094464249688fb211b7d1647a5f1c6207d3d3378d98d388cbafc33a3b94d15f589e9d3c414295e93298bf0a1b99cd29d7675d04da209ce542285d2cb44f36c15986bc13d73a7ad1073fa5919576daf352975eafae480b5586df20ada6ae9a0b05d4a980410818d7eb17d9d9840d321d9f937883fd113260ec4595b997c0722416591a027317d93dc1cd255f2d799e0e10c52cf6a19b434f3b05ba33310673eb3117d5d3ab43bed583345625c3683f7fa7befe1cd156f726c78fc80b8c738dd8186de3cf76a6233b8101acc488546c0dafcd574e723d7bae0763e85b4b875927d45b5b10aa115750df69d51f2372f94141ea03e696e0b917e1e74d1f1470225489d0d4919e3e850b887c61ad78f04447b31454289703f57e906b47602979b9cc16d7d2dd6a5e6837e50ca8143b3abddde61270934e20a8d0e391be955294bf367e69ae9b957563177baa17406661e4c49955a98ce2e6055394afe52a1963a45d12829ca03bf40e9fa7564f163a1cd68535dcdd12d5ce62f780a768fd7d7d7cf3eb3982773d453e830ad3d858d90eb1fb57370cc45cd1fcac61835210e8e174addc7b6d5e724227e678c6627dcbf6977a3d805e74d694bd8bf80f3a65345700f436e9cfbcd5b0bc23b025b09091a26a7d88e08729d51ef1e6ee86789409230b2b6d0edd8de3b6b345560639cdacaa115a186dd97ff10bfc9d679ce268568548a50b193b5d07e9e5299f400094dd8dc20b090f6a8b7e825e4fd30f016bd67c6fef9e25e3c195b50cdd5c9a2efe15080700f878871946a5dd5d7f869fb0ce9598890bbe957fb5d49cc4174238e3262f1a3265c492116e00d69e7aa9130f58f2d9579beda6e580a7edcb884c79e5986dac2c9785521225f3ba3ce56391d5ede1d3bb28c29bba067134c3972fba912ac865a3f9fce68af3209b690dc46394c4dd955010e9e5f451a2ce02dd23e36d618072e79d58e2902bcef5e5de18615acb15aa4db079f9470ed15be7415e18bbd13b003fccd00927877e0b02f63068ac1a806385c6549e7977f75ad0e066e6692f0505b7ca4c7e94c336aee5debb8c9d665eec0f6253c4a435c37645ba1c97be6e60fae6e640938fb47972d2cadd5c1119c359b7a732bf0c8d37b8e124aa928c29dec24e8fb0dfcb2dc1b27c8ba1033acb7d91bcf549ae97186519ee9d257e9a17933047f7e30717af72dd344baaa7c4284e6456beca321105424a6cdf50e08dc541fae563b108ea69679e6233ce303b294f73e7ac169af780936e9a995445e93532368dbd91fce3055e373e3ed56f8d5b0daa74ecc583a6744b99e1201bfd8f67d5ed0411802d8b4ed5fd405dbd61ea890cc0b7a0ebf9e42a77b3f57ebc70286f49e83315550820e2e9691a8b3218d9def9b70a47ac3d0f520632a722475fa619d33e45c65034c7fa5621732d2309c1106a50b2a4d0a40cbd42c4caa9905c1860e162efb40013b138eb9dc56d9d4fb5e33b275c7652079ca6c28320a9d6963ea2e4ebd25b244f6e8fd5abedd857d02bf15d78a5b90a71efe84631b48524b3f40f57b0e8268cc607dc0a7abde35d77be2a04b8d13a0cec161504ece8e96538ebc0f1797449b21e5bbf5cf06edfcae3bcd81898e38fd177fa3d3a3a081d9f53635dd7984f6fb2ad01190c43ebcd6198fd1a95b95014e1fb9653b081e19be9fca464d7cfb51aae485c94d7e37f14557b2df866db39909c9ad7e2d3e748e24237b6aa982f0e283fef5db6a5085316af0554cc35b84fe8ca38864f53c395ae2a642cc6f9e886b0e7f81d6a8fc11401296011e7e03ef5b327e467e153f5d612b190950e335914c7957719d837e7868970cc0ff9fa24af08e105b750686750340c09088b06cff40f7b5c7ad7a6b8e07f8dc90b070000ecc8fade67e63d4dacc07b855efdde8d1334b2ea2d9e3a6d57367fc34ce9694bf2735a43d7fe19c8c3d35f966f7afa3210748e01c0bc887bf9cdc91b63fa0b2e02261dc534f559de72d88f248b8b9b05d7a81212497bce643fe89757a9b825f5bf3e8d84a129bf231f6efb13fa478bde185c62101510d329d6bd8e3473d0db847eea66ce2d1975232f87b9730c3c7e3421296878d1e5c48d4250b34a0c4cd21e7d7bc01cdf4887c8a954db53c3257549267ab405b07e51d71c90ff75c7db5785549381b9cdb348b5c91f8d00bd0884363b2a9f928b05fe81d9e3de3434cefe37b215b187bb9062950877e42beb0c1335f7ebe01fef8de02df37dd81909b2494ef279c496e2970d992ff90947f23ea1b1c42a0b75d5ac8ae52ff66d86dd0b1aba76b575adc2b01b9b374b93a069d7dabfa608ff37184902382aadb582dd10254f426012b3d6a6e180e38d2dc1599bd7a2c4ebb43d6d8d066340d9464af685758fbe4322e788cf7500a6ffbaf04f02821f72d9d824467a6b7512285298c78b06dac316da4663cd3714eb25cf188cdf7b5545203963596b75280b6b802aa3f6ab0679c9d8c7195702a4e8805e5e129c156897565e61f61397b3aa30f09985f6417b2a26842b9382afc56d2c7d3d4fe94e04da9a4a91319f8a0b5c3a2d38d900d56e4c87299ced5f838c9363258f61f0020b15270f5e8e5ff30f0b0e75fa453caa0e60556237c417735a3fa3ff07b73cb7517b47fe973b6998c4f536217d61c379ab47b04c7972e68839e28228be3e9a6f7fd3bef7fa2134b9d705d400c4a08e7150c8fcaf5f10a044fb39e36920fe7f17bb0e76faf58af6ee125e9226c0cf3aa48d46e147e00baa95e07507db9d85ec9bee0c399afe803623720fcdbc54c413eb0d8abdfce6907ea6461244e46b8e429dbe7a2bd145e308124d59eddc9f1b83f070999abedc427c0fa8bc5ba83e9948a159cdb29992dcebf35b90b5a8becd971730b055022f879b175c4b31b9dee5cca39b76641a49d8e99dc4c9a3262ed4249a84057c9fed72a28fd3f82dbe472b3da5f841b85dc7308dba06bee6389c357f33999236b287a998557594af60ea9f529086e06e7c4e5bcc698b18f4737baea0f613d4198726116c8a11cca7865dfe78567d62554aaccce463f29d5f9dcfc5b891429e579e31dfc3df8ec6bb03e37c6c97d5bea613ef6c6119bf8f3e90fc153a55750c2f7d829363f74db4e04955eb3908bf3fec12f1742abb7e6a174ebd30bfe56dc85f076ac809724dbf5c7a59b6915106492e5fe60daa254c963d1612b70a39124d89340d68778510268671019991f2ac15654d2aa699646814860f03de6639426d3b6b08f8f353243d556ac045810f5edd24c5550b50f7df9f53201a67da6279b864ae28281dfe6589f5cf7082883875f17826da76e1dc6c608aad26e7235ae883518c4cbff3ca930c74e67acd473119a665965f61fa8078fad135143e26163bc86e170ba0354ac373f019fbdb0324b74071a7c52607a0bf45c5fcaad148adff114e8c90e25c59f50e8b57511a111464234ccee58d3ee4f44f73fb9afd4d5604aa61e11d08154c45359cc1468c2c0d0e16f80a0a906c49f92174a6bf858d1b32ed959d5fefd0763852ac5f4df23d0c6ee5e093b3bce3d9a06d609a4df759a103422f27d52f702a9d7483925ab76cabfe39ea8ff885e62a9b8d13798dbeb6c450561720166d4e2cb931c11036c8681581d9520365cdbafdb2ad2b3a38879995ed8fcb0bede44b505723929b384e1aad64487dee16e8d62d1ba966cf356171de763ca7511e92e6f350f0c2d1272a0e66205de28de0fc8bf5f2622e9b5ae2f6b073af1609d241488704ca61dafa67ee7d732de6c944f825b2f124a4c6949646c80684f246888364a64ebe3ed8d21aad7771346e6b63ae009d5aa1b845a265c1162496fa638bed18398f59a1a6172c37004774405379c1064eda243d3fd5d89e509b8b859de15660b0b0bff70e58518cb106f32d1dfa887353daeafbe22db58990635e35c237de270ffbbb4d1bdfc7ad83338ca7b8def055acbab3c518b0287931836233fd99b69027263b33c9b92cfa1863b0487271c911ce18a9ca1d326e1bb6a07f59d72425e6661001ba80838ace3d9e31a45e3ab639bb8aa54528a9aba8b28e654ad476826a0132f283fd8f906796286dca6a3f378c82feba3e6b728a84614fe4a530017efbea1661a4930864093c7b6ef1d6083fc8a7b4634663a754a82988f7d17b54b7bc3d2bcd851235edf47d69e84e0016703d3a3421b959ff4fe72fa0ff16f1795b00adc2000eb3a662a89dde7f173bbd315cb9246de0d4cca4d4d39303ef1896e3c11bf8e7965e50f7913cd069edc94b47989b13e04ae3fbd8c3be2b54ae15c3994f47cb9251ecc10046a06fbc0eb793c039cf341939871e0affb358bf67ab06d99acf9ac7188bf03d967a10bd38f9b5538e56e41cad8be5176c57377561bb0db6e3b38db083ab987307fe23e5bd393dfa1e839e3f427ebd4070873c023125bcc5d71cb9eea4e741e78aadb6a66b07186156260469e552dc5ff4b47f6fe22c4ff888d6b809d473bf7c849248fafac4b5c1f6ddb92f3ad736a4be64d636fe26d72eb38fa9bdff999b39d1d42de244b12439a09fdee334079a2ff3ff70bd577ddb6fdadf32474d100dacf5500806d08c29caba04bab5281f1e5d01ad538b39a8ea94a8e2f43bc4c4a0079017b5df9e2ca10424c22269a1374e5983d36967181a97a703f52ada939ff2dc4316102eef0aaebfbcbef30056e388c465e69216dcd5d32c215f72781b71b07e7f863700c27f726d8ebc9f71bde442c7e909be84943f8438904173137ab28420cd263020003255da9d267e04b7732668549dfc74e6c25213abb3cae0037a8d16720482ced449d8675c1a1446067f8753abc1b87ffcb109d3740ea5027f8caeebd547239eb3824fd1d0c7d969e29ad7afa85216e863d753a5d15e2d38d5ad2cda648416f71b7fd4e1060330000000000000001c33d91017e2842b264f8eb8f263abae58bbf3cffc1bab7ccf867918f1e4f3a0c0120220e95875c38d7e2139bd0e006b13275c5e79991b60d86d71d0fb714daec525fc005addeafe2c7e6e566c85f7b42a03e5669d7f7775ca8573b2886c9b4db5c4d45620bbdcc350d481e79402b83b9f9ab1392e4def529dcc06b86d7d50e06ecd94e2e167cdda5c6138f3f2905027876a95dd284e847688a5670ac076696fb1d3758cc380c6fdecef9c42e0d2c33f81b39b9e132efad3581a0e3f6cadc18d949a9d3a5c40b715d3462a7ccdc62b0a04db16c5487e15349a402697a3d7ac367e1c2eea4db2afe32cebb472d95c1b582d1904354b54a7d38fda4eba9ca416f68a157bd4b336337ce9ba0dad7dfdea98001e07bad7123ed4c16b5df86a2ef65ccdabf660da5425f5f1a0dfcdcbef7d912f6d221e9e815e6dec766bc28ecae23908ceb246541fab61005633b290bcae07e680ca879e3d62e9389db886c236c324bca5a8db63c831110c751b0a0f795258cda457f4b920249ead7c624ee5aeeaee67f1baaeb33eca3bbd339ecb0cb87453906171205367e0b68700d2e44b93975727caf9644928d79dfb7f2eee6f551de021547f22e5090f9150e29b43acaa258a04f28aa9c3c8f543bcfe72278ad299c2db3194936873025c9c30af582906fa6e23d697dc397781009b5ac71e0a95d4194b6a823344da52fd8cbe5f185bd06b238061988b2381f7176cbafc140c47fefe826383fc5ca22fabc9b66f74c06662c6fa45659c0e56d454026e62241d97e089892adfb107b5bc8742ccbb90eab43120969f19cb001119bee595d6092806208558928cb37482931e2b7dbb4207af09c745509d59875e6610218abb2ca3df14802a55be197be2894ce73124cf41f91a3ba09be6b7436e6bd72f35ba37b878dd198917d9ac43619fa32775e2d8fd82eecb03795104b5d88799585d5ced9614e62fd68e16db8d6f911c3bb6a2cd6197a253f30d3f615f03b239f798901820fc78dac3e622961fca281ebc5f63e4a29d7c3117513d8c07bf7f8a74748495a1ada326410eb942110bcd7d72e367493dcd2dce59cc84fd40c87b025b4d6e40fef826cb0d5f6af2113bef4b8e400187a84ab7cc215ee52538d763f4d7715724892a4fbc29f6be31b47b47ef1280603113736f4d51c3e0df6b0b09a0b1b84b9e0e9b531d32e28b69df6504489b219a4d6d9e303b7496244b411a193b1e0095eda316df21b42089de67501607d1630019e7ee2007d4c9f3f3ab6f75d6bde6103bfbef0655a3320156cc9e17c00efecdf4cff74da63fd50d062ff051610f76f20ab2192dfacc0872bef33232260811490ed5dd9cd61e2d5512a74e05f1cd3caeacd5d3e233f936f1fb301171761c999a894c4bb274254ceb109ecdd2088b9d862aad60c28c4bcbabd9707d979c7b95043132798171ac3f48f4641230e6d9b10c12478e79130d16b155b4170f8cdcd0d80aa78b912949a51e76b9e20b669307ba2a27da7e12ee49a917deb0dbbb090fc163da993742205f20da0540aa08dad582d8a164c26731203f3531f52bf4f1a760e788131bbe3bf526864aafad0bb7d5837885ad0504f6f133259b68c71cff5784e5350f8b58e0e86ab390d943d33726438f0d23bb84d64dd969a5b2fe198608e28dff3b5e1443fcf962ddbe9cd52bcee7de9543892dc294abfbb6e2e1ca6a5b3b640fbd1141e46a6f3770dfd101244d31f70d31b2edfff3db7f473d8221a0ad9c99aae726377275b6c4e6fdc3051776c1e7010cb46687960c0650e09e054931f0b40f7b717e5ae3c25b28df31b387845ebf648b7300b612306c30d80a4c19a10e84f7149c9608d993c64b7713aa1595e101bb43b3e33ba0f188026e3d55d5f74f72eb33bb16389b19001d3ba197bad43a90850bc076475e375c4f45c9753481e0e2d9890720495cd19379b2545005d5d2030c565d01cdbbc25c57a0db7e0ff6e9104559dbbcf47cecea244102d27386f3c452d60a29e8f618a779472d819e61a8c8fb3f184e8db34b02fe51266849ec90ab99cf18e484fa2df59e9c2f69babc7a8fcb74d176ad6c5a926dbfadee8a53cac71d4bc2255b7ad6a832804782df0f23891a877269ca3fc1f43b80fbc5ec21c14f2be8342318abeff5aafef0217a04fd83ef9bdebe9d5484d99437558abf2a129300ae82375c40d6c382e10e4c33945630e1ab8bcb2e8e8bcb17d0ffe2d131b4ce10d88f99932707b4be6468dd39fc51ba9eb3ff75dcfde7b771415854f8e4b2ff0f7b1a7222b2b30be82c3ecd16022dada430e49f45bd5fc5dd0f171c5cb4785970ed5861504021300db6d5ba6376a4b9fbfb1b3b7fdf3f80dd09c2783be3e19ce0e19628c000c8c0b607b67c3b36f6a41d7f364e58ccafee04e554b8e293b609fba4cac4f9379f2c823d190bd321c0bc9aad08373f5e219daec83a27098c346ce92efb75fc863691197b89e9c3209997411bcc1348c6d96063697d9815bf466ff4c9411901bdb7918fb979ff0c942cfc687fcb8aba9e559810adbeeb60f0dbc1fcc8e2732d24882ce0f18a22f133ca9e31a7da1f32f98790dcc48a4fba217e7342586433063168e58aba3c646056cb94fec4b4594225a2b965f3c88d79893675d5293e947094717bac22aeccf8e3edecd7b9cf7df5566ab56e3010613b8d355208bd54cca7f9c749321be7544ff2db934bb2329b286e08a9deb21896e36564457d1b4a01c26a1cdb10ef41cacfd8f572c2fb749cd27a4537fe7a67a9ddec78f890d23b5d76185cdf7e57943b58f9faa90d6afbb9e93a8226bdeb9de351c7ad2149ebc33b824c952481d8f2a0f959061663d9e0d44b9b84f7b0392ac15ebac6e47137a809692bb9d765d2b03b2b425b3a011177d7eaf646b24035201500860f44514f9aeeaba7d30ce91c020b2785867000feb7b9317870287aef2acbc7367327e219ad35ac26e298c88f8a7609206a0da1f93f5ea90f2a9ab9dad1b7f745cf4e8cb2546a534f8a131289039302855673326520d93507e888c4451be70a140ef25a6a22809f92728972e8bffece5cab549b56e0daaecdafe23d9dc1ec839afc3485a0d26807b8c945da7db0989781441bdc9a961a267493ad516cc20fefd5b52ca4e56d66eb51c2efc285cde9688d27f38c5f957bb963e5cb5f9d905de7dc6d29cb03dff44c9ec0acc89ceb6e8e9b4145d1385a8cc3128e35795e9e9a84fc456f5bc766c0b555b6a60a981e3d53705b76d38c26012e21d15f448590d1890d5f6c473fac3ff4bb6088b1dcc6e0a12a9f619864a61af92011cf93fdf3c39a2a8f93098afb3414f3f3b3f1059c2f02f5e3083db3ab4085545415c372b0b3eb7c3ae492f1559f72742dd9877752747a14e2b275cc044911250173e251cafa05f261de33808a3a0f86f99b3ce1da8050292712ce67ff4ce89e09727deb9d6c2880326d17d4a404d374650b83927ab47e472377ae6700b48e0d9bff66e354482e1bf22e3c2abdd4eed3eb3fb50093764720fddf2a88ed4076d10c8be2e0abe1b16cfad4380daac7d1283ecc35b6aa4f5d8c8643a4dcfcc0646c29a9e51796d1f66e44d2b1f072238fd915ff57e4b4c02b771aa138c92a2e95dbe5857b1c420e95abbfc8cf0eef3a00f26f3b23f711fcd531f8de7ecbe9b80edc57edc5922bf82ec0f6938556ba94fb224fa3fdfabf57bc67bb2a187844b98d387c53281b30be60a52b05c3d1f888dea96d6381c54d4beba4283e698c251b381c7bf30bd24a7a8b2255b153c49f63ce88c25cea6cf0d01e5ed06e6267d0837c1c7c0b6535551395ae5ad1474f890c0173ec2bda3398963378f4116a7b8f8aecb9c1cb40650b9d1e68b2e669478240d28eda84a5a39ecd4cbe1eff397c31864aef93f836b6c44862c3398651006639530426b1efc96329a2df19384d50fb5f970ae5e8f709b121bd4f6c5d3861ad361927c015ed5be3c5ccde335aac4873dcb3bd3d6e2a503511109aba866ce501f56413aa1431fdc8383d60dec9fa2b1512c318b07357d377b1c77fbf9be17ca98a486eb586b267b14bcd57d9e01014527b84520e64de90ec7b34ae18574e58921340dcdb5a336cb2c1eb4be6f5da85eb438bf8240996a337521aee3e88ec8e7b26ace05cfb9db207d4562f7d82b014f45a83b6d2fa6a17b7fc0ae184da3113e73d9b07a4574660a193b7423c1ddecf7e461091c55cba7116a4df76476d779710570861163b3ded3a29bb4d6e253536fd47ada2c3fc6d3c05a18876767ef9693936cf11f52b3f026f7f126b0f845f01bb0b020caf22a5fd3df9f8f6eeec48dcde4d7ae98514db7baa8bbd7a5736863893d690baa2b4da1837377d1a532e474cb70d5dc6eba831d42d80e9b47cde97fc6ab7d24d0c1c3503fe048accaebeac4c1008e9e74acf01cd4ce0aea8f96ea066df63e8182428cbd2997d2d23f4c7315f6f503fe3b99322d43a0237b0936d6a7018cf9f11d4ff4d794f8cd8ed28a1f739597d1aa2e3dc9532cd77d40671c3b7f7dcff9aeb66ad94926d7a772ff02a7d0965f5346520e2f0cbf8460823091163dafdb0a73a93dce53157ec76330d0c19fa05db4ce6375b58f1cb1037e12c3d7a7cd7f6d8347fd24c86bf88929779a557c7a56d2218a8fd9de4026dcf64554176b2a5e666d0b9703ab62048bd227f42ffb1d8b74bc86662845ff510326347f53c1c012d9e1df0618b3f48e80f846d5a36e67353c6f484290d1a43488a67bd037301bfafdc3873f374acec41c620b984b722d12e3c37307d01bdefde0ab81c94cda747a000822599fdf34699f4bf03c1978aa10f9a14e99a225ec37e4acc2449aef3e7af422c5145c897cf06ce5127d65041a2685232eee613bb98741d9149e58c98b95707e5d584cac8b766028cfc369fefbafd225adc9a2d3ab9a3e941255fb57eb77d96756155b05fcda1856df92b19fedf6d1d01b47014d0cdb3cf8005b0684b43bca38b051b0925ce2584709796fa64db54f6f558601a1f825d42cd22fcd6be23b14c4c4bc58a49cb530475f8fc0dcabdbfea8f9371859fa0f820f3861eeee832c11f727b7de79d7767a907f2b0b0b38c4d4f912c9fb3bb5386efef15f516b6908d25140916591a6d504db70b0d19c0eee0293cdbe6b4de7a9088b5b12a2cb1e6ea70fd99a73275e2d4f6021e5ea0c774cfd6aa52b2f1f672e099d8db0a54ab4c7901ea4fb2a4008185eed5bbb5367a0789ad6ade9c658c05e323a7d31bd8631f56b0800acaeeaa991e939062187b81babdbca1cd35539f732904b3e19e95c9d474ee9fe5f22b04c505f36e427fa69a005fc731999a667c485e49af6ddf56e56bf0b431b871ad5d3471d1eafe6071eee3c9184ecdd7a1b5a8fae6c1e7d16923ff97df48d95c85672f75ef7f9b9a07d8640eaa080741ae93f70fddc28ed34d7fc675552523a38804318a6e3ac41555ebcd41a07eb0d851ad46be57df683e2787cf0f229868c72b65974f7693086811deddb36abe89f3c280cc87b49f1dde9041279dbb9a08572fb4504c5671e6800185903a72e604508beff834b0498a5463d196ca37675e664e8ea51a20ad8d771c9caa58a20d27735c5523f9df92b1302c83819f5af03b1f57d3804df49a8ecfe8e3615d2f6bc39b7a04e13ea22b8c14a7aa6ee86e32f3d88983247982caa158d821fa6ad0d9e452d699f6eb37f669d27fcd5c9fa8d4e4e338d43928023d938c752daa7fd628b6f3011d4bb0c39a02d8156a5a998ec15459259051f6647bcda8fad8358923c853d2a96b397e6f8f44ce6b0e0c984950b70d9d56ba71a397c9e740da8bbd17c56a4f2148bec5365afd341e0c33b08d7b18c092ed8598c0765356f848374ad2593665c0015c9ffd99268ca6aa25eaee6f4609563020003acd850bb5a3aacb206a189ce5e3f128f9cb909bf24600e03e5f24f04d904950cfac2430d533f0fb8acc2ebf8eb43c34bd65eda846d38235d473cad374fd1eb10ae9eea0318b89745d01ffb06535d38928056319575b1570d263c241a0c5ae353fd4e1060330000000000000001c6d1f400769c729e9412f8bbed33d856bd7d816cc231ad0b7c390506dbea00b4286a0c675b405358ff3a6e5a10686089f67bc8e5248fa54d05617915c96bd18a7153aadd43d91bafee548fabace1f2309b6eec4e60fc775b963d8c04f1d2559808cd52a99578633f2b22af7caf11cb42e399adcafd2ada94edf2637c777f5b2ee79582496164c0242e6e9b9b0f9855a758c5d60ea62d490a33fae4f92d58b0f4c26d250002466fbd93921c7cfd791887500e80732cf44e690d2b4de921758524e17c0f733d1bd00a200953af0a867c6e213721284a179ba2e931af6f9d75768fb9136bfb494733d86caaf2d9b5298f4018b5f6e006b9a85930f3a6f31d1aa843cc10fc98cd44fdb56d08241f5ecf41c3c1dc81ad479aac487db16fe52b9ee1312b0fcb68d756eb88427eaae3b2dbade315eeb5e740a74df2bb984003a0697b77026655352a580e17f9cd4d50a9c1a68fdaa98e5dad4e44cf58202434be6a43ec0e122c54259bd7ddf85961b4ede1a8a8ec502c27e5bbb88a3abfe39c01fff006b281ccfb0a74fe0f7db3114941dec08235ef29602d2b64908b200544215a4dfc230ed6877282de2bc99d92bbaaf35f66de32599d4173109e3594806d651b8ce7698b12e6c9c8f34bcda6890cf7074685ddc81afa17d13478fa3519849ef6aeb2335a86fa2a3a87d7d8c8398e8c7cd35effd04e47ff881f6b29264b8e94cc661b79dc9bf13b34675bb9ed8a6f22067a057625a774522f9abf1340aa2e054bf9438cee865fa046654a71271dbd67d2889f2c2bc58dd7b1b64bef9ad4c184eb03726977a154391d3547b64519be5203e758017208a823774287ce07744592f0ac6f084d14027d9e699db21fbd6c0ad24b9caff14254328683766f4c7293ae17f0bb61fb4b854a1035ca2b3d95096439fdf6d1547b560d7e0c7fffc04018bc66e84ad67fd9519d7376c083b85d4fe6fb6f8cb231d223ce5a9567c7134048740856d9a514c2166f82fea2f5d723a3a24c00f611eac96772eaf9a801f85006476355a18e145900d56a57e455221ccbc7d4789b0a19be91c1ea9ed8ad838baa1ae4eccccbaa2f128d8471aad10b1dcb45d7aa912e953875928cde1ea5d98fff76a2af3d2ab8afc6c11feae0f9628008fef54e9c0200c12680ed2b7aa269c6afbfe02dcfa8e70c130cdf48138ec68c2c95e9d0f6f2cd0139d0fd7bf220d91d2a3c02e6095648c74acca5ef2fe7f261408fdb814bd0506ef84a77713c180c89508e301813e2b427180945732077bf0c88fb3cda7d689d7d83a72de4599fe674a1bcc54ff6bfb44e2899d45ed106ed9f10ec56e3b62a23f85d75b9e6e5f3d8415f47cd572fb09769574a95f299249ea9eb0a4adc21ed7c0b82ad677d78ce3cdb89a66f15b8ffbf9aa43f19a270687ba9419091074d8f5a700ba72c964f729e4fe4ef3ebcda0ce4797c74f9f8583b72c709ef2058c8a45ec091386ce9de3beec55a2f5131f8a58cf44159dca3850eaaa14d8a57c9cd1329c76f1ea36df0b761cbe3ea627e1808c8743ba3af7dc7f14b75974b5c7f1c5b8787a723abd7f591e3484064403b5541b70059753e3e6719e4076e19263c3ec7bb2865c605e3329b792fcf70ad2280491ca0ff3ebce0b9f3d4612dee6eb8a9bb5eb2fba17975b31ea0837a152f87575784e0406c6ec0b8f53c0b346ebd9f913bf9c0b20f0f5d217e2cb333da4056e5aab642bec8ce4392ed54aa82bf91663f32891f54d2ca6ec1b8aeab4bc5d8b7fba4c47a5988a799fec72ae6393119833936d41729b81a7b90c78a5144b5ec45fb7029430644533e5e330d812a9d79534cb3e3fbd13ad05375436a898076e8ca4316dbeb92e9bc3e0d1e2d9f7f8561e86302f857267c7eb09d23950aebfb05487785c0114088fd434f6585fee9373b92ccc7ff6fe8f6b6542593e7c477aa7eacb55fb4971c8fff5ec64cd2731158795a6722d73f3829628ec9fe640f101f6d1c8e0fe4392693f15db42765d34bd2e28679aa211712ab4f191bd78f52a595a2642735beb6084990bfddb5cb36e1620289357cb019e89584b9c8c01e88eae15b3cba7d4d028ad9d49e8ef699be45714ffdd556155a6a03b85a67c3776444fe68a19d872c50803e83b1c09d0ba69b8255e52bd53a0423652e0d7d3071bfa16bc44b52cef8edbea817598b6673f3e36a58b22fb82c1a03a6ee828edcf2464c1d85d08f2d706f4423971edba40410f2474110e42c6b18dc733c34edb1a68019183754de85664f2f878da0d76c20558fb7986eb6b06c70dfd2d0adc9372080d367d7aa1fb2a1a2acd51ed374d71e0641b0bd32089b74ff758486fcddc0cfcf70c686f24e20f381072f992b55a9626f566e2938fa5da6904cb7188a9d53cfa6944ea012ade3ec02e6ce66529134f9c309ee23e1ac903c3d3c51c1dc25da18df7234683d341ba807928a159370b0204848eda4d1787c70268d7e5c87d879c9a51d122a174e2a6a758117c15be702d9867ed991b51974db2c7c68899a8144f73689211b1d0c54a96b9eb64f1d377e57201acb9a004a423812d1d94743ccd4d29373090649c70b90455fdb6131b09e59a81661ac495af3abc2e6f12ff4f43ba3ff070786f8fb6a0d5b0a3b30a0c45b9500c28314507355a2b4ff4197722500303a6fd7a179e6fa26ab1c2fe45ce3fdd9456dec5a5b227d2a0fc41cb74ef24d27dd73621cc2bd8d844bcc81c1e7e2437b04a4c4ec67d071ba278418e69ce131dc85f435ba5a86d0b62b730b0bff76464d5f4fec6b9b74562fd9f21f02aff6a039f7304da2560edb7f8b59447b197b9f9bc894b28126a02dedd608d10f0d4ac601cb2f047b9cf7081921a4d0e3f62402382f2e453adef81582af909fc34d2f2ee10c663f96b7dee12b9ca9c339c2bf17f739d3a3637b285450ef98daaaf551f7530ce1241cb30262c1b0267f3aa0f6361aff2808c05206025790e3cb1d42562cd759f4ea7a82b223b2f38d483af7625b4b6fd38b256b06fc8a856a6ac0271face148ba8a3ee83e751f7ffb434555ddf7894f3adabfd7ae5479e5b48a64a449d8adfed028fcfcf1371f3116a6ee67cf7f8c33d51565b0ee2f051ad3df182b79f3aabf4ee8574aaaaf934604c5571eae19e61165f6fb5a76f6acca86e112fcf923f0e5a0553bac81c27bcc2a9e5511ddfbbca4774c7e874f1deecc83341bce587395999ffbd6ac93e4ee6911de9f39c5f2e3687b89e18d98952e784369919c8563edec90d898071abe66bb2f273ea669e9b97955e358db56d5dfa23bf4160f2eb43646eddfc902cf1692d9078d30394ef3a42def26d8299125b20991f588357e1ae360c700d5bacb413d4cbee5043e3d2a6e73dec751f92a8b7fe70ca957e2f86b50839af1aac2ff10a952e0995ac26b38836145c6f1441864510575e38eac0efdb3f6ec6e9db3d18916130e5133681bd5ee2cba340f3f53c597fb76e2fb21947017183000ae142835d1a0fd9fbb7466043558605eba888f686ece4d1904f87daa0b91af687b6c6d5e0ace468dbf9d0402bf13f4d6a907e0d96dfa4a41183a3c5771092bcdcceb139558e5f9d47bf4a84f9ba49a753cc9ef8ef211b4e59cab5edd0d4315eee63a5ba1e7dff70b13384417128719b4804f336d2e712862b8401177b378045b97098f6c16cf342a7950e4f552498520c96410437eccd0cbdbc0111180d173af9ad1bdb37798dddb85ede23122a9bd8cdf6a2a6d559a972700418cbec04fdf21242bd98981e0782c8881af0875ceb0363855772f72b4d53e74ef03673e38dd225c9db9e36c596b0264eb91e8531a140e55255394f71c3a37ff9e6d3e77953ae0a7732a49b8ee94397832be9a07f8a1ab9b818f3de081e9b8ad27b9ff7bc84fbe1f5af0796ea813d987a4adec03dc1bda93421d6e746e43cdba796362cc2340db723c2196c366c85d050fb2fd89ea1bd9fc2c28f3345d0e4c867afeeeeff83d2f444ede07522ba5ab363c394b609050868921a1f3f8e3081338e41a931600a8b94e278af651e5ed06f0368b92931c1c6a2c6725cb123b7e25e017eea8284a853319ae1be9a53fc275a30eb5128277fb7a32d10c9a5fcc6dcde0043236f493378417e32e8a7e51bd18dcac112876ac4e092c91ff50607de692696acd608991a3ccda0920b26b1ee174c663bfdc48da1c0164e072ff278a566b5c5df52aabbc82ad5c77190e5da94b58b2b54e734198fc5901e96a537298b1735e235855c18ffc8c155f92c4b2879ac9462455eeca7a6b5867912c80d780c62f39f7712979b70ff0fcdcd7901cab5ff031f140369fcb050a848d5c69f5e11b4532f5a6cea261c2a05382d788a86bd913c554a2bbd8e9725119c01254e78766ac7f619326624b7cd8760ef6adcb2dd45ea1051a59259a14fd04ffa56889e3d53cb37e03699c1fec8711ef8d882a9cc068101cd6bdf96300c5787ae31c42121895782b6d8610e82acbbe66d69545ee1fd7fd5e6b5762358faa2da13b8204eba367f2a511c6f471f7d5a006a6cd9b808f58deac53b49f13329e83581e42b5298caa26e557374466d0cd561df711545d875dd75f89df953694b3a9733e9e0a6f82c4e6238502edae435b3e0e95841eb4c9a23094e1cc2385cec6b160d59948245eae04b3cce02be4a8104a22ba509a262d0d85748f6da2a9b7403211ca0966f31f91fe00296cdb43bd7b75cd07628c5dde7e5fb4baa5b10113b35810e92037328d127713b77285b8650ff8ac3d568a67a7aa0d544db113bed6f79aa025a98a3dea6ad4c8cca5dff409c988e781961781b223c1428930e3ad755d12cb8add748b5044c9310f936c8a9c14971628b14dc19431a5491ca0c6b38b9463ed957034ebffedd8f98732ab2fdc5e1d4bf1cc74ee24eec7237b3d74f1f878153621304773326209ad322cbd11429c8f1826c10e01ba4b0efbd61cfdb4dc8e5817c1fe86756a92eb063230fc864c7dba5e599fc401327d930846006009635495b9445f934e88606ff18e0e0e679c79dbeaaf25801d4a0a79e8701307dcd8285186c93c4f1890be402bef66d61635f4edb7a6c694429a7d820fb98612e3b1549a66cb0b4895c372465c660760dd402a87b4157be6fb889c390bed5e2e189a134ff6a7dd34766b89f0cce3e4869189453c94c67fbde2b5e9b84e3076a0a3c20d0627f70e24f3c91619163c43f31cdad8329b1c125480faea02e3bc08b7ce061eb3243707c83488359e94c216edde71c0b793025e1d3f85a0405ba30be52693d2d0748b8209d6dd99db3bc941b30321d35be533fc71ef2be1cadcbe5ec0377c44fbd34eab71f76e49055f3b1aad33f439a7cacfed87e76ad350fa599183f8f254ba395c3c03c62a235be942b11c22d45ba975443ff208a7d0f412d6cb6e1a47cda5f05c0a85d63ebf5d31f1a3cf6998d79fb06078c9e6dd29d7fc7a875fb85bab2ad7ab4a96f557e99f9f72c41ace596c31060a8e55dc34081f8570919a798e87e298fe5b5191f484e8ca3ebf0620490dfa2fc7676c32b8e620ea64ca47872bcf146f4a0e1baa6225740c4b6312a7533c5779730dcf86be297347f5bac60d4dcb3943bf7959dcbbd1b5f624a85e5bcc2c8785282d41cfab97543474c5a68ea5566be51cd340227ac5a6a3711d17c5d80b9657480b2f383ecdc7d1124ef2a77aed673e97b995d4077bf2bdaf0170f966a17eb69ca5030b2e4927996a6d48022a0fda8ebb439e0fdcbd0c69043ef835f5376f9a8372a2e2c41111f06ea7d06337ee9b408265672d2eabf80348b9ccce61fcd6fff7d655587011f6443eefcebbd3be579c3096e9536783ee12b6905b96dd11dd2e168b30da945fd62799db7cbf142aa4b8338b88ef9000fabcee1b3ad1aab57824875bede4906302000360ec3a4bb9a65be0cb2689be7fcaade68d250b9e1ec020fdf9d9497578e339b90a1a2b3f4f0a11419b95e64926695640a5fcde9190b20f2fe11303179dc03e45df098b231fbbffdf51ea820b07947d09b6d1c509b8571b42383ba5afeb3f1629fd4e106033000000000000000143a5b5019c60be7e16be5506d11d319f3f42b059216bf5e3d1a56e67ba1171c47e3d5276c7f540c5698540a1dad230661e2c5c6cd43f6936bb772432589c621a0d1d10b83045595814bf1bb84471e2279fb4e661bf97c4eaa08083c55bb5d26e9315efb5966f0c152d3dbfb06f81b06b6b6adeb00053038b8230cdd74014f3c69b6d9b269b0b3eed4020abbc457e1c4b11a425dd27000e5dc44493c59bddeccb298d2de50e3328dda9b079d8fe72ced033fed61ea41eb3ebddf09efa0c64ba983817540a4b5faa864508aa989708897858ca98c027c80b152b4bcb34afec416a63521e3fd8c88be607c3bd6229874250e00454d20509eb30fd50bbf03423fa7681c449c347ceb1be34994290a04e38d186f54f37aa829eb1cbc295888855287e4ab9bf0e70cffb3cd6768ff80bb6889353ab2b6253241e2842e4c10a86a0f1f26dcbe6d586a9352f8ef8440c4ef2ef3d09dc3aff80d8f5e6ad2cbbc6682bd974a3733deaed2a2b7906cc6b96b6b08e2868a9b13deb4c9c748cb45861f0829f819b8432911c60d456ac89af653a3f0c23a776846fcf1fc2c7f2b9492c261d73ef1963ca66443af1056941b0b9d72640af3f7f1ecd28f7e25385b4cf002a6d7848451d13b1feb65e217ba274ba0b95b688225b0c54b63e6e3ee006886f9896e5b89d1bf97f0d8b00e6f4d1927b957ebbadb353785a9424bf007b26f84af99fc20cb9f3dbff54595f33f4a1fe815d554269bb3aa35342033b00118d3320cbcb82f7b3d0c6f4d2bfc99a71d51eb14f15dddb82a553cf1f56f70eb7fdfb58dc1b9a409a9c33345b207e5c0f7807f25b00a6a4ca6979b06c02bf90069e1087a6aaf571a1df8c64fe5168932b0e964cc4dda69c664ef58b9040ce16ebd084830d5eaf973f846400e82ef3a711f95ddf35705128adddb3e7563c5fa898c138b9045e7a6813584fd3b48041d80410b115ede1c378240a44c36cf8057b16ba632b40c425d0e564d7f92716221ed99553a7d270a73dae65d3c55d07b9f8f1ea06c70eec8f6434bec0d02af0500e27ce371aeb77f441e10ddcf85040b42243006c232d4c816eb54ae61a24c92dce20a470cb4f1deeaff8ac5f6bed8ccde8a7b23b3f467f0786ff5439d862f5f151d182e11c3d4dc983774930b7cba8048fc04b71bc3c088f83c3354de258de13d02ec29b2eae91c61a831c5c487957da505f1031e5b4986ad98c955d95d4bf8eceed15d42784ce11e9fb6958c5e84d4518a8ad4b0760a13721ec99ab1cbf307053c939a7600cf7263185669051936c0f1a2099b0b7bb1cbcacd726df96cf6de3cbdb2246b28a14f67d6b802d733434b968274cecd4229b52b826967a8f8c97ac6323a1d327ba13a7e27be5a3b807f967e3da2f12466e51716a1863604a2c53c3471cce67f8ac0e816aaaa25aa48ac5f9ea76f825546e21cb7210c3cd11844bc4db9b037deaab1368f50781bbd245e62c1f1eda71ef8024fd7e23d17933bdd9b4056586ceefe53a108c75b64808e7d1d6b144d9cbae3637c2b3fea653ecfebfeec133306df5a29339d09836c26b891bf4881ff3e889b850629bb8ff0beb0b505e24fbfe094202954f4a2c48ed08d5c286194ca8fb0fd127d093d2959b049e0618f193d4c5c46f31c5a0ae0f79943029568cb2192393f7105922a272f2d8d1e2754277d52b942fdd272b464bdcdca69148d06e79b07ef773cffe8589ff515844065effda63015d2c66333127a7333f8abaf4a389c5b0aaadcce720d6aacb1c7cfac2964f922c21bda4be69d56c7cbc2de6c56dd2c60bb8dbc98a8d024d26373a694bcddce56c91d63bc2cd17b1899244f34a582c5c9cf81c27fb3cfed8be054846b4292db964bd3b269f9a4cb95f75ffa8a29fd4c0fa44e3d8bb5324da5a0b10b5057fe02e5e0d96e41bb9ef3700da3e9bb84f08a0e4b3e01cf41dd545e4635082cc350bf87bff8cc50dc51ecd447e488b6deff26069a3a1b6494074bbdf968b6435120338671ddc687cdd9506afdc3cf9cfe5c8075d31491b31f2017a08b3dfdecb61c21cf1121bed0292ff90730e31e8fa5317e321ea3821f56ff22f024b8cc836842c349ab98a7aa8dc94367fdc802fe2fdd2fde334b6d81b5be66bd69e211f940c442972e54c2f159b668841658e20d96e82701242b3ef3de9d311becd9fa1bddeb61f42a3fab879d63fe424ddccedf6dc63e647e83028d5a530c7797fdb2d6fc35c8c95dedec86db5cc3d8e4a2c13c89fb46232431d2afd0953d5104c83a5c03eb9b5d08ac9b25a788cd24df846ceddc6cb1e1051dea31c5a6c3b147f6a47e44ca9853f593e70e41405343ab223d8692a8fb6156ca2e87bf069350ef89795274f59b1f2315cb7cf503f07fa30815e7e4906ed62cf706c9d1ae0c7816155517a45581f046129af7958049a5a528e16bc5faccb1dd73ff17cccc0f74dc7d375d72f6759812fdd974ac0e96b87c0175b3814aa2efb2106449d22022473663bd40397942ea5b2e3577962b2ece7c44b7dbb5cdc14a2c33f7434149abab2334c8f7ee758badfbd5dd53b172d7ac395d820fcec3814dae32540b54daa3ee30c6ecb945298582a46854c7705a22312f128cf3c1a886245b832e47ac6dd3f07b1911d857407b6f3997b5ea83651a4cf247eab9c8782266a16ec35785d48dc46822ae17dd4bca33d6d38e6a4178733716d49320cb1686c28cecc79c9fe311a1866fd062462549a7ab99418c4eb1a4b99a3766d2770aedc61a9b406b7bec6483efb0b48c3a8b9b95c3e45b9bd4cce770bc40e627567b4e0fe1a4238debae657db1b3f5e41202b41bad91e54a16a1925563a898534f60b74dcad2a053fb2c5a1e1272b83cebddef2cef1dfb98129ce4748e754b7a573f9397b07c2be2d531759e0f7df84fd3b5b076d052c3569021bd22411b3130c432a830be73df7d80743ff0c1547e2e2b120be519884b06550dffc554a08961b79fabc91d5fcac7b98ba98df4f937116e5fb1003131504d3debaf37398db9e09ad4628d586c24e5715c974fc69d092e3e1e4e960c2cc9372f8d6de652fd34a23fe1f02ccf4ef3d5b75782a010f59ff7c950f8e423b0d388380bfff750c13b68913c1841e2029b9a35e5f0daa9f80ca55940ef8121383af699f12f1c540ef3827ed6f985690bc2a1dad4d9746978d423982a1602069ac60c32540355c4edcaa525d6e51979f9fab4582d8e579cc9aff0b9be31217757fd378749189be44f75d48fab4ff3749e8d07fca74ad86411dec45e0b820120e6c4d5d38d332d8617ed439598264e3be7a5d033fa7f3598ec0f309afef0ef8ffbc3f0826b4e1e03f73b4fb9bef889bacf5bdc3bf8f570d6899ef72801283f517bc6677b338633a79de18491912eaf630c89fc1e9ac42eb1fdb81ff6bbc1a0a7e148ec473de69d22b24a061e1526fad4a979af458d959de9aac53774f07a2d274d87bd7a26d93ffd623245fe00f5bc26e40479ee3af683c2e0aefebc3115f47de8a087605714c29876e9797004571c52dcb4ac395d1b973efe6fde5d1bea686e85d661af0436c3fd7d0d31b94794dcf20cea5b32329ffdafb864015f7fce8d5ee30f6fbf69fbf763e2fd98d3d636c4b182320fbb64ce7ae4ca52a554220607f1832502cab93ccdd2deb267e19977fb4030e2908d5792fd9540c8eaf822c2711d2f9d0d19348f43ce422043ea54cc3d9feac90b1b5a51fe996e3d33a62b9278c6fb3090a2326047c8a8d977615b0011f735c983c2966c506dd8a61c00c32b1cf939aa064d1b4d0bac6176c534fdb06e3a32fb9fe613add69aa2cfd385cd800faa2256458fbddeff833dd118e5a589d52f0f7e3ce12d9ec317131c64f4ccc907742a4546ad7c0beda679b215db0d4b37e8eeb123554333c7ed1a0f2456d627e34bc19f5ac6a42538f5c482de636031e23f14f6eb28133fc8529a395af6365668aa871f06a955d5305a24bc4c13f43daaff426ffb75f50bbcc494247503d96a6734f8a897cfe2d54d2be208f8790e0e8934c6c798e4d07c85fa0a080e2246242223e12b75d1389e4c789b28241d396c43f160df74c8f44291bb5de76078fe44a5210cd2a4b02235cb0801dba2a781037014f4d3c86be3839c5704110be21f43a7a90a0d2819620c7a501e96f1540460cba9319321100bdd0b023af3518e0e5e675f82832914f8d20dc8e9140a4a86b07cbebeefda16c5e2b97bfc719a97b8c1000c549d9585b3ab7ce68cd04e34f51df77c1da1ce3f5250b4ca8fac4bd906e24acc3b60fca8fdaf82d674f7069befb6c7027d9d052fa74add353316f3b50b24d304ba35b20bea481c4ed41d5d5e4714d3fd5f96601a6dc404cef76638edc84be11e84f2dadf3fceceaf92559d5309f116c708ae62cec72fc62f65c539ebf5929c55ab5416b7e037f2cc3b24fd4dc53d65b3b8c0bbc7e81909840375f9b4bc3e0d0a21fd4eb9f777a5a09272c68f74d645d8075afde7c3e3d533e4fd69d375794566f5fc6ab4a8922db31b9300f831b819cb0696dfb248c59e6580b4205fa014aad8a9a1c66cbce92f6236a4e0799840fd5da4dc0f9ce05a873504a75832a7653db84d5aca9f87584f1a4a8b6547c7dc00b81edf426d5cb568d520245a529ee1d772835f44e30d9fe3239dabed7bf6b61bfd4b1add4ade5a51ff6983fd51a66218676465486f02f7df41e400d49ad28abd31fa9ccd1f57629a029f9bed07c2a04866561de5beeb18a7992e82dd2fde42fe38b24af2b3139d4df1e5153f411ed1e848b9c2eb1cd919676eda3f3d7d646bd5cb73331e5ec39c83a16b8942069abcd5b85c2a767b5c0813b90035cf8080b8f999c23b30ce2ae2ca6e96af87e1e5f95eeda75e29cca622e2709ba4ef62a2a4853f6a063d058306b208d8996d8005069f832826f1aaba9e23745a61ff442f0d4567c076ecb491739363f42498e7178f042812a1c02e89ea5f4529009981e913a3207afebdb7ef23cdd28c79af6e68c199b765b825294d8c2bd577bc944bc01e305b36c5e245fea06a0c00a6c3d1600ad0f9bea27449c7e06f87034284ac24a9be38e9d73460089ff970d961bee12023e4effe1ebba183e18b6a6c1b8373879315f208d5123b4f9f5d491a3c235fadf1a8f26a103d56974f62d17dc7151dafc175bc4c34b2f1dbb80e029b721249145e7b1c3a1041b631248c3f411df7e0cb2f27e5e6eaf877639ea1e90b2e5661e80f90ad174e8e61c43ba25175f930e2a94974f3143f59a4617edf78657247c152aae09c56d02e4752c306c4a5837d2f038852b2ebb2d97047a3297141d70d617a7fdcc075e644a3332a8077487e9dcb1f056ec08f8571bd1664a432bceb7c90dc8c7179aedabead80a3eef589ee32fef3de6f62431962eb71098442819adee0e030b0e2a932eaf6424d3e21a2b0406bc144c98dc3d43f9c3d997a20db1c0e961dbfa33c820cc10f9473720e409a16291dd508e8f598c1cb008421e2859e5a69206037109e0282f20981b4653433e36637b52822513b3fd73573245fcf07e54f16a7fcfce5b3315f1945592bac6b504e67e0f396372fa19a7ad1a18ebff55601919c24568722057268dc9fbd615e257d04aceab8c4a759b1845e746de4b6685c8311a33aadd1e42b0e5511defdb279dc376d58206341894d445ebb2332c1f67a8a91da91edaec0c481e77b3e2e19dfbb45912fb8975fad75ea5ef2fcb6603f0ea84412e1cbbf0515958af9e23a7a9e34ecc2eefcfafdf0117270e566b4b9f88cdca7723b7795060bbb1484bb5c2a136d275bffd3b98b9e6fe14073d1fbcb31bf00b984dbc2b5835d953ed72551bf8249bb79d873c1d06e296e40a2b9a818bc99156ccfd403fc605299b6fda4de8f372f42da3f268d94e21717817bd63020003820741c5e301402efa10931ad58263623b77ea178607abd7c1e4ecf6f286e2c0e208fefe43be567d4d845d1a35ed6b3f74f52df3bc46037c95e06944f66c7732db210cb3fa8d4cbd3bbb79434dee4609796b828840f017c3d229715e6a76cb70fd4e10603300000000000000017796c600f697d8c4a11a172c989dd0726e0411b329dda4e1f67ee73edb3327f669a91cd3e3977c66de03a2785c4a864ac38363bba4d4f5c86ca5d965fac5e949b8a8b64d6a58ad4dc3981f5e053d76c1f6b90c910da6b1e5adc468e0fd8eb81c101d521808fcb5c21b386ea9997d3a5aa796331b14d078a3063c21a58ba58da1f80b9a9d31b0392954d8c85d11e629f38906068e46e71892454507ee2f00031c0380b6a2809fa8947187406d77533754a52aac97a109f290ce47ffe213c824e4a306e6aac2f6a832e7585b12e0c50d58628a7cc37e6b6dbc23b5b905dd5971880a6b01fb778eb1ebd00123a289251e77c6faa93690852cad09ac0f2d8fa5b482f3e07f8ec79afcb1ee68f676c69b9e22894f57807c71911f0e0124c3c2beb98e2edd743e5e62aaf7999026e6be54b5ca6d7e65c0439e13c0d16588e1185f6df325154d84a25e97cd1fec156d9c8c267cb5601dcde096353c63ba9fbf0454d18bd7d299ef22e7cfea546f4eb20320cf68e73537ebf701391573bbac7d9d74ad2ee7156a2ee9a04dbca6069fa7be0477e58e92b961005f3628da991a9f690ff774dc1d8a714ba594d4e4950f8e95e965df837af9b83ad745ae6c70b08aa96ee1528bfddec4edf627bf36bc3d78d119180dcef02fd635ed6576d136505003aa23a7602f34fdd02a4474a56b9c3c3057fa820962c25f4c8265e133a1cfb18ed803e1c6a12ca9f07145b92a7db233a47fde30990dc85618627ede45b345411333e9af3426edc4248ffc037694f4435f0a98f87f0bf74b8c0e9be583ed6e4817d3fb2aff0e6af55f6c6a700270d0eb504abfa9b840139b3023c38d047450e4646c3d9a588e6b2c02e1e9ad26be13fc96fe8e256579b7fc18ac9e3450966e6d70cc41dffd415fe7d9bb2e3397e91d3799a2e0a08fa1b42ea29d8a1476c6fc0f72600884dea3af729edb85823859a37ce82bca5ba4c0da6b34eae8e3814d5950d53da109731e83cc374c65a96d3b91491d0f89d99542afaccdff18e18a42cebea88894dee94977f88279af63fe9db3910116333dc070315cd6b068e6fc946fc0155a77ac8a5f134b64a285ac1fbcd87f410497ef6fd38425720688edcdce1b6f7ff799e9e65311bdd7a4c278e266dec5b582839752ec43307f9207a6beb16283f36746c63b5629eb0791c0afc1179845f4efb1c01d655064cab995d260636354b97c9ff69c1d3aca2badb65a6de564612c0f672362d0d7ff5e18352e9c009b60f419668bd3df480ab7a5f8b2e2bb950416701289052722ba205302e35e5b31527f00a623190b99e742f2be808fdc5cd87f7121121a8a21da5066a32055803aa6b9a91e0773efc94a86ca8b5535d1dbf80d1f595295b9750745975e870dbed07e0e6047c5a98d9281bf63f3b3a29212499e7c672917b66df39790d3d80e9219f22726eb410837449888eefcf64c37f7d97a849177f54692d93be39a9d9cae16c6b4be0444fb578855ac8e97e6aa6e07688572fe62abd6b2dc983ddb89920070cd1ac11d1b27d3aa7edf432d381a4608a845c04e5f25ce62985fef04734222fa4ac2d4919af9c1efac312e9296ddddff61ee57d2ae9b2da5287a9a50447d21d5c8850580a37402d9126c17daea16122328d2b013fc6dbc20e0848c2eedcbda70d6dc9b95ededb65e5baecab25bab57acccecde87bf08eba8ab72c80cd23721e921a01a653d075e6adaebe97fa3e21f6ca6774effdfc6edbeb2b76420a46f8fac9bf84ca7f1a19af5be59c4be42db1161f741c800d7bce42c7b3949a0172105ab158e79df0bc307c61f330604e6a33152707f5ba55c60f8f9730316c4959c710f5e21e2235105aa819345955543873f3d001e9742eec59d822419976025210509fe9e8e64e4ccc8e5ac5b8ddf0085cf0c07eefa03c269c9c6c919c1621de90e990efaa98874603b5c2f70b096ac2ec024d8f3a87b51bd3e65195af60253d8145e79c17d04bbe9a460e7cf9fa6771b7f0bd9cf7bc04d1513b5872bf3f62e3863c84c060703a920747e9aa8e87f16003dddb3802c7267490069f78a6822bbbec6bb01ec0c65180578beee51f97b4d381611a94d5804c91a4dd65d21b3429d70372746f36e7f77c3d029964e913db5af8c18ac532400c7e2ffb6006de7dc64d473d645bc47318db9c232d07bdf514fccffdd8ac7862c5f5f0d756d0f3924dfef6e9f98345974056212d258340bb5837ae9ea4503fb0fd3db8977f49e3b12f8e47b883b29139362b5589a18139ef9a2526d4ac0fd81e2f8a0e4b9f0265eec3f2471c9ff93ede0f26794720235357ccd806ee1288bf2e0f5ff77e8e210c5c3f90b038db1f6aab826008155171995aa51eb17289dd08a531ba7b069731d22be14cc3f613911ff75748e3ad4f467e9f7d9256e8a31c8a6e89a1f548d81f23e394fce754811ca6522c03b438f23c324fb22e88d5fc3314f87480b75ab7c95368c4e34aeb6983d02d07cc9f6f6849ffe37121349637d4358237fdd4db015c069b3d535ad534deeb4605f4c4a7b17a8a5d9f3b4ad8d4853a6d5b7ce61aa84d0141d3c0122f5579178067e5c98d12ecb737f2595d37838057de67c825c72e56c06cdf43d907b808ab8c8f3428ac60d37b33dba3937e5b4cace5f8fe87e45f12abc3cb74c2d89a167afed25cfc44cb74eeeb2c91903774a2859de55b0ae25e1428f7af8731d66aa6af0cceaf50bbbfb4c20297eb385b3d74d2cbc3f4c251b5cd8fc458fb824eb27304248e46c9b7ea15cae4eeb9e2615e29cf30f910b79f6899fddde79daf32740083328f48194e03d8ae2a15a7383deff1cd45479daac780140315cee4bcf017b1ecded8a976701f508379187768eaa6fb6b91e6d640a907cf1d9b0639b51783fd9dda7ba50a887b09219ab52f008cc80ca60bf291f2e8acb7c54c149d532c608fcd617ba94a2fa3d8c1088fc3ccf7115156f80e7317a5d539b86344a89e155c112f6519863e37ff9319133b09802d571e99e41b9be76400bfe5adb3977cf678652225459e408483781f23c9f67e3aa07c4824cf41be8befc9ad95fb50073bedf0ae34668e158190c25631edb2cdbf85c9660c896e1246d11afcc633babbcdb8e66f367c8f424ec06000427538afd14a1222e152cf1f0dead40ca554131654eb4e841f709950835c238a51ad8e43bf7b85fa2a2ef40750417213c61f6d7c0bef8646257c9d527a00bbbf2d1abbcdb662e26a58e6e149f6fe01bb113aaad643dfeaf9dc39338ee4481f82adabab672100d1ec19fb0b0367fce44b8ad4f0e186d470d569a8fdd76effaf4c1490bb144897ac5898bda12ce161de69db2e18293b65b1afb26cd211e7ee7bffb915663ab80f4a990fe74d49e0b222b675e78fde40b4224e62bc4c3d08dcdbcf5f7ff93cc687e847f15402447edd303ac802e72026db8d14e530d29fac06ab3100054ad578ac1aa0b1f3a6696dd28d280b8f6b8cd1399eedf377b1bba9dfbb1ff2c2664e2f7b83de7465d6ca3c2d1e9d33d9857b1819bdf2b99c88fb212e6755ae6990760ed8700c858f245c3e37cc5cb3ac338fdaec8ff8c38097b0038bc9a9222a5b26fab9de55919f8fc1bb96eaea294c6fc7becd37a7ecb8bee4a083c0f8c721cc01b79c3aec3f871360c610480664a4074a0144a6047d86fe35c2948e4a94eeaf443f47aee41df7da0852aa743523600c80b22cb4a011fd546534be4ff95d831f1e1ec5d4a425a94f3a34d35157604dbba6bdd569e51783fef17a522a05e1a7170caa0872267a933843a9afd1b8569d72aaff5fbb29f2b9bae7995b40f52d86d524eeaeed1f847bc42333f602730b71b715d15cec39f954cf86a1556ad3a73e874500b90ad63f7229d9026ff1643a3a490bd44f26639e017b3f3996776cd28f9ec980443166472d55986ee8a600236dc02cf0c5b9579158c7a8313f67794912577dd806d7420910620cb8693711f73e63056c9deed8678a14eaca1a5a6dd338231dd13199560d1c6152935a5e7d7ae067e20bf3f53c0174f2eae167f2110ffcf7e783f4b15e8a66db78336f177315e252534fd041a34a05f4390f8736bffcb92a9cefe45b569dd0a189838e3857981baeceba82b08dc212e6db4cf45150cc8ce9104e6209c20641ad7eb80bacd2dc674697fb0225d545b8fa50b7652a91ab819cee4f30328b352f4b69ee304f2318f58aa692b0dbe333561c11d0c5fca8f1030a6df6bcb5343cebf7d2da20eb4238b83da5b3b4df6b9af984597a95a9a09bf21af0bee4b0781d90bbd10b6bd4d0e061eb786acfb623e6e4d767d30e810740d888df523cf6c8f52c7b25b06619e7baa3fc510414cc19b33ccf080dc964f858cb359b62d9acdc0c07498668ab6567f70a72422736bb5aacc3cc01a1721610340b1cb6ff2e5d2bc9dbd34afd4f299e3a3603ec1e05f8a5961ccffa77c2fd974f3046bedc0c555ab58e61caf48426082e041363984ef8002730c9d4fa0f30c019d03856e874733cf89a6729822bb6bc2451fe93363eea57c3b89dfbd0b2b12144ab0736e271574a1a0c4adf31131da26df06b74b415c2cdf4b4120c845a03a211c5dcf1812632600f7dbbf2516537714ec439b1fc6f10a0d76221419d972356d3f023535040c0f99a8f8bec8b0e2bf7d8a205f1ff0b1a1891916ce8bb1832e9e407d740310f6c86be8ec87896f867dd1070fcf8ff8e75dfafea01cebe2fcedbacdb4465129062b8c816f0ac32520afe3bbdeaa9e7e18f2493d028ad4c20d135fb8f2f33d2431d2e94589ce92c6a3e7ae96657a3e2154e80c9fddb67c5eb3538ebceeaf25d893ab767f780a41d5c6e8503b15bbb1b4acff771ecf0a0c70dc40e7ae0db703aa64265a3e291ec61b407c2b0f97ddf08d3596bfd7b1e034cd320085e72ac6ae28dedfe05c086d3ec8a8bf05d970639687b23de5c66951e0083674466df4281360fb41e4b4fe229fed506cb85982a6034c4ea9da788cea969659c33297e4da63bb638fa5f87e0ecf733d7d747167a2e48bbeaefd738cfacf2e7e6e691166dea0070db65646eeab651224188e38f1ad9175cec9de24a804e4cc6210ece8a3f09eb6182a184cc8eed6cb927488106f574caf99dedcd89cb964396cb7842c346c99b9afe99138d416d751f39dbf4c222bbbca0cd39d591321e8417f5327755fac4c193c1e2adc1da79861d21c3f71d58251df8b823b0314e0e3c8faee7d7354ceca4fbb0b03e0899b2d1dae292cf8b6dec76404d0b9b0a521a272d98e77d7c9582c607ce71b960c0387587ade2170ce31e7214da35b6e4260ab1d89d11ead8df7379f874cfb73893120efd5cd18d05b50ea11332922c501b94eae971b388c701bb0d1d22358a6ceea2e15d1494e9227dc21e424fa14b1852329282fa5abeabbd3e331ff7f09f510322e33268e6e2d39a8856c726a735230027cf38fb3bd35f771d7dc0c41ffd3d7913e27f055956c10611653685c2626a0ad4f21e0117669d9c462f5cfc941c1950f1d990144bd30084dcfbc6bb415f9913915a1830f4ce99af13dc7c74ac30df4b386b20a7cbea46633186b2d46e83e25119b9a8a628fb856e13ebbcd648744e767866d6e8418c616ff18e798b530b2bdbf88f9e4bd400aa3cc8d46f31a763eaecf43affa3fd241540bc7af39aa147303dab8f4d639d16260a4d95e5401577bc6cd1ab497d99e092174a19c297af999733e5efcdcc0b92309be3a4c8a776c17841dc070ae7d977c3baf19523b691778387dc4ab5de4b462c7cacab9cc0a85580f8086c352226c5b71bd1986b87f5cbe856ebb315b169c9cf35cbf78cb8ed019c1e2873ed8e77f0543bdf085a41f1e00fb3c6a629c53371c8f0b4059bb0b337d2aa2e940c73d40ea1695a0272b24d178f18751374e63020003079b276e97ee1ca0a84fd5c462e014c0a9dd29d2f6f63020e904215b1c3369cec65c2d2e64551823dc3d4ed90db729ebfeaf6ac0747cfa818a75c57953866f3efea5d3d2e9eaac6640836be4d66a656203c4c59337894e252fe2a4343d88bab9fd4e1060330000000000000001e12f00004b0cd0b3a784062ba31c1181565aa2ddaebe8cd0732586fc1fc37f41ed8fc3467eac595c4314f7543de0d1eaab4dcc8a40292ae938d563f8e508eb89dacb5ca85d1a1921e20e898472a3cade302b2f0be6039f8f93fc5c66cd10fcedfa89522d2fa94657ee28261504645ce76ab786b69fe85c5743c6e769fd489f9f697837f7e7accf0556aa268e4af28757e7ebdc1eb3ea36765c3e98519705d2f11b0a41146beededfc7281be9f3bae0579a789b2e55def54a048b3c8eb49b3c2eeb3b5d3ac21fb87e3f431a654fd6b2fb74a0f3fa1f1b48bab5fcbbf6f2808560817c692f9ecb4e0ecc8ebf33385c89956622c8d1ef35984701c4dd5c21b22dc6be47ad22c7c9ae7a26034635745ec263ba61f9ee76e1d4279bc815359353ce4a776322527af1c67fc4eae1904ee716d93d1cd51d49fdf27f7200c7c1d89d65688abea94f538b43a2055343462e4b9627d4bc6c38356540d3467b203d4675c74695f04f4c818fe288fba8202a95c83f966a9dd98ff0729ce8a6caf51e6c3eddc1aa4354b7467e5d0fc7d72933a445bf32c093a607471ab039fac55f7114e4626ce22e75163c8501797141680733ceb83ac0c76892f07435117bc86171d230c2790cb326b5465de23a8e83acf61dd866b47632ad3dec8dbc25ffc5373d116398e34dbc69334581b6eea3c95364866a4cd184a69cffd1c1a87a391368cb6e7436b3f46c785380613c041ae296dce4dbb25f2cf3095979bda8b8a4d5ece7d374427bd0663efb00a215d7f48eb7544efe666c8d834d201a51bb80cc46f56722ba7cf34ae6c45e4aa6c30d4dace1cc227f6e5fc4c26257523e798cf014b7fd0867455276c5522e5a24d9e168aea61dfb7da4b1c85b0859cc5bfb9f461dd94b0e65b88ec882cc1c49c33faef4d923c15780baf38fb57dbb0df54d3ebd30f626aad51f28126e96df32244041c38e1abc7b82061d5942cb98d69a0330b9eeea31209f4e66a72ce4f6dd771a78969ecdc345e066349584c16adf43a07556cb84c0558b3691f6910205d8286a8e4ace8c3612c227decb05f794e245bbfd0a6105a879fc38e53d863feaa24d0a09e374884336afec64978c0ea5e1751f9d4895cea48da3cdbc8f1547df46a507df56663ee41e64e04004a30435be0cf0e637cc5f5d32ab78e0ba31bcb9759e98b4ae27b01b3468adea384ceefbd4596eec313602c87b07f82c76fb1322e650a41cd3dcd05c195b94f254cec7ff56f11f0e2ac62c0c6b683d36f08f4917241e4768db16f90969aeb6d102b3340d3a39473c1d06003bd1dd616c590413c9880f07dbf04b12e4d0b262b47f64b185422d5d4919f8d819fcd0b553660387e45c15c199ca9ebb2eac411f3fc2db4e0199268717655e84a1b07a2bb0d11215d9de2459b33469aee7139907c947227e9cb3263525919607fa45380a0110c454f298fb14b6db0dced3675a94792c0aec305a09a77c833ef4df873d01dba5e73a8b40f963a07ee8a76dcdc9c6f193a4d5f2e7d349e42d664ae463e8214d21d23b758cec7a1aa852ee4613807b84ed4bad9c5431c376e0070502f8b0facc44f48fbdce51e843f224b7e9bee7ed41c755157c55d0b39f72a047812d0349f60c16eac9b79eed5e3a33c539d4e8c9ff15ba7c93a0a51f6c12fd5813f3d2b98c807f85bb0b19f3cb8ff19ae237dd2ce271c9a2dab2f3a3c76fff88c6fba43dbc78f3f8e79961084891400d1f9786b005eea34890f05cb44456b812ba89fc69377ad51afd6ee45a77bfcf6fba92ee0ac090d32739973ab794d218e7d8f6d96a1716f07829ff336fdd4e58986e65e148989ee657b25a7131e3bda51a83d86efb57b9aa1589620846e56390317b9647bd093cd6c7c5c9083cf458ee053184d0561515c1ce0fbf0883a39499d6df945d1f9744d75427ce88084caaf71b22a014b107c0dbc4b0b113c7e4c3244eaa7ee0a12fa61b0c146e9a05083e2b8ecb47b3b49f279a468ca3e948b8b9d25e4d7d0845d96de44ee1d0cb91c498a2a0a96665950572d2c054896dbba8e5a309bc0d3f5bd145fcd008f805af09a2ab9588ac1b8f0aa20abec84daacce02595b66de7244dd191355a740af11c693dadd139f0094410c1cb9cdce2b02648773b16bae1dd8b37eb697ced9a6509a9555096d6aa1480ee3c387cc46431483a56274c19eaeb5302f3d9548c8291ddf2c208c8f30c6e8ddde77b37083dbe03cf3a5cb27c1d323909ddc6129e54ee5462c6afaff160a43e88bfec436aa705a3317c014cba9b872b88f8f05790a6e68ecc61fb1364f0e846b95562b973c430d862e2c7f55d22dd4c99110f62a42d6ba63f15f2f04e40f6eb3ece59c3a0b4b762aead6512206fa6f13e11eac821ac81393115ce40840f65130d6a79d7efe3c9e2edae0ea9175fb8afb79439b82b8d89c4d9bea5efdc8ab5ca2975410b312e4d8e6ee5760639d2b00c090efcf9bde300922f7bca39521b1dfc200827e4c7320d405f8ced70241335f2c10697c98f69ece6552b041a763e440db2909992aa47a6bf5ee3220b81e1525fe315c4183e0999be5ff29214f79f1c16f070cdb57b274748423a6864ac5600a35a3d2da12215b4f6082ec3e0a0ca93688497d83a5dc0b73aaffd62ab6e56c94cf288c91b816a8be5de7f530a01ed6bb4e303a88e0bb2211388790fafeb095de3f9745903a373ccca71a2fa539a748884ee016ce72b69aaaa5a53c77d95f05d68d836df15536b5d8fd4e8a0384887a88e294d5f799c5100af96da315ee78625105590ead6a7a968127dfe5115e532d3df13ad37ba65d1d15d28a2dd2e050b3ea89e58ec0dc1a6c2d44dea5c960dd47719a2036b293be485e6dcf80c869d4c0e7269c362da6f621aeb7b589e56db07b1885d1b6f2eaafb53de5f4c8455564bfd302237df70531c0f3199e3624669d01e052d1ceec69e6aefa9a15d2b774b8ff1f574868af69fc5b67ed99912f4d5a0a4eff593df4cb6efd03841ad5d8175d6676014892679bc34bfd5792fb062693be3377590b25ea7907e26cc66ca33e0022d60be5689bde2e939b7d7c5da9b3e9403d6846e286d5673f2aef467a9da9aaa0230f665ad8f1498f139d5a19e98526740cb4d0b2fe70a98a50ed05b488a41c8b51774150730bd22ec132018a009aa42e014cb5f94397a743dba9321e8c87fbbfeacd4ee76a08e533c200c4138101a4257bbd14aa3e4d8afbab972d19b813ade52337f637dae72a335df5152aa4f77e25f052631309ff9e0b683808cd801ff0047c43c115b698514ace481f5672b1459de7325e36fe2ede6ef7c964006ab8bf55d29c8ed93dd8990e0ba9e0af9466ddc56a01f753ed895f43efca8ef43778656cf703f7b7ffedf4132b00686ac90009e251f5483598d35e9bccccd4cf2763c358d8739c965997a91b1cac424d1b1c7256c71b4b7ebdda7d8ed5ee536719eb5b32dd7dd0b6a92f150cd7bc4b803963999e5ed9ee6fd18ef0039bb60b001403d999f3dd553bc5a7e289cfaf443d6d2c934feef72d913251082684d608bdffebe5beeabc05a3dc0dfbc030de609b60f60bb8527030685da95f7e90a9a712213999deadb057f63963145e71e0d826dd1a4bec793245735fd65763ecd3f2565732ed2b03dd888f774d5618bfe299ded333d058e51b7e9f8c4fc9967368a140298ff9299107b61dfd3a7169136c156ed006a020aa510ac87853f350b39baaf0e5563bac34a3717dbcc6c8750f84a054ec0eb947567fd36aa868d4fced08f104a65dbe3d42400b6d3785b759616269bb22b3d5b1d45cbcc92eea408a2020c422a309742da792a3aa2127b2bc6cc9a58c1c151bc4af219a6cd3cdf487300e58133c2fc6417ccdc6587e2da15edeb1624b99ffe1824a72dd41298d3684ab87acdebd25b5f093a17c18bcf445c5b72ddb170c6dd39c2f11c3db04925d030450995d1c3abba499a30f1b72f8fb30b63d1d8163815d993edacd43bddd3932d5b3aa6dab6e48735a9f06c856dc661d6cb4ce7766567d4ffd08b6f66956789a80a1f34b63f14469e2800e3a6e08478a69bbd1e800b02137cbfe2cf8e61750273d8552690532b963fc3ea581e68197cf46c084f57817880c40501d361ffb78cc1df369ff412e30c08d8e3ef07e7af46afbf6720ed010917f6f332bffbf1ab8e0d1d36f1b8b01a7b30d8b32d7c20f21e521ea294e060ea67388324fd86d681eb80d79e988f8e5e956413810fbd07a4bc53993cffd0ebaec3cfe8fde0e0f14405b457bcdbecc563ccb1416fbb7d7c9c60d522fedc8e2b619bbcf628434db5be673f474e12eece9477d213441b6f6f698ab8d31f914b5e4575ea6eadcc0f2a4a298450576a397eeb6554c7b93557ed9e371288dab6921a380ef240f3d9ef1c9f79594fd42ca0e4118880ad4ab24d324e9c88f5231cf301a2d5e890be61f3a74969d506b723c61f01c6363b55ac8a4650ed3f2b66e748296583831423a1ee33c4649e73926574ffa5ec7700a69fd7f4f1108fa496bc3ae377346380727a02cb4c0b962d3e33b4fe1a0d080617c335c726b1039ad1acd4f3be02d3d973a6e44b7d7cbfc6c373a0491c5eef45135350fb444dd34a87ae66879ea1eba9359c37c7979006888b4a3c26ea6b6d1241599ba1c6063d8df3354d663fee705c67643537ab37b947eb2b07b1e10d39b5a988807ef4a05cbe4a3638fd28223cb88a74eaeef493d7c543e0a6287b15e8530ca04216e4993ab30090cc4d569b7577d6ed95f7800e2f292e7e31e6a80a5fb2a2f4b335176a2c2fbc891f8af12d4e81f30317b58f614e759e5e1f04e9f804b929fa285fc86e843e3d1b2e66c84f268bc3a858ccbb93f55eebb787dcaa3ec7fe37b3aefd732bccce2579e7e30d1ab018b7d6d09ab8b90a838888c35638339e528438fd6fd6e536e55f8503b9311c2823ac53788099e8ed16b362b428dd34795cda00e5934eb6ebbc180e373ddb22fe99ac91380fb83e5947891cc34d20a1ccee479ea6300ab0b4431fb9454987beb69c0481052184e0ddbb2b8ba5cbda85ddab07d1451a06fe105a4f134427e6c30086a0f1cf1d78ca3828aa9f975cb94a7402cfb00ec7881c7c8db9643d4c3dcc3ab96bc6c0e1d0511b110be2c6d7522726f8e6864c526c166325e66f051dc58d54d3eadd3762bdccd3ebd246d2376487118a2a3053e80acf68952e68f61477a229c6d1ad13db1540849e7f2fffd69ae9300052c8c020aa062dc1bb10527cdbe3eb272330e92e3c5bf63553ef835eea4093155c436904c822c99fc2abece776f7235d9078a09ae138ca6eb1e60ba39ac35deac241971365a79b851004abba6b9273300fc80036035975ce354e073f39bec73c22736517f41b44b5a7cb2265cc572ae4dcc9e338c02354865c42f9064c5ae8be4e45f7e15821037bf255c05abf8fc39d3a248b2c79a2776080e8d2cd5f82e9f9fd0d5d925b7e5f27699fcffbe4a43dbe84f716c22dd6575fa86e66e69e5803e130b9fc0be9e77ac55cee10b6aff5886af50ac0eede460254f9b58157aa8fa482d55b9d55305e620fa6cdf8bb31d817f3af83e7c40205f33336d82af51cc7bdb0e1b743b31cc85d4beed2871bc5a0b8433f6867a5435bfeb0e7ca8168fadf31c6c1b389208b61dadd6ea0a4df113e171b4027133b546d7e3817cce5a40bae7874eec67611ddbd9e7270ee7b6ee277ef658625fc2ca08c71a380be0f510cc2b2ec203bac7094ccd39e57a5627783a0fb16049846ba96dd29c60082679927ff0c42016d545946e83fdc8b1980b19fce2853fb63d84be896fa9070f33f8e0096d8a614c4acc66300aadb1e98728391048b82cf1955364c2f7dbd069af15c7debde6944af35dc3ccf51e3bf2323dfe77797a6cba7c6ce7b630200037a6da6709d05feb844b14c5034649b02a4edec50a0cfa3cf71ddf17bd9f3fddd51e6702cb84de29ef462d2ade164d6d500129a0901e200a4618f95cfb2399e1710d6c075b271e8a7764d5a13ada46126c93c21602c6707a9576c2358c126828bfd4e1060330000000000000001659882011fe7eb88ee594460f92d27d859f795e28255f5cfb23010ac9338ce38a3670635c90a8e64a86f05e0f632e2a86c19516596fb6f25cc8995aed488a447f560a7c7344dbad76b1f4b69678683ff69e35130e19adf1cf5d76a6cba6611eed175b1b088f71e089e4a4d0f0d32b8051afb42449905d2165b06bf9957e1a57bb34a0bb0f640851a69311a81a5b8e4fb64ab90c4182eb3b3b150d528a76e319b3ec1bc2dcde1bf377228bcc61593847a32ebb14ef6ef326cad33e73902f7d7a25e0a060328981effbb0b4df686cbcaa97e403309a94ee23552a1c0a22d98439f34155d2c3f1551370b2973658f4a7c7bffaece164df7fd7cab2f34994fc0f05673e992cf5835e25d658bb10feb6e9fb68e2fe490a24437cf24e545de9b30d31d391ce3898e3e56c87648d335c41dfb88fc61ec84c9a1d40e18a5a4796ebf6269b5f2d948a17adf5d1cfe19bb23f2280048d9edba6dfca0c6e4c231c864fcbe56cddbf0330062f284fd9555f1d540c5b88c2cad8d037a0b2539d8ed973bbc3a0d1f830252c4b68f1b84dc81e44ad1913adc0d39cd504c6ffc435aa91b8fb187f26c75280558bd016f9706df8918b61f49744d7f40537f7c5cbf5404ef534a94cd339f434b62beb8d4c075dcb40c8dd91823785bb41e7c529c147eef84aee7ae50e57a8cebe361c441611fe5e7d3f1a16b3f3f6ff09e7f3988ca586cfe2d3748347124d986bd87825666ba2a9215350e32abaa5b619b96200209b457ce9541045804a933ec3700d240b971ba7379a5b3a0563631c7e5a8c4561746006284ab01f44d84fbf1b1d920837bbad1eb11c6810232727f43eb7227632666f9646e4f9bec6836b57a1b86a3044b8ae84ff3468c10421135300712d24ef33769105c73e0d88d0ec53e1865bec30f05f099aa8ca5c60b862005a5780b235f95d6d5a13ece773425c676711e3fe933797beea6f695b06f71650e045f187024e2d7dfc67e419c4e4df903b51ccad8ba47d523bf45644d124cf1ca948f508a322ecdd3af066f52b51463e1df2507376742960320e1e30b34a292b0b24aa59b32ab94cd0627147d8a62e7caa175510408da1444420e4e89aa26e42ea422e84528d7dba8784e5027a0aebb6697bf242143188366a044a329eee7ab8dd241d40d8fd0bbcace7bcf33cee3003ca343694f23f2319d2001266580b8b501c27258c1afdb2364b7bb6d8027736c73f1256bbc4d0ab6411edbf277c92d6cd0b2ef42b57df71177c3aed9c41bfba58cfd515a0135643b0f5858d5778efcea57fe40b1710ab447ed737c1a062fb9ac468ec35f7b348ed4ceddc953a50d4d031165f8ba043ab2db8144695119eaccc4d1d546b278f7a1a7a0c05144a783cfba0d6eef63d33eb2460a368d1e2c13a5b137f8ef4abaebf80564e71ff69beea19b289c2e791baf6ec3c615c46d6f84d8aaa36cd7897ae629f5051f35bb46875c049785378f3eaeace7cf87ad4849cb472735ebf8ce9360f48f715c4f74eb61d4fd53b4422294cd562240c50fa1310cf3fafb5a8ed2124c7a6ffb6acdbfc043b8794e3c2b0b4f8d7fd4895d34f307e109da51b192cdf4b93c43ceb3b97f0509deab52ab8d31c58894a47473c43b17e1455b7ef55e47dc130104253b22425b8e12008462943e8faad5907c991139f1c58be4a8a832c10414af575b45dfdfda8b0ad8fe2976f661ece7133939b9330a229d85ed9b1417b2621cb7e1389921148c61c3279401f894081d15797d0f06cd494e344d1fa0f587e052eee99565df04a89b43a43c5f0e2cd3539f0cdcfd68c3f1d682a3e4e0ef4947c39cbb2a469ad396e4181afa5426398a866cc1471d9f1689099dedc16499260558ba2fff82d5a67672ee642f6424ec15123a3306cbd16057f77b3eebd58d0320f8c3830cfd9d42791f7d298eaa4e6685f95c9a482627f980cb51c810649e6f32ec6d63a963b4b4a48ed37dfe5dc17f1b249bf87b6d38c1ca112f7520f21a525d93fa0c96c8f295cd974cb5167a03020fdc8f3c54389b9b2f1e00684db009122fe8d6426894ea7915a7950db91a1df09839feef4b02742f2569646ee8bf2913bb973de9c03c61e61db550f63f3c06964d0077963b9eaf107612eb554efb904cc7cb9fc6a83f9ef7a0c9ddc79e0c39c6f7f90c5d5d2a55ea506fdfc346e0401915ad0abaf8d5b818c058a54d324b03293f354ef83223ece678b3fc44735b5e679d8b15fe162872c2618634dd1a2071ba7dae8d7adec6930e3f31d0473633090f27ee07094d095222f0ca424116ba23bd13ba9b1b146c3b7d4c70812bb3fab93718944334b08cea55528ef6c9db5e6d8b520db63fbb5d088af9caa6bfa5a40239f87813c73e5110a0e0b7255fffa0ca79d589d216e09938a1bc083b19bd54aabd50d0c9e3dbd8b6a98611df57791b0901a173ae3ef4d206634a06cc181e0ffbf6ea756d1e97f7bb50a4991efa242bb5483ea6837954b22c4a981547c856833f4d4f1b2c6c69ecd2620d19d57bf1d10c052455de4a43a347c86726bffc1f98e8f15a50aa9484f0e3774b4bbfc4f3ec4540f8b81f9233112075b94599ed9a32a6a2dec9e6ab3f9c14cce2bdcf2552da0e8735d79642715488f7c6d846e1db4b23bb69fd1afe360e76da04c67d82383d2f9671c69568985eaf53637ce25e55560131a33647376e391efe71f9f58bd5b51e49039f66116e002799e507b4ded484edaa237f7fba40bba08dccd486193ee6bf3786d5ec223b0fd9562b8c7a415d6800a5ac2e5051d094ce1dd953524007284ba46bee751c447c441644abcbd79e737a501e5bb28a18c827814810ea14753f246f68c28ccf9f0b8dc757fb39429d6cd015d9575b89bf954fc3e1fd6f3d240857d4cdc256da8879b217e7f632007bd9ce7bc433452845178029375b6731c51f04ab79b0b457a00b65fc8fb9d9f9d17988cdbd003c1263da4259ad46587d5a631ca45b477bb89cedba9b830ec1f44bb7ddf8c9f3b51fc85dc445106ed8e6d6f0ba36cbb4d0792a1d4d614c6e2b13d91e32de63194a807a62b81e6196aa863adcafcae34cb8fb59609cf01d8432344e92073a036dfab37b8e3aaf2a9c5ccabb9205eb4a3d3b3199e430129df0b869fae519e16fc1baf3ff1a72761ca8b5e4b91de84359bf516dd6fd4f29351dd8bebbbb775fb43a5fbaba8ba0a28fb34b340ac8a534279c68fcd29f7e423276f0e50e11faa21145429016cad518a2e3d43173087234e21dacba49ae692b1c95389ae2b6087bdc189c8b4d9b2ee524557cd3c97e129ba8261a2df46bea9761ceac6a4041306b31b01d00da518e930c07fd290ac30bcda9e4fde0bbe8234e118f7ee477ef2f1ff9438b67bf51cc113d93332ed0dced933e9f1ca3bd5cd111aab4f3635c7de5c6430930cfa2bf6b0b439ab539377fd20a232827e237a38bbd8e843e343256dd50191ce94286c155f16c0375fd86612fd9eb4ad41997a25a1029ac7dfb21b2ae46e13944e7695c8334477060431867c3a05d30d0eb024db7a458b7a664e73a1e57789436ceadfb1f28ff77b3bd23aa833cecb4ef1010ec4ba2cbfe0e555e76c4532b68e7a8d14ee35acf05acc2bd290609ed53d4a2c7f089806346860d0c09cb37da3bff7316ae46bf58a92282fa1eac75f26b1b9554580a69e93f2ea6fe6e8322072c0ded1086244915c199ec69a9a3da4d6fe780bf5d0ced52058ffbc566b4062b4651733c231d3c57d143d884a93d0291695b03a1c30c21c698e17e30ea5f01a1a9ab770a6ef1af86b5e6b1ef74ad7dccb172d8119a2693ab133683e8f074eaaae6ac7ffe49360fc0eb2713467093aa8adb7561345623bb626aad1a674f2cd726faf709daae110f4a4646d8ace493fae8b3596c19efbb3472a91a3015c678775e65f5f204c7ef4a9194b5c62a0f51687a041ab651b2fbd8b8138debd51f38d1245cbeff5d5396b769d12fd845fb1fcf5b93daea9a3264e956a3f4a3993f1e9019b28ce2182638100dc13ba80b494d038217336b9123d14920ba74c216df74eaf4f5822e76ba6eef88633ae6992a44557cd066484f7fd35efc7f40e78a1f5a90fbb3499249baf0804222550064b03b0f01f12940adf156998880a36fbae58887bd63793d0987db932fbac40ffd932da6433170bac633e771df74f6a1a620ee7769c0faa946ba7d0f8e2aeb2186442315c25c64a45f57772e38fff0de702a77b011ceec1d4231f4707ba53034777d1f0d397918e068cf121837ceb3384547c16fb5c7bb1ac7a8d290092acf79729e531cd3d5360a7ede221f946c2ede002a9416053bf7f55376e6c535ce314b1f4d160444a4a2e7b30e45d13c4ac7f84e985c0f129b1c84ceddfc6e26cbb40a0d6d71272b41a107b85ce82cec1a9e6d2b3510c114d93fa8caeec71661122d6bd6dd72a10ef8da00b3209f9609d000040a7cd70e36f6bf6d9e539e44af156670304e178cd749489aedf85727825316ac5b79fe4831858725a91621760ebd50bc9c3fc994d4ddc854c1fc872f69ec717e6d36f845b70dfc72146e4271eff93cbcbdc0c99d11065c75b39ef71d95e251396c08aa0a41dfae344e000091102d17d09a27bf3234ec69fc91eab60bee6d5a8ce03d681ff78498c0bbaff23e917b7a96494cca11db086eb587e5dfecf58ffa5b0b1cff47337fab339fc2d418f55409b5fe23a0f05e9996bf8b92a5a42b09f0d3d60495cd176a397126e4a4f355117917df5d073a56a95f9ae5488ee53844cda9bf4585d923d750b07d18313f4b0b5d1cf8d8033ec97045e26aaba0113a3b8b1311479b07145551c8e5a9f1cd91ca640045d92f7732df705d9e4d1fcc14bff9004638de837fd21933e3182dc2ce380672ff4c91cf62f76300992275cf23a007b5104a240307ee372d60de8aba9ebc882860d6d81bb9e55f0afb6abb8a6e09576e0fe4f12f5b8ef85bccf6739d36886eca5df257656e5d09a7bd3548fba478bb4f873def7f2e2c8bdf02b29ba2dff6d62821c28323b7213d6332911b5fc8aa7ea66c595a4ede35c70322f3e8378837041c127d65d169aa4bf9e33078b60c22405912d547c1ec2fd6fd890d50f19a7df480dc8a0cdf15bb1ad32a1306a85ced602e404a09da90890e4ff23f59e9d4746e71ad9b0dd28871ed0536539a58e2529124ddcd4423c48aae4d9164cffa6c12978d7f3f4fdf3fed0b819ced888481f208d596586e70072bb0221ceca7f303944845a6aa3f69e3ca56a46e47c3c7a9e6d963780acee076c083cf9f27becbe5ad97aab82524dcc60ef36b82ecfc8671a2a06977897a8278cc8bbdbf920cfc8baf3dabc39e9d6467beaaea339c1a3f9dbfc425aa05d1249ce54e6352ff690948cab0f7ef45797396657fc091e22a372edd8034d743568bfeade805e9715fb0496352995a505fa42f5e730d370b4f86faecefdb0811c011a32e2aa947ba7e407de26d51b8957e63afcc7c8b9c8ff7d87f7a8e6d9ec535a3a4e93245b82bc51fb96a2c11fdcef4448428b72e94fd56f544e1ad2022fedbcf6b2bbe2a20f6d95deecc3306913540eee7e5a1e3897237bd377ac87cc241cac1cd86af7de9c8fd3c0ffe27e628bf843500c61499152420c20e2fcf86b9422338c28dfacc70e4ab3442afc38bcb76f6677555fba0d36d3c0a01d4cf17ed9f5fffcb2d20506f3cf8e4a0f425a9c2927d0246f0add5a5a5d3240ed8e15f74a8e16238a36ce665346d86a3d7c11d657877b64904e1889de421f6616a767b0e9bce01ba43908a743d1818dc19c79251b7086f3a09502774addd5277e13854f8a2ab16a61674209bc484824978e892ac92c13d1f658ebeaca43f70219986f4c5c8c9d42d0e0965efa64108527ed9701f3212a4294d14320cef916d1a68ba7f630200032108c2ec440116e3d14f20204a7c410c649c55897a69b182ffc0d339d5cbe281754f0e2f41d8e49ab2ada91f16f2d8dd7fc1cc4462f3d69745241ca38753c4e0c4b4263fb92b14dcd6572e29175ec14e77408bba41df4896b64ec9e01ccf9109fd4e1060330000000000000001cb4d7100a9654134fe39bca10bd73b695d12bf24577d1708716baf18073d73dcb0011d7ad1ea06a184de45b60887a47d9461b1c6318d833760cac85b9dee07b532d6ffa005343332b9b4fd0b4357e6a57330e6c0b354fb9196a18db446897bcea91bb91b4f382ada0d512936c98dd8bfbaa6473125cdfff34a2c9c2dcf7a7d307b6570468d38c5d734b518047894a24f7b6d074ca7f037fb8f7a53ff518c180a308de9ed7f037d4a913a31b1262ce2e71cfb092872196cd91e192af6ca3df98aa9a0303b7bb9d9d05ff8e6a1974a51752fb224b809e1a8339ff67009e5a84d7259c1929c254cbc927fcd83bd75c5e15f27f743be1d38947c6f1d7ae4eb9b8ee65e0a81c37d83f865c924641f8ae07025bb7329e62e63c14eb4861a8c10b55b2cf262f7918d17f143d13fbf387f254126948f41014a5a9324cdd76eb85b7966184d51740afa98ca61bb0dbe5c47bb3908628d17d3049ab01bfbba81c458d7ddc0afbf8878196c44aef743edb493a8b88e60f1fc79b4fd506f42c1b63fa9f70c1c715b6f8d7e5ff73aefe71f3e938282167702bb096136b26a3383e0e6c324b682f9443e0a2c2f9e717ae3b068522e9da1cddf4494215a9b2036006a48041903dc018ef7c8ecfcfe2cfcaadb6fc64ceec312395d1b20eef30328ba08e05da50ab9aadcd820f0d20308703613bdd48a72cfc200e02d524aadf4e3e84030dc37335f27a558f191d5f7d4fbc364bddc2907d66b22503c493541d52e600b14d87c98a2c5a4cc79c94b928b908bfb76a9a59b1252d83078f621278cdbb9d19b708d32231cc5e5c11496278e59c564fd951c7c8431599fac667c64abb7972fa27d22710eb7acc9d84ee10ebacab027044309ea52aa119155f0eae702c5499a3b6b375a3d3e7af8251b3bace39c7cb0a9d8369d43d1a7ffa7c6dce76149ecbba1ad28462edd1d794eed5ba4a875be57e3031d47ecad179b6ce0498981c4b84b3e99be2ef04fc6aeb9049c9366a15136c1c7732f38f1cb4bc9f677130d2e3830ea90ad68740459d3298927b5c5ac98fd8ee02c24519cb36c25fee86871d4d447d790f607308214e4acdb2a0c0ee572b128e0f6a884038bce3990edb5aac63b8e4adb34ffd831b3b1c26c908b6be079f2f75062c8d3b17e2fab5834d7174b1f6de1fdcea6a4ba30d9c2b38784c7a9ea294e24970aa4a77946c2fd3052a2b5985c3b566df78a0d97f296b5cec7bb503d35139ad5309a5e447a181de4333718f1c0fcf510331384ec708629e6f9f5da6238cf56b5228373287adbcbb4193191c4652a05af3b2281075afd2d474f4cf6e4db723410ecd581bf08b46e66235cdce48e1b7ca26f93245547410179e93591bdc37ee3a18fd5aefc0bc0d87d05f6290b470d58394e524ce02095727838788b3d8ac8ae2a907fbf6359cb64105ee067271b43b16e67a1665a9fb1be10910f0a28a088fb3f2d3bfc006c7e366ae78b8dad115adb0b06fcb261fe3cbc6a03aaec0e7b678e5896283751b2c570b1cab223b82d781f97e6ea926d6e8a4208d7298841e01178588f385be59b23095a66f0dde5f2526fd1f8d4f2584cd6c1bda55f916ec30bf9367d50bde632193e3875e6f393364e474ebd49d52f0c26bdb94a7b020c8edf007394e594e995619682bd8b6cf71936f0502a9b2eba3121a721d8bc91b230a7c2f6a2ac62e8bfc6b5f7bacf67c7b0bd161aded05e671e6e12b71c5b6a81d5fcc85fd5cc4462e5d3a3d165e9b3858a345d99e66c952978b4b513937094bbcdce408c221980cb8685f83b6214a303eebb99e834985bbde3bf3fbfd3a3327a0888e237df77c79e4ca9287a0e7fcb53c8ccbb2a33b6fd67173cf0c6681cec86b3dbd5d8bab6d989eaeb7bdad18996be9d6eb96908cf60aa5db4f4de062ece192f11d1fce85bae1d00eebb3c52f922a1a8508aef015692614a8a13d80ede3ed8e53926f885948e04a6a68f4779e869533e0ad888af19647ef9422c4d21cd32dc97d6cfa91743638c9b7ca9e46fb687a29fea8d64707b2f5287f9396b6b6b35ee2e7dc38ad27d2cb50652646122faec2060c56742de92ffb39aaab3c860bf188e3e2097081b0bd3f475ebefbb70b889727577fc4df7487e9671885327db8d88eec571f1c2b6996c83ae371de0bd9c53b661356f648a0f6cd1a30da1323f4b546bcabe767537d6507fdf692533e0d4e125636f6dc58c76d206a518fd9be789f8f8a86979be782bd3b4723070e4cc3beffc2c0fda67f0f15f767a2d9dd4d393a35e06a18ed382a383946f91e036ed15c1c0ad24e189c82697cf244c03cbb9f7fcce08b5b93d0774eb971b962fcf8aa041953600dcf91fe174dd915c50986b8c35d61ec580d83d0cf6eb220dc14a467642ddd54e5c746581241a9f22139fbd042d786e4ef5dd6dd15c2e2d9bed3b3eed825c69f800d51ee4cf06c184f23f3583e9c2850688907e0e3c1dfc2fd37b37a594f680a90947dedfb2fe9af36f801bc129227e8cdae62bcfdd4714fbb0f7ee40a6f44891c3bbf712ff763060daaa0d5feb67eae999d1c54b57addad3b2ac31127be42fb0a8491a4aeb43b339b45bb53c759198f6d7c13b0e257bb243a17f1d6acbb019799a57d56e9c8072a4b6241915b66ebbc47603e820dc53ab0381f2be1d9a5dfc553f6dae02dcd9bb78fbebcebafff8ff0f7e865d0e07d2720c79add0f2f97477903869dc8961085db63a809207d5ae853ba28cf89a30bfc7d362bb466fed81ab3ead1d66c7cb7f820fd65a99cef4d8cb843633ba03972e070349da81ae6ef6baa5b28abb24c3356ddcf0e1999b8b427ae623168ef07006bc74411caa175a6a9f11272ba0e11c8ea516da92ef1dd4ed11e647099bd59cbad77727b93cde07f6a3ba088a10a25d4d063821ca1abe81c62a7c7db0c682b1a32a30b5a28b7f12659a391119684715049307fe6c3a1b72eed0adec73043e78c20e8c3b657e75fbe28d9ad53a374dfd330d05e9cc5342bdc5db8268a8d736aabe9830e7384af76866da7334cf2f5bb779d559cabd2e4ef291ce3a9a8f488bb824f144eb608cc127da254c83112344b26b587221d8d8fb0eb7da1d4328a27b28499e9f1ace6151e55756397f729fd6e9d9232e0a677ef7a6dcc6dfba74855f7fedf78dfe1c7e8e1ac44040f42603037cb2dda6521ab50f135e0191f84faf48d08a68cec3e6b51932ecfdc1abc49fc9252868288963ddcdf7cfe24a07cdd98e1bbddcdd2b6936711a884e7bd930c505b6fc7ae283b8fc12d253f7a8082b9c71729804b1a173e813795d4c05f328a712a9bcbb35ab8c53e1a7bd9e89b8720fbb6a907ba1d277385ec082eb53f239c7f917ea9f4a8c1bbb0a3154d363603fc4b8ff26526725b5da0e56c634d926474691fa3683943a6a1067f467f73cab25caba3bc2a874696a4f8add24ef00a9ae028567587bb608d9efafa4515bc7c0d3521c25091b32cdfb2a136ca65f4d3f5ef512a145156b8d6c2046848b25dc1b28d9cbe4b23d403c577478494ad578c18dd62a39a3e606cb77b85c9440d5ba578e09acf9a1999d1a0f13d59dd46ee70e27c8d60aa86306f5a525aa6a45222d3076a75c1af1aef9d204322f83cd1f708f8fc55eaec01c54714048a794d56e55fa1e20e9f48b67bdbb43888b43742bc66de752f034047b44cc4e13961acf3dc30f3a9709c94889a960853ed47bed2b721c4943f35c88706b90e0a7c905124aca26830433124e9cc317fb0a4754d259dc3d8fc7518a2325f3882aa97cc8cd7581004e32ae02b20af90c48bb02ba7d0e29166987bca0d84268849c49a508b22f0c3cdcb87f826674861e03ba2df157688cf31e57b2406f1973cdf5994d77ed48d2d9270866c72312e5168fa0c31f7b7725662faba64ac1a8d7b3a318620b6d2dbb9cc22061bb121cb06576a1214894acb4db730380bf479ee3d8ec16f82b629acfaff124281f47bfe3487921ba73f6915b093c572c68739edb21c73e38a406c6669fa71f178c9755f0c8d0c6ba5be98eef2f7e27ba2100724e9c9f3079b79edc6ab4fb4f78631e26683ffc62ca8b846cd40016f35a808b23596f54aaf9d72c6638c87a405d98139cf7168ae7366bd2f440f00d9cbf5fddcab3a7029d386604de517f0c35b7156afd0e77d4851725699873399d5e2f535b0129e71ff8c41ba03ac5abf9759ee41a240231f978aaeb980ddf1c809b5bc2d2effb4b932a7794b26540a7770d7e170b1b0b81e10114a72db8713ee6c370af4c6ddd1d76c10e5b4cbd79fdc818a2e32f286c47efe55b540f59b5a555c43fbf673df3101b22e58b2e3c0f2fb83a586a5366d04e1739dc646e9efd0dd41b4391f2c6a45ba0cefe420b070e566555d7de61517f660cf95c1f2ebdfd71d9a2ac5ea84addf9c0f2d78e1876dfe80efc3b0bc2ee217249ccf82c3a06def9a3bb210d071bfcd604e223524fa986aa9aed206c4cdf037c7835cb27679ea43be4d8a07e08817358f9eea823812f56fd2a17b2455665fd55f09f8b0b0208eb6f38688a85ae7a3636cc2710259e2a6c19d23108bc81877e16829f26f25e878854f5b7bac0e2ca355ec50affa07b3330e29f93f134d2cdf291f9155797275ebb4ba96f68232f49f29b67c597e1f281a2321a21ae1ddb9961ecdca80711efa8c188e4819309c2d36ad1aed8a2f0abb17409145323328ecf482dfe60d6d6a3eb727d4194115e4cefb888a56c7711ff3b60ea969eddd7e96995646998bf25382557a2105543b4db2ffd5937b988a953a6ca167188688c50edaf44924b4b7fdd48391d537800c887d4944f2181c50434de8f3f87fc14f4b58fd1e4b990fd139f2243dc4046b9bae7729b0290e00a0e6623c2facddef9c88d0924eae647e48056857c45eb351d37f2ccd7f0c0c6b74db67f19a3da4d6569a45541c4954f99c119be3cf4f9d4a0f84ca549cd91220fa62f90debaac56c39fbb8ed5a53f428e6091a4bde41a430df18a4ce86bcef5537467e867562b53298938a7ff21f51d69c775aad8cd5a556e727ad330a08a7b3fb198cae003cf80740f107006311cab2adefae4b38ad0dd744b9e3b7cf3c9dd47a9f1545a62d664c987a4213b8be4eb33f4ce984b6b7ad8038b7c54bccbb031a82abf1192e26f507c3bd879ee42e1e74a560740ee173e23584e370e151f53f1ed32259549d70643fd1d24230904f2dd5cadb53ffdc768e4be9ee2cc528d04c4f8603d4a625fe267a36be37e319a374e35f1d20e6477eeaa03bc58cc9a6948b82678e0c3555b6589f72db2923cc5249fa1515bc8a56122529e8eae7543b3a01c4f180df51fff9c1e1b5465fdf5ecfae61c97dab19f706ee009c0dfb4aebefb15288f8320183ce0f3d0847d424279fcc9aadcd7d1ddffa12ac78e80e2bd345eb6624c8bdf82a5544bbaad8c500233f66bb9d781a6edb900dc373880db5e929ffdba840a024de032e02626402fb76e14e4681c39da2be9536b9499ab44163cf059ce6d73c40339dee4a4e79d0436c1167976287d4faa36b064d84bc4052091296a0405fc2535440d97428b7e5a5bb348e3e4ea4f13a6d7daf728942ef5a64104fd8ecdebcf60e8c76f39808cb18afb80b1d325b3ad2d1a1dec701a9a5cb8ace1268fd15b1b88d7dac5b89c93f28a3673ac4495f3c3965341d07e1138b848cd220c66a2ca44cc6d22e8038270f422045dc4265d605c3de060dd81d61b2bf95b9d9c9891ec9669e5f18dac332bbbeacc9a87c75f00aa02c4351356f8a2aa7ed2d869b52a7fa5e4bacdc3058a92ecb0f4db06f383e09f99aa541f4dcea30f830d6b721ca59f61a6f6989bbaca92f49c836dac0aed12d1b4f09a83ce40cb4af4f456d6becaefe5f8218379470e98e74520ae9900240a37c55d38f60e79e12b2d0000" + }, + { + "name": "3 tx test", + "numOfTx": "3", + "hex": "000000a03db40ab09a2c6840e2ecce95d8f23730eeb491699de4c306f1d57f236fc84632ec9a233ab87d37ce54f03270328678a7abdac37bec16e909654384ff6d4845638d045e607e000000012200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc332604a000000fbee9cea00d8efdc49cfbec328537e0d7032194de6ebf3cf42e5c05bb89a08b100010151030200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04017e0101ffffffff020125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a0100000000000016a80017a914a6aa6ef00bfd6f6714c8969601bcf491add40da9870125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000000000266a24aa21a9ed6294309b9d39c458965ca69bcd5a36b478b8ddf2b3a0ca0e8217ee0f1e0e4ac8000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000020000000101ab6d4aba721be5e32a5124c9351b4396d1984d4a69f04c874ecc20226a84e46b00000080171600144f731ca75248f7443951374c8ccbe943e98a6620fdffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000012767649f8000002011cc98074a33e14ccaba093c11b4b80d51bd64719d7cb20936f602493b75b806101000012767649f800001600143e77536b6a977c023d51327ff4774309655a08130125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000028a00007d00000000000247304402203c5a6dab11deca9035dfb850da4e02bea71e09bf0353289823225ddad2dfcf52022015964324c4c9e04ce87455a85e0d9c9052c15f3d0fae5a209af2158f4779e231012103f123b7ee6f68351b0209799456f6e98b66912b7a78ed9f11b54f8a9620e441630000000000020000000102435d740d05da8077191107c03adfea535c95b3d6f9723813e96ba519aee68c920100000000feffffff1627283d8c3b5d07a9af75388dcce0f2069c8b6fd74a5955424ae69f7cfd25940000000000feffffff030a1d2f54b647653eb0171cc07c7048a8d5c3966268fd8d2b097a5f6d757bf2c03d09a8f858bc15772945ed461f9e3930fcb7e23c4e966b43e469453dc55c832b3ec6022550a7ebdd03baa1f9eb331d2b0a51e00af9e1740cec5c96cf14c048e8ff6b911600142e64cc46b950ceb16b940bdb7f5723e1e85505d70aa8ee99c6d5203ca61ced2d716e9a760675ed98c933c45be289f7c0c820d381fd0835e91b30a0136c7ad587198ca7c6409f3036231588f017f5f87118b79cb615e1032591e84a2ff4bac678a0997a12df347da2cddb6a2cb801b4b2855c254593e6ab1600142302ad660434a6a1699d31097c461f6b99a3fce70125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000141e00007d00000000000247304402200d88aadce535fdaf67ce9e81981ef6bf0fdaabefcf250123e62d2ab1583071c102205f69685fa5f225a57e2f393c09f35c4b6df005111b29c223d4dba813686dcbae0121023a98b032df5ff3555354d6b361aa10185565d147370e37c8e20fdafe64cc15020000000247304402207d1ad20ded877504de9c4018c9a8322c92b080c65f24bc4921c394e35cb107cc0220651a6ad9b44cc2923f0bfaf08a5829648c3fd080f904eeed27af1c33cf1b6f86012103c8bcae0c89b6e9c332eca0154e9abf8e59f6f190192b627598689d9edaae94a900630200030d298a450dfa839aa83a4499ee4f61b3eed235c168115af97eb4270822b5bb36ce2b1da5cbf2aaea98704886ae9c53e6afb454da3fa99f99b2dbfc9b01763649508e9258280937a10bfb4c4777ebbbff4d54c1ec2cfd87d98104b72268df840efd4e10603300000000000000011f46a000b8558a1c0b4c5dc89306856ec01f9466ba0f97ec857ff809946118f36cf444c860560d8a2c93b33e2cc3f254861b3c01de248991e8a1bad77f6c851322a834df4146126ca12f4311d54dc6749ccc5a67f8c87bde4fcebe9e04667c9e446a8d4258210c81267d5ce1bdac50b147c6288b1d5dced4ce5c218bd4f408d4c2dbd9462de518cca997238be3c74871ad2a9bba5a48c54842ac28fbf0cce611b57d5e7ee1356c2fd2caaa7c2112cd0c1a291059023625c4990e78bd254aa9e158c8da23860ee7a82c8c665e86f9f55fb68426a8cc2247ffe4d7a13f5969549198975e1e130a174f225a0c56504f4818c3f2980ad4e81f6e90b7ff4544408c88b471395bc937079854f4f14735100412607b5e73558ba8004a4e02f71af2a537741c4c1617946d75b2594cbe66761068630d03d19ae62d7c86017423670ab63699c57bdab695f80a93e49c8a755ac08c361c614ad179e6647ee026bc72210d2d154e1dea98194c8804c9cf0226f12792db91cc316db74edeb65adbf33069be66ac16d1c34e270144fd982a8cd030e8aea7694f63a1f69c196be4f6c2bee28085072bd2c150b211739fcf947d72fff445cbdc762ef174d94a9b2be8e433096ccc0ce795f4eb230b88b31f64e0b210a0c55aea504b8b25245ef438aaab23ce915e1fc9ecaa17ffcce6da47fbc1270dba407a9a917f1085d70af32b38e73d1b99e79297acec21f7321475bc50286a2a6e76cb357f375b874552267a606345d2a00692c6d8a365cf80836d8fc7f440bafb839d053d6216aa14bce7d8e9391e761d59dbc3632037b77156484cc1a71f5d1869c1742cfd6d5ef4b755f067d1b201775ebc0714893e922547e506c0691797254c6d56a1fc0065ee27a44c7c6cd12508b7181f281f28ba76f6b4b87f3a2b15bef3e5d37afaa3addbbeff84157365abf7ed45c968eb804bc05fb49c4e338e3970267ec8744217f8952275057ff655ef4142226baac1516d75d4b11eb25f61e9ed89e7a12cbc2ba481666f5f8794a48d1ab63fef3ccb7fe179dd53a87977e15c43ba138608c0812cf6dbd7b67b2794c8ecc8c18278afb930234d0e362d926c15eab8a74a3a59e3f4c443130013c116643cdae821c10984a832153ab49802a9fcb54b6ffe46a9e8da7fa668fcbd91d169aa85df684095f1b5e8bbaddde4b354e4c6d65ae0b9770fdc79f8b3258a8650c4fc2a2d512dbaad0a588ef7ae4ca30b7ec6639680303919a28a7443d83d93d706ea7b349d85f9d07f54a1446cb0dabf9cd2c6bd0b32342a9dfb1a54f4e53e1af0577922f95ba3b7702223b1e02586c98d118c11c9f0c2ee194a36c3bb05421f409f5c3b02b0189b66f556cef5afe2663f863923a50f0d614cb044ffe533b3680f6bcc89bbcea6a97f10b4a201d1651dd8d50131f40885553a22b768ba342777bd9dfe373752c22c90cfd5a34edd40501e5687cf42d72517da4263984e5b4beedd2de48a12b804fe144796fe52304f55165e76fe6e4d9d1167f2cff488418295513e3b79e3294dda1108931239fb7969e5cd1c01c9b73aec18d4cdfee83cb76852730869687c80fb97f6cbfc62a292e94f4516efb472774acd55ae64e523b571d33eff0f37838c910ef04da3c546874400fa65155b4e98f2573f2ab3c70ddfd30ebbb2222aeb8095727dbea048d80d1b8cc10342be96025aa3ac20499bc9601ada20065128fb71617e85a51e337dbfae322e44e990defc01890317854053a1e9618000c4c0bbc6174fbc24effaa17c251330be9532ddbbf240d72c8f2f94df12bfc48d79421e322acc136c20a30ca4809e96ec75572d64330641684c99492ac1806baada87cdf8e8ac78ae1e58843ac0874b9c3b9c324d7accfba6a1490a2aff6c8cfeee6db9086035ac9404c82623c3c1191fe59bc27b8d68dad6a584e6ca82ed349f2ac9e9857c7116b028ee20dd674fe8511d22a8044abb44bde86403000f5fc8de66c957814daf6a651134eed3c3032eb9990aa4757135b85d5448ace020a621e8d6074afd28a856dbcc2d6a0b86123213644314fb7c0d1e4eaddbc25f63b7b507855df845791cba82405031cbaa81ac0a5d8b39e6f53c7e5eb883985e4b84218ffa363e3e36911cefbfd7a90c56ffa080fff7cc572a1875cc525f173f4f1c305926f150cb49e087e5ad3ab70f29dd847b7eb77c48a3214fb61b4d1266093ad3b78977759d9665e5624d161eda765a3e6f148876bb155e0ff8bc0cc448945c588d6a324d3304060ad7875fd1f4222350d528dd21831d137316bffe308b7852b46eb55e97ea52045476fc3ac43649a0731f9615ef51e97ed129849ef314182e57b10d20f82085d64363d837f28b9faaa011cbbfa346b52fd0967b32cf7e6702046f10c4003426e74f05bd1e03fc27ee596c645c073c7e51678966af73a0b18d541bef8226133b174a8e3c5963d69ab078cda14a0b44fd30f4995b7d66b551f7f772f0b9b9536f6f68905b1116797d57f4598dad8e58e0034e13afe7c3eab71a16d19ea98497b94748a4949dbc954dd7f89add0952c3073b19e4d9a52be723bb5b2a5cd50cec9c6cec7d1e9b46708d0b7f48f6a51d49324ff29b0940e970371f25608f37ed30641515f6c820075e7b1eea7e3fa548a0195899e14e001314cab985ae209d0b2d67d6afe45147b3642e16644972ae8cd004ac5227db3d40d75d16d7800b44ddbf19d2a15e98ddae30cb0ad4db009ac295d3c65b7fa31d296d226cad46dfcd12ba0581c1500aed0cb9d14848eff962037a013ad7149a8828c1925e7f0bcee4157099f252a957b4e45f736c461c10380b793d42e7d22efc783226aac4633c2a542c434b762040daa4a508c015f59a8d143742417392278a41804a992f00bacab5d598220b4edab565147891d7ff7a23cd1affd950433df0116a784913ec1619b498a2ca7150d8f00d5cccbe22396baf5e60caa8b93cab70ed34ed91c5cf3766694c723a90cc3a726d56f0e3cc0dd7a6c7787d35614fa4f9a23d0bb0155b5f23a723bfdecd0e1a512441b20637d4a7d856f2f553977f1ecd57045b2b95053993e1fc297d14145046e37c2ed74d4328c5705b7e671b7913e4bc9a9f5198e9f7529acc2f23bf9fb60c17cbd33fd10f44835a397ad25cf85de0626a6b1188775e1b61cb6f16cf7b9763b3e49410f2859436294ebfd19a28d2fdc06906f816c4ae05a2c7b70d2a9a9aedacd3b0c4b1498838364d858f1f299429036eeea1d66d9ccb5f3d5e9f0d6222e1456fe58a35acc4a286158425ab1baf5c097104ec8b732ee74a1fe63daa56861197ce0f8d4a5f248e9147558318d15d5a0c28ca26e62df17ead61fc4549413469278e79d500dd8e7c758e905eb3f03c7363b4e8894583e4b971623c8f5def9b9c38b3bbe57012b057a81bcc1f005b3339ba6cd6066231d502272cf54b8d6c64470d796668ada6f675e993260ab9b7bd99ec58d7b480459209dc0301531fef53846b7100887ba1f4ee04e4ab1dc8c1aa9656d04d14f78189ae3fdee92659c48915c0bfd5506830ab0decd8f747d6c06f359d78e9c183bdae20f2f76820d376ee6bd4fafc8bdb1d8aad87fe19df64d9faa92fcce6ddcd1267ca69515abe66c15848264132775c67d8d589a9e4c6da3746f257b45186b2991b62c16a8ea9563f6e49b58399953e643d9e9e35781733011a8f64989c6aac37013cfcdbc0ee932bcfdd96e37f2649fa378c7ace04996d73648ae4675c29fff84220dce78a9e943e14256bd73de395d85f3c9fa5eca9576c5c11716e764c4560180099d4b9210ade37ddcfe8db433b884948e4da8f27ae3ab42d3c41feec2e92dfba3610936b81c5cc5098363a03cf8dc2fd41861e0d5e058055c701d27219d019e39a82d0258cc2f011cd697f61abd168d346e91ac7d937d43a8a3f3765f2cbde3491bc444893da4a2a1a454dcc75f8a483462bac0b7399edb5507eb251656e46541657115cd326619e84e567fea6092015055d38ca7ec6707f61f52e132217b4119378b4f3c955fc6e7fe89287980ca49107ce972ad8dda987e066c9871d3e326e9d6feb51ae856d4f3cff85159168c9603bbd17caceeb385c4dcb46f44cc419b456ab694707913cf1f34dca0a2e0c6404ff9cd38a0b15e9ef0977e09eca1d035bf87d815d12c86d568a0b615cb4702dc146e30b0f5c45412cf8ec286029bf4ab70f62f34b7f8e9e67d171ee623d0106ae292caa12beb95d8cfb26c47b6a1387f3cfb8319aa34d7e2e88d275cb704db1f7089b019c99c93758b8c0c5ae30c9fc1bafab6b0c66f1740e7e17b522b3cb840b67fe3f8f08346736c9dcb15c32a7c0b27b107d669b93af00ccec64bbbd320933e3a65e1b9df0353aeddb87cbc2636d8b2f06b2ffea94eca4b587e26d783815ced73b48fbe92d6cbf56ce3428c09b4d6809cc9090e5ec6c2fe806e7521ae5e3440c09810d0749d13f46a0f52963c32b7165b785ff71de99302083e6eba62f5f5d308c3d614e1ce78096011903a310a8af37cbfed48c2d17b7840ecb89525e46ce21b74f7ea07456a1acdf5870ca484f046c81a584018da0f1bb7687f446b1547e2ca714be2f7074f448e5c12d53dfc388e18441fdfdca5fcf5c50aea67ed37caecb137ec14351c272a6b06c3c0687b1ecf912e74532e2393b8d5288263d7f864be271823bd286c7648aea29f77e33a94a6f86a6bd0fca63d1a3fbd39a2c22040c19105b2c38cbd19819fef64ab4b2b7c26dcad4850fbb1ff37f437a819ba5ed135553259f1c0ec1753bc78b0d4a74a412373d86eb1b59c541f3b18d24f3260cd379dfbf8ed389370b517a292a34e257e5e6a833ed472066fb69b5cad6189002b33f15643697891a09205b7b1da30b43378df867afbc734ab9e2257bd9bb692dd16b836e8dedf9b67e5578a8769debf2b4fc2e0015ef6aaac9af3a81a419b4cddc4a3968254e1b326adfd83697c49f69621f034fadddf123a9666295de20bb0c7f3382609d4984b017d2a06e76abc4ef934edb1d2ab595cd042715b3868f19113bad333e908f1ebd1b3b468818389e137749c1435c7a990304ea2b80aeb01d75313114d36e5ca94a0e26a955a49c8a6ee2a4968f8ee09874859c6182cb7e1971112d8284b94c370a7d6e682e92dada0a74d2b825312d7de1ccc8ba3bd5ec0939e7750c748d1fbf4f0c3d4452d5ba660c92742ae82845a8f45676e0c33a4bfc6df4bf04ec6dfd443372520d5d82adeb6cb4ad697572b30876cd87482d34e1dfeb0cae1ca21706ef12335dc129aa0e512698555801fbbe01a8e5d11bc7ed08414cbfb826f62cc14e9c65b2ecf1e882b094211e67181d62e3a823e13463249c4e171eb90de10eef05c1741d31d91a7dfb2297c95579a7b4ef13d217d7d285b97869b40c5f2f62df1e800f3b0e7fe45a896ba617c0083e8d55e6d1c61bb555c24678c4dd6c6683a24d652a399bf0621f05bfa62ee2c57d4b86c6c4541621345c358c33502b2367734a7a26ca27d1111039b4e38b35ec24ba1a13ce3efad7f89676e56423bd4035cd568b9427574c189afd3788918a9f0a7cf9b28ad17eac350e8c430718612b5b8237f7a2f8b4528dff99e01679bf6c868b9ea9ba7e66628ada0b1c29b179e541493de0952fc8ffbfdc819a884373d504959654a935533f11d8ea58fc8bae4cc7d89a4da456ea8b9b8438acae41a98b1a912505870414808f7ace1c5e0336e0946ae42d8cd665f94948f9e56062ccdc16d42c30be28c540fdf2a2ba7a447ede427122fee78ccb94c33c3477aa6a064d900a1077cb8cb6d65f72f48d6c0019a5d1dfd32a8e7145e6efd4bf4aebd798f1c2869dffdae91c23800abe9c399077c70fc68ad70844037f10a2cf36cf444363020003562f6ed6a76bc80fab62621e494e15867e4910a934e8564194321f9134356b02f58fa5e6e24642c7348116a8998972dd79faca3f79dc1df5accb64f295a742a757fe37063124c7b17a0645707d2ee1e214c228af2d9053c116cb443ba3ae8236fd4e1060330000000000000001b50fb201669d2446d2945cf115c3a27f0be33843907699e8409db5896c0f0fe89b8f4635e003dbaa0d59c240629cac1cbe764ba860f63daf87cf849ae66f3fb9bbe9c99b6ea8f5ea15bdac91077927f8723cfaa4b61a8be17a9bbb8af98101324d51261c0b538088b09926a58c1109c9d075f59d66ddaacdf139954757ebba40f66aef92da361e1001df9ee6cc3698b8d85519ea41fecb777097ec7639397e354a559f717d5d1d82264f26a1c8b1543b2eab074ca250f8f95ccb1b2f0951b3c653ac5611b76c21f6996936745e08513fff7fcfd96e1701ce42c2eee1ad00fbe42163cafa5743eeef6028b541bc0ebd202fddc5fc56344f6acf321f759d192177ee7a8b51f2782230e1d348fb88a8d3e6cc541be26f8264bc6dd097a184bfb53cd8bef59740f84e92d9a8a1ae76ea3e2df69823e16bcb7e878da607b4c9b60a99ab80f86b009000dd0cfaadc92f6739df63110c43f4aa0b5ed4be5bdff9cb1cdba2f5a8f82bff133b6673d7b2eb305078c648ec45a509074174626908e7092eff2281b1f48d53a3f82e9bbfd8a69ad0e969bbeb31f02274bea0be04604fc192cb93bac7b7ee8753c3429f4ea531377bb7340dc7d5d541cb6d39604a2725e150a131e5753a3e9263b6fa148aa3268824c4d55c450d406a3a94735702904f90732758b7a091da43d952b7d3c78978b9f1a6c2918e700da47462fc46df2e6546d744230cd8849dfd15dc2b7a89fedb5ce92df7c164b503e7ac9cccc0fc9969fe4f3344fc56111f35b08e4c6e075fecb631aa8f7a0129cb3f3e8fd29903287567d96ec4bef9fb84065e4f813bfad829a7242ec7efec4854c52b0b279a8040261e3b888ed400ecfbe038b4bb311612772b37bcd101a6fb4a8fe99dc64948101d584a22cb023dd16eecf2d6e6fc757ce44284df762fe8bf9a199024b39eb17dbde3e6399a483aed278b3705f4c4dee44083c0edc3794166c987abfdefc2692f61a5085b63cca88caf4fffb4046a340aa2e651cefb6c667cf4109bac4eaf7668d29bec2af835a32a7a7d5691505083d79aa7a5d39a09454edcfce3f49ebb2a6fb6fba398cf5838197d1f8d5ca7cb9138e24b03663ab6248e2a970304c931d578e632fe259fc67506c5de7fda0f2eb98311c64bb37f4e009215d2994e4b350fcaf2530b50e62651f485242f46cabe6156222a7c3313fb7492344824fc4dd7c8a8765f8cfd763fb6f207d0bc3b24df339fdd16980ae5e2f5c8a24cf9f1dc8c8b9ef6f2a6c6a3ff1f54130be7fe86d6d04ffe981d74f8b746f27fb573b495631329904a4d00da18abe6d4afdd66073f1bbdd033b842604d6b64f8753009db366b56b0b456b0af84339eb7f5d310c72991d4d1d3068d24e72a6388929ce807ad292da52bdca28cdaa32d756fb8ab535f06b9a4754ab92b393885a53187d538352a995eba6179cd4b5783765ba71063f6229cafb0b5b02cf57fcbb361021ba7f14e1e6f9fd93ab6e8d9c3ac3422b4628e647a37b9d47d63529e1d51d6d0a39153c3ded64c22b43dc943d37cd6c032bb43c4fa2f7c5768efa2b02d01b7f0252acc2d7454d43047f0202d37f429ae69194937c61496050b51714d1d5e8eb68b3f5bb20001ecdb18538c43b60d85de51eb05d5a419e39e1dc44db29c1c5937d44fe650dd30d53f5d3176037dfa5411c7c57786bd4af0cf3eb5d910a7c29a760937f7e6f4ffc123aa62728ac88aeca4cac26af763386f80ec6c78455e27a4ddf3176802d9390eaea4daaba0688cbbed0cbe834ef75ffe4ae703574ccc03d6a1bbb1c42b9e3082a933062cf4e5be68e5dc6571d81ddde656c1cd3e2e184ae90abda89f1e2a42ac0a4cfa41486bedf984044bbff3d2d3054a0c2c4eaaf70553756dfaf3704fed2bd46d6f81669ce4b9dd4b55e1f01c24505ec6020b022d0fab880ecb3809b4e6604c4b9bbdf3558a03fbd399761b8f14b8620b30ea7d0c71a25fe6019016c6a7523e9c99d66e425bdaf6c2270b24c428373c2723faadb891d64abe0a447369ad3f7e441bfca5c2a8e8f858a6bc6d284e71a7a657e9e7f40af96d780fb9c5263a30eb1cfe079042b3bce8b5b1019df58ed06e3aff02598d63d322db71263e2c4c0b58cd21969506b1f64459d0e8f3f41b0b9dd26b4d969a18e06eb9ad5f560f9944c25dcab63a8df4194865aafa4c9df84f68e9215f239b3fbb648cff09ddc1f860f2cba4fa2691cad71720a82ebf86c5cdf803dcec102e4bd0e520f69b8259181ddb8658859491cc6d28bd5ff8ba52a7cf384dc4a39f105e86db0da4c7c7a107e19b60f138a7b52de1a240df41b1ba3f56037f6b7c6bc7cdec387cc605ebcf01b1f2bdea84d2824afa8f74ded6c89d921b5b64b314c8bd382d3d9a29bb7c3bd31975eff68094acffe0dd13e375ea33086577da24f979d88ca046c950ab41f81666b86303a1a6e6a116c527ea11adec262eb91069206f7253e7bf45c553876dc6a196b8465e3db12c422bef82d9b9b7e6f075ad3695442c0733a0c7a35479c979750221a9f6647334f73698791fac2b73193ad5232be11203c5337fdc4a43152097e5a8ac4410ac2626747c280b3872a4b504950528cb079c71e928b760c807735754c2668ead7b9f8556fc07a390c3b56892ede6c95b75f17dc1d1b126fbe59e0147ef10ec0bb7c02012ddfd76a4fedec3bf29c6cfc914349fb25f2f42dbf1dd3587fac7dc6bcd53bcb72c8334096dcab33a5fe3db5332fb96cf760c7b6f1fa533e54d4eff05c6552d7e4065ff185047c841d07dbb7e18e256ebba4925879a825f5c750ae936c10ee298020e6eefe27369138d7fb2d743c8ac6688196e07c1406ab160c63956dbc06652b5e43d52c44e636ac11bf816019ef57492d364ab4a95cc60ca4c8905299faa5217dbeaf28d6626e6dd255e52d3a1ea45e60bc17fea3a109be33a281800f8f52341a94773954a723d305e0e4ea018ca410f89658ab1398ac5483eaa8ec7c8e33673473525891aa19e3a580c3c4b01a4b240344e8fa0b725bc1587e9ece58bf3c785eec39fd21e91087f41b691f1b53e9a334593d7698e2b41170c86a5f52580163919d5e82271427e289120a5d0cc56768c55e10ab48124fdea472070b76aa1edcc9dade90ea9876b15aeb39e03663b554e74808a137c5dca72e6939ee7414f3f5777ad1e75f61dde0014068ebf147891c789173d62902b051c840b307bd1ed876f104da5cac77e494d4fe45125027960e056b54b1373b8691993ecaf9357bf8b0c13d491bbe2d42b6f38b70320b84596f32cb2310baedad1c24a4334be143bb82ca8343044ea8dfe91056e617f16a17768916597c850774da68d315a1d2ffffcc6414ed4f8308c49926c9458bd10f7b5c84a898c1e2a6db23a7d19e6515d704577d3740f5fec65089a1375a2318dee12dbed836494baa7f12ae4d728b5e5117359d1cbb0d3d4938758e65967bc32c28a3dc1e9238319e646dadc22dde3e0c5f170f56d9d3cdf93d9d9fe95db4fab4a2409015e54c75ca268b450f24c8373d918640f59740588e114fa90af4b7549f90a79fec48ef9a43812b286b8ff60fd8e2029351539227ea9baa4419fcd6e541be3be0fb2166d6dc8f1452966c508ba8a9676fdbdae423dde5b372a0c1172c26d5cd26c6b4ee981b751b1f1e9e45f77cffda31170ae5e8849a7167b993d68b12a2ea25bb2366b47d8bb38b9fab31c8579ca7b7cf581fdb215b6a16579495ae86931eab248737aee7095dfd73581e96168470f6be2c728135b38db61f1ac0a949e803aacb1c97215984ea57f4837d8a905400b295673a0d96f4ace36028beeececa42f2b8c7e350be60e821b7c47425947c53632af9a4e7a36548cd6e45f43f5a0a122565a33532d4a533816974037ca882ae1aafcf840d311057d100d7f95a87c0701aaaf8777746b26ca7f3e55fb939747052ab3b2e9eb44d53b0a37b5d50930dae1bc6064712ec006a87d2ff396b3bdc85bb06d5b0250686c7e61c6ea524a766d45377f9479368ab266b32707b4f8d987b004dd847ae118f139fe8d35d12336fb6fc00ffe98fff8c21b11bfea8fc847d7f7c2f2b2991203ce377a9aad8dc7c4b459e4c93df3362206c28ffc26e285d8bbaf553e2cb0c2956ed4a9712a85a055dea18c8c6eddd1044d6bb7b53b4688bf29da0144af70da1ea15d8dee148d9373a7b3beca6572db03ab3be244d0e24a21bde7808dd930054f63695c0310ec66e4e2a6a09e63f1873f9b770a548bff8603e38d6cf498749a8546f224cd04cab7307585ef56a7e0202b41a44336bff9db610e37bdfdcf60ccc737996537174a89a54e38a2e22ddfad0227c65b84daade4cd4ff13e82d8308bd97aa1f7fa766a8476a2cf8dc7641c7288ecc4979eed04bda686985f7fc70768e129e5dbeef008a21a883dacd838ced6fc55289c913db9c3d60f5a762c1cf304b388870e8048c5a6592c37ce8014b7fc9089b193773450f27f0f33ec05ea85e355f0d84c1c4ad734c1b08adbe942ac93319beb4e6cd28db263e83e5b81dc75fc867c2e89d5d0c79287c77a6b07e781ae6d804551c39590f7684c5d7a30d2e126e5d57684ae8a78b600dd481092978767870a189cb5beebd1236047ab5646e46949c24a8608dea822cfd47d8943a4cebcbe6256ba063a14f6369cdb75af0eac7394239775fde0fb0f8216a8b7fe7ef5fb66259ee6d5fe68c2008f2ebbdbacef30a08247ae4f3d53c0f2d73e368255e38cd03d0ad384318a2fc55ea812aac8936e459387ac1117f448ef5130c8b428eec42f0c2a890b3fe20bfa1278fb1f8d8ffe9c617c05af3ee666226e30bc06c82a5d9e0d341ae8dcbc4194864e0206a44837c69a003cbf6fd354ed12ee9fc038bdd62e6e5c9dad4ba7e9bba236c68e0d4c3bc1f4cda978d5d533c8c855bfa62c85b4d7343fa7d9ef61fb39aec226f084a9555d374a408bba85cb874683fa1c80cfa4fd780d79acd1d1330d9ea861d94556790e5e73307f08e874cf8dacf50b13a3152261b7261297651766449eb7aac12bb3e649718c920279e7791c6d85bb505fcb94db0ba8f8de3a947b648475c4d36510113fd2d3d6808e27c39d8f7b46327ab6f5e56b5e779528e34b54b087dca92bcbb0b39253b7127f0d5250c364057275a74837e487d030a685d29b4c607e15e091fa8a7afb2d74741d4248aca3c92b2d6640511aff60d2e45669af2483574c674e2da413864be0230263678ced2421c17c187d82c69bc1021768038242152e9b3abf5d95cc29ff28f432368df1bced284d2372e1a48ca72e8760b9e01f505d9297762f4b21108f08942950ce9004af6c6df1d5203322534b14543be8e89af31a85314b2ae96dfe0e53da37e58db402eb315619b961ae2bfd71cc85debff6d3740ef46cf1a3803640358abd8a7cd454903b43601a192732e23025a927dd3f231c5bcc9293041d08569a95807cc97ea0c996d95f16f9b08d34686aca58c5ff6c8841f22217cdc60744d4d1f59074e6dfc973a99a3f215dde080141feb19db447c5ed8913b80d65ec0d86467db83f432f3f609436d1b5c94a7d155de412a4c29705e5eb1e9e56465cec4c2cc23bd5970ce05d26264676f1168208d2b97d59a9983c380a19e2813a1fde5ed38e8acc31efc1528f92245175fd51bd7bdc1841a30fff7a19bcf7469c1835e1af4b9b4fcb5cbb194f1795d6258761ceab09e44b7fe8bd08a6742ea3aac62358e1294a3f651af767a56bc0a162eb3bbef36f5186eea43b4262bb0a9585b65bb5032314336fcedbaa0e07fc403c27f3d38b94e5bbe04f19b3eba49027e7012b8282f8a4ef294765413a29f9820562a758d273ce15a2522b3eecc34e547972e0ed93964d5a60727275aff958e4d71bf70000" + } +] +} \ No newline at end of file diff --git a/ts_src/block.ts b/ts_src/block.ts index 9a4d6750a..b160a2a4e 100644 --- a/ts_src/block.ts +++ b/ts_src/block.ts @@ -30,21 +30,17 @@ export class Block { return i; }; - const readInt32 = (): number => { - const i = buffer.readInt32LE(offset); - offset += 4; + const readUInt8 = (): number => { + const i = buffer.readUInt8(offset); + offset += 1; return i; }; - const block = new Block(); - block.version = readInt32(); - block.prevHash = readSlice(32); - block.merkleRoot = readSlice(32); - block.timestamp = readUInt32(); - block.bits = readUInt32(); - block.nonce = readUInt32(); - - if (buffer.length === 80) return block; + // const readInt32 = (): number => { + // const i = buffer.readInt32LE(offset); + // offset += 4; + // return i; + // }; const readVarInt = (): number => { const vi = varuint.decode(buffer, offset); @@ -52,6 +48,120 @@ export class Block { return vi; }; + const block = new Block(); + block.version = readUInt32(); + + const isDyna = block.version >>> 31 === 1; + if (isDyna) { + block.version &= 0x7fff_ffff; + } + block.prevHash = readSlice(32); + block.merkleRoot = readSlice(32); + block.timestamp = readUInt32(); + block.blockHeight = readUInt32(); + + if (isDyna) { + // current params + let serializeType = readUInt8(); + switch (serializeType) { + case 0: // null + break; + case 1: // compact params + const signBlockScriptLengthCompact = readVarInt(); + const signBlockScriptCompact = readSlice(signBlockScriptLengthCompact); + const signBlockWitnessLimitCompact = readUInt32(); + const elidedRootCompact = readSlice(32); + + block.currentSignBlockScript = signBlockScriptCompact; + block.currentSignBlockWitnessLimit = signBlockWitnessLimitCompact; + block.currentElidedRoot = elidedRootCompact; + break; + case 2: // full params + const signBlockScriptLengthFull = readVarInt(); + const signBlockScriptFull = readSlice(signBlockScriptLengthFull); + const signBlockWitnessLimitFull = readUInt32(); + const fedpegProgramLength = readVarInt(); + const fedpegProgram = readSlice(fedpegProgramLength); + const fedpegScriptLength = readVarInt(); + const fedpegScript = readSlice(fedpegScriptLength); + const extensionSpaceLength = readVarInt(); + + const extensionSpace = []; + for (let i = 0; i < extensionSpaceLength; i++) { + const tmpLen = readVarInt(); + const tmp = readSlice(tmpLen); + extensionSpace.unshift(tmp); + } + block.currentSignBlockScriptFull = signBlockScriptFull; + block.currentSignBlockWitnessLimitFull = signBlockWitnessLimitFull; + block.currentFedpegProgram = fedpegProgram; + block.currentFedpegScript = fedpegScript; + block.currentExtensionSpace = extensionSpace; + break; + default: + throw new Error('bad serialize type for dynafed parameters'); + } + + // proposed params + serializeType = readUInt8(); + switch (serializeType) { + case 0: // null + break; + case 1: // compact params + const signBlockScriptLengthCompact = readVarInt(); + const signBlockScriptCompact = readSlice(signBlockScriptLengthCompact); + const signBlockWitnessLimitCompact = readUInt8(); + const elidedRootCompact = readSlice(32); + + block.proposedSignBlockScript = signBlockScriptCompact; + block.proposedSignBlockWitnessLimit = signBlockWitnessLimitCompact; + block.proposedElidedRoot = elidedRootCompact; + break; + case 2: // full params + const signBlockScriptLengthFull = readVarInt(); + const signBlockScriptFull = readSlice(signBlockScriptLengthFull); + const signBlockWitnessLimitFull = readUInt32(); + const fedpegProgramLength = readVarInt(); + const fedpegProgram = readSlice(fedpegProgramLength); + const fedpegScriptLength = readVarInt(); + const fedpegScript = readSlice(fedpegScriptLength); + const extensionSpaceLength = readVarInt(); + + const extensionSpace = []; + for (let i = 0; i < extensionSpaceLength; i++) { + const tmpLen = readVarInt(); + const tmp = readSlice(tmpLen); + extensionSpace.unshift(tmp); + } + block.proposedSignBlockScriptFull = signBlockScriptFull; + block.proposedSignBlockWitnessLimitFull = signBlockWitnessLimitFull; + block.proposedFedpegProgram = fedpegProgram; + block.proposedFedpegScript = fedpegScript; + block.proposedExtensionSpace = extensionSpace; + break; + default: + throw new Error('bad serialize type for dynafed parameters'); + } + const signBlockWitnessLength = readVarInt(); + const signBlockWitness = []; + for (let i = 0; i < signBlockWitnessLength; i++) { + const tmpLen = readVarInt(); + const tmp = readSlice(tmpLen); + signBlockWitness.unshift(tmp); + } + block.signBlockWitness = signBlockWitness; + + } else { + const challengeLength = readVarInt(); + const challenge = readSlice(challengeLength); + const solutionLength = readVarInt(); + const solution = readSlice(solutionLength); + block.challenge = challenge; + block.solution = solution; + } + + if (buffer.length === 80) return block; + const readTransaction = (): any => { const tx = Transaction.fromBuffer(buffer.slice(offset), true); offset += tx.byteLength(); @@ -115,6 +225,34 @@ export class Block { bits: number = 0; nonce: number = 0; transactions?: Transaction[] = undefined; + blockHeight: number = 0; + + // DYNAMIC FEDERATION PARAMS + // current compact params + currentSignBlockScript?: Buffer = undefined; + currentSignBlockWitnessLimit: number = 0; + currentElidedRoot?: Buffer = undefined; + // current full param + currentSignBlockScriptFull?: Buffer = undefined; + currentSignBlockWitnessLimitFull: number = 0; + currentFedpegProgram?: Buffer = undefined; + currentFedpegScript?: Buffer = undefined; + currentExtensionSpace?: Buffer[] = undefined; + // proposed compact params + proposedSignBlockScript?: Buffer = undefined; + proposedSignBlockWitnessLimit: number = 0; + proposedElidedRoot?: Buffer = undefined; + // proposed full param + proposedSignBlockScriptFull?: Buffer = undefined; + proposedSignBlockWitnessLimitFull: number = 0; + proposedFedpegProgram?: Buffer = undefined; + proposedFedpegScript?: Buffer = undefined; + proposedExtensionSpace?: Buffer[] = undefined; + // SignBlockWitness + signBlockWitness?: Buffer[] = undefined; + + challenge?: Buffer = undefined; + solution?: Buffer = undefined; getWitnessCommit(): Buffer | null { if (!txesHaveWitnessCommit(this.transactions!)) return null; diff --git a/tsconfig.json b/tsconfig.json index 4b17002bb..c3a2b36ee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,7 +20,8 @@ "alwaysStrict": true, "esModuleInterop": true, "noUnusedLocals": true, - "noUnusedParameters": true + "noUnusedParameters": true, + "resolveJsonModule": true }, "include": [ "ts_src/**/*.ts" diff --git a/types/block.d.ts b/types/block.d.ts index 7d8309c70..4c4db82ce 100644 --- a/types/block.d.ts +++ b/types/block.d.ts @@ -12,6 +12,26 @@ export declare class Block { bits: number; nonce: number; transactions?: Transaction[]; + blockHeight: number; + currentSignBlockScript?: Buffer; + currentSignBlockWitnessLimit: number; + currentElidedRoot?: Buffer; + currentSignBlockScriptFull?: Buffer; + currentSignBlockWitnessLimitFull: number; + currentFedpegProgram?: Buffer; + currentFedpegScript?: Buffer; + currentExtensionSpace?: Buffer[]; + proposedSignBlockScript?: Buffer; + proposedSignBlockWitnessLimit: number; + proposedElidedRoot?: Buffer; + proposedSignBlockScriptFull?: Buffer; + proposedSignBlockWitnessLimitFull: number; + proposedFedpegProgram?: Buffer; + proposedFedpegScript?: Buffer; + proposedExtensionSpace?: Buffer[]; + signBlockWitness?: Buffer[]; + challenge?: Buffer; + solution?: Buffer; getWitnessCommit(): Buffer | null; hasWitnessCommit(): boolean; hasWitness(): boolean; From f98c35e1f9ad923520e287c76b11b589d19287af Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 22 Sep 2021 14:09:48 +0200 Subject: [PATCH 02/14] gitignore update --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 93769078f..9a1cb07b2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ test/integration/*.js .DS_Store # for dev ts_src/**/*.js +.idea \ No newline at end of file From 624917c11f4c02729b0c634cd35068ec1110fe74 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 22 Sep 2021 14:52:46 +0200 Subject: [PATCH 03/14] format --- test/block.spec.ts | 2 +- ts_src/block.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/test/block.spec.ts b/test/block.spec.ts index ce8ec6aa9..99ab4cee6 100644 --- a/test/block.spec.ts +++ b/test/block.spec.ts @@ -4,6 +4,6 @@ import * as block from '../ts_src/block'; describe('block deserialization ', () => { fixtures.test.forEach(f => { - block.Block.fromBuffer(Buffer.from(f.hex, 'hex')); + block.Block.fromBuffer(Buffer.from(f.hex, 'hex')); }); }); diff --git a/ts_src/block.ts b/ts_src/block.ts index b160a2a4e..2f7f596d2 100644 --- a/ts_src/block.ts +++ b/ts_src/block.ts @@ -68,7 +68,9 @@ export class Block { break; case 1: // compact params const signBlockScriptLengthCompact = readVarInt(); - const signBlockScriptCompact = readSlice(signBlockScriptLengthCompact); + const signBlockScriptCompact = readSlice( + signBlockScriptLengthCompact, + ); const signBlockWitnessLimitCompact = readUInt32(); const elidedRootCompact = readSlice(32); @@ -109,7 +111,9 @@ export class Block { break; case 1: // compact params const signBlockScriptLengthCompact = readVarInt(); - const signBlockScriptCompact = readSlice(signBlockScriptLengthCompact); + const signBlockScriptCompact = readSlice( + signBlockScriptLengthCompact, + ); const signBlockWitnessLimitCompact = readUInt8(); const elidedRootCompact = readSlice(32); @@ -150,7 +154,6 @@ export class Block { signBlockWitness.unshift(tmp); } block.signBlockWitness = signBlockWitness; - } else { const challengeLength = readVarInt(); const challenge = readSlice(challengeLength); From c76d150652ea68dd9af85c5e0aa2422ebb276ddd Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 22 Sep 2021 16:54:11 +0200 Subject: [PATCH 04/14] refactor --- .idea/.gitignore | 5 -- .idea/codeStyles/Project.xml | 61 -------------------- .idea/codeStyles/codeStyleConfig.xml | 5 -- .idea/inspectionProfiles/Project_Default.xml | 6 -- .idea/liquidjs-lib.iml | 12 ---- .idea/modules.xml | 8 --- .idea/vcs.xml | 6 -- src/block.js | 4 +- ts_src/block.ts | 6 -- 9 files changed, 1 insertion(+), 112 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/codeStyles/Project.xml delete mode 100644 .idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/liquidjs-lib.iml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index b58b603fe..000000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml deleted file mode 100644 index 7ba3bc069..000000000 --- a/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123c2..000000000 --- a/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index df7825df6..000000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/liquidjs-lib.iml b/.idea/liquidjs-lib.iml deleted file mode 100644 index 0c8867d7e..000000000 --- a/.idea/liquidjs-lib.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 731207430..000000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f4..000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/block.js b/src/block.js index d2fbe53d9..1124f3902 100644 --- a/src/block.js +++ b/src/block.js @@ -91,8 +91,6 @@ class Block { block.merkleRoot = readSlice(32); block.timestamp = readUInt32(); block.blockHeight = readUInt32(); - block.bits = readUInt32(); // remove - block.nonce = readUInt32(); // remove if (isDyna) { // current params let serializeType = readUInt8(); @@ -102,7 +100,7 @@ class Block { case 1: // compact params const signBlockScriptLengthCompact = readVarInt(); const signBlockScriptCompact = readSlice(signBlockScriptLengthCompact); - const signBlockWitnessLimitCompact = readUInt8(); + const signBlockWitnessLimitCompact = readUInt32(); const elidedRootCompact = readSlice(32); block.currentSignBlockScript = signBlockScriptCompact; block.currentSignBlockWitnessLimit = signBlockWitnessLimitCompact; diff --git a/ts_src/block.ts b/ts_src/block.ts index 2f7f596d2..20f237ff8 100644 --- a/ts_src/block.ts +++ b/ts_src/block.ts @@ -36,12 +36,6 @@ export class Block { return i; }; - // const readInt32 = (): number => { - // const i = buffer.readInt32LE(offset); - // offset += 4; - // return i; - // }; - const readVarInt = (): number => { const vi = varuint.decode(buffer, offset); offset += varuint.decode.bytes; From 30c2ca3a2d6a3f3a77a5475a0664c9350c002542 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Wed, 22 Sep 2021 16:59:30 +0200 Subject: [PATCH 05/14] build --- src/address.js | 524 ++-- src/block.js | 714 ++--- src/bufferutils.js | 411 +-- src/classify.js | 128 +- src/confidential.js | 333 ++- src/crypto.js | 39 +- src/ecpair.js | 193 +- src/index.js | 40 +- src/issuance.js | 154 +- src/networks.js | 52 +- src/payments/embed.js | 94 +- src/payments/index.js | 18 +- src/payments/lazy.js | 47 +- src/payments/p2ms.js | 275 +- src/payments/p2pk.js | 141 +- src/payments/p2pkh.js | 383 ++- src/payments/p2sh.js | 469 ++-- src/payments/p2wpkh.js | 365 ++- src/payments/p2wsh.js | 459 ++-- src/psbt.js | 2905 ++++++++++++--------- src/script.js | 276 +- src/script_number.js | 108 +- src/script_signature.js | 89 +- src/sha256d.js | 558 ++-- src/templates/multisig/index.js | 20 +- src/templates/multisig/input.js | 42 +- src/templates/multisig/output.js | 61 +- src/templates/nulldata.js | 24 +- src/templates/pubkey/index.js | 20 +- src/templates/pubkey/input.js | 25 +- src/templates/pubkey/output.js | 32 +- src/templates/pubkeyhash/index.js | 20 +- src/templates/pubkeyhash/input.js | 30 +- src/templates/pubkeyhash/output.js | 38 +- src/templates/scripthash/index.js | 20 +- src/templates/scripthash/input.js | 94 +- src/templates/scripthash/output.js | 34 +- src/templates/witnesscommitment/index.js | 18 +- src/templates/witnesscommitment/output.js | 50 +- src/templates/witnesspubkeyhash/index.js | 20 +- src/templates/witnesspubkeyhash/input.js | 32 +- src/templates/witnesspubkeyhash/output.js | 30 +- src/templates/witnessscripthash/index.js | 20 +- src/templates/witnessscripthash/input.js | 73 +- src/templates/witnessscripthash/output.js | 30 +- src/transaction.js | 1162 +++++---- src/types.js | 40 +- 47 files changed, 5676 insertions(+), 5034 deletions(-) diff --git a/src/address.js b/src/address.js index b9f87b6e3..e35453133 100644 --- a/src/address.js +++ b/src/address.js @@ -1,325 +1,337 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const networks = __importStar(require("./networks")); -const payments = __importStar(require("./payments")); -const bscript = __importStar(require("./script")); -const types = __importStar(require("./types")); -const blech32_1 = require("blech32"); -const bech32_1 = __importDefault(require("bech32")); -const bs58check_1 = __importDefault(require("bs58check")); + }; +var __importDefault = + (this && this.__importDefault) || + function(mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const networks = __importStar(require('./networks')); +const payments = __importStar(require('./payments')); +const bscript = __importStar(require('./script')); +const types = __importStar(require('./types')); +const blech32_1 = require('blech32'); +const bech32_1 = __importDefault(require('bech32')); +const bs58check_1 = __importDefault(require('bs58check')); const typeforce = require('typeforce'); // negative value for confidential types var AddressType; -(function (AddressType) { - AddressType[AddressType["P2Pkh"] = 0] = "P2Pkh"; - AddressType[AddressType["P2Sh"] = 1] = "P2Sh"; - AddressType[AddressType["P2Wpkh"] = 2] = "P2Wpkh"; - AddressType[AddressType["P2Wsh"] = 3] = "P2Wsh"; - AddressType[AddressType["ConfidentialP2Pkh"] = 4] = "ConfidentialP2Pkh"; - AddressType[AddressType["ConfidentialP2Sh"] = 5] = "ConfidentialP2Sh"; - AddressType[AddressType["ConfidentialP2Wpkh"] = 6] = "ConfidentialP2Wpkh"; - AddressType[AddressType["ConfidentialP2Wsh"] = 7] = "ConfidentialP2Wsh"; +(function(AddressType) { + AddressType[(AddressType['P2Pkh'] = 0)] = 'P2Pkh'; + AddressType[(AddressType['P2Sh'] = 1)] = 'P2Sh'; + AddressType[(AddressType['P2Wpkh'] = 2)] = 'P2Wpkh'; + AddressType[(AddressType['P2Wsh'] = 3)] = 'P2Wsh'; + AddressType[(AddressType['ConfidentialP2Pkh'] = 4)] = 'ConfidentialP2Pkh'; + AddressType[(AddressType['ConfidentialP2Sh'] = 5)] = 'ConfidentialP2Sh'; + AddressType[(AddressType['ConfidentialP2Wpkh'] = 6)] = 'ConfidentialP2Wpkh'; + AddressType[(AddressType['ConfidentialP2Wsh'] = 7)] = 'ConfidentialP2Wsh'; })(AddressType || (AddressType = {})); function isConfidentialAddressType(addressType) { - return addressType >= 4; + return addressType >= 4; } function fromBase58Check(address) { - const payload = bs58check_1.default.decode(address); - // TODO: 4.0.0, move to "toOutputScript" - if (payload.length < 21) - throw new TypeError(address + ' is too short'); - if (payload.length > 21) - throw new TypeError(address + ' is too long'); - const version = payload.readUInt8(0); - const hash = payload.slice(1); - return { version, hash }; + const payload = bs58check_1.default.decode(address); + // TODO: 4.0.0, move to "toOutputScript" + if (payload.length < 21) throw new TypeError(address + ' is too short'); + if (payload.length > 21) throw new TypeError(address + ' is too long'); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; } exports.fromBase58Check = fromBase58Check; function fromBech32(address) { - const result = bech32_1.default.decode(address); - const data = bech32_1.default.fromWords(result.words.slice(1)); - return { - version: result.words[0], - prefix: result.prefix, - data: Buffer.from(data), - }; + const result = bech32_1.default.decode(address); + const data = bech32_1.default.fromWords(result.words.slice(1)); + return { + version: result.words[0], + prefix: result.prefix, + data: Buffer.from(data), + }; } exports.fromBech32 = fromBech32; function fromBlech32(address) { - const result = blech32_1.Blech32Address.fromString(address); - const pubkey = Buffer.from(result.blindingPublicKey, 'hex'); - const prg = Buffer.from(result.witness, 'hex'); - const data = Buffer.concat([ - Buffer.from([result.witnessVersion, prg.length]), - prg, - ]); - return { - version: result.witnessVersion, - pubkey, - data, - }; + const result = blech32_1.Blech32Address.fromString(address); + const pubkey = Buffer.from(result.blindingPublicKey, 'hex'); + const prg = Buffer.from(result.witness, 'hex'); + const data = Buffer.concat([ + Buffer.from([result.witnessVersion, prg.length]), + prg, + ]); + return { + version: result.witnessVersion, + pubkey, + data, + }; } exports.fromBlech32 = fromBlech32; function fromConfidential(address) { - const network = getNetwork(address); - if (address.startsWith(network.blech32)) - return fromConfidentialSegwit(address, network); - return fromConfidentialLegacy(address, network); + const network = getNetwork(address); + if (address.startsWith(network.blech32)) + return fromConfidentialSegwit(address, network); + return fromConfidentialLegacy(address, network); } exports.fromConfidential = fromConfidential; function toBase58Check(hash, version) { - typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments); - const payload = Buffer.allocUnsafe(21); - payload.writeUInt8(version, 0); - hash.copy(payload, 1); - return bs58check_1.default.encode(payload); + typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments); + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(version, 0); + hash.copy(payload, 1); + return bs58check_1.default.encode(payload); } exports.toBase58Check = toBase58Check; function toBech32(data, version, prefix) { - const words = bech32_1.default.toWords(data); - words.unshift(version); - return bech32_1.default.encode(prefix, words); + const words = bech32_1.default.toWords(data); + words.unshift(version); + return bech32_1.default.encode(prefix, words); } exports.toBech32 = toBech32; function toBlech32(data, pubkey, prefix) { - return blech32_1.Blech32Address.from(data.slice(2).toString('hex'), pubkey.toString('hex'), prefix).address; + return blech32_1.Blech32Address.from( + data.slice(2).toString('hex'), + pubkey.toString('hex'), + prefix, + ).address; } exports.toBlech32 = toBlech32; function toConfidential(address, blindingKey) { - const network = getNetwork(address); - if (address.startsWith(network.bech32)) - return toConfidentialSegwit(address, blindingKey, network); - return toConfidentialLegacy(address, blindingKey, network); + const network = getNetwork(address); + if (address.startsWith(network.bech32)) + return toConfidentialSegwit(address, blindingKey, network); + return toConfidentialLegacy(address, blindingKey, network); } exports.toConfidential = toConfidential; function fromOutputScript(output, network) { - // TODO: Network - network = network || networks.liquid; - try { - return payments.p2pkh({ output, network }).address; - } - catch (e) { } - try { - return payments.p2sh({ output, network }).address; - } - catch (e) { } - try { - return payments.p2wpkh({ output, network }).address; - } - catch (e) { } - try { - return payments.p2wsh({ output, network }).address; - } - catch (e) { } - throw new Error(bscript.toASM(output) + ' has no matching Address'); + // TODO: Network + network = network || networks.liquid; + try { + return payments.p2pkh({ output, network }).address; + } catch (e) {} + try { + return payments.p2sh({ output, network }).address; + } catch (e) {} + try { + return payments.p2wpkh({ output, network }).address; + } catch (e) {} + try { + return payments.p2wsh({ output, network }).address; + } catch (e) {} + throw new Error(bscript.toASM(output) + ' has no matching Address'); } exports.fromOutputScript = fromOutputScript; function toOutputScript(address, network) { - network = network || getNetwork(address); - let decodeBase58result; - let decodeBech32result; - let decodeConfidentialresult; + network = network || getNetwork(address); + let decodeBase58result; + let decodeBech32result; + let decodeConfidentialresult; + try { + decodeBase58result = fromBase58Check(address); + } catch (e) {} + if (decodeBase58result) { + if (decodeBase58result.version === network.pubKeyHash) + return payments.p2pkh({ hash: decodeBase58result.hash }).output; + if (decodeBase58result.version === network.scriptHash) + return payments.p2sh({ hash: decodeBase58result.hash }).output; + } else { try { - decodeBase58result = fromBase58Check(address); + decodeBech32result = fromBech32(address); + } catch (e) {} + if (decodeBech32result) { + if (decodeBech32result.prefix !== network.bech32) + throw new Error(address + ' has an invalid prefix'); + if (decodeBech32result.version === 0) { + if (decodeBech32result.data.length === 20) + return payments.p2wpkh({ hash: decodeBech32result.data }).output; + if (decodeBech32result.data.length === 32) + return payments.p2wsh({ hash: decodeBech32result.data }).output; + } + } else { + try { + decodeConfidentialresult = fromConfidential(address); + } catch (e) {} + if (decodeConfidentialresult) { + return toOutputScript( + decodeConfidentialresult.unconfidentialAddress, + network, + ); + } } - catch (e) { } - if (decodeBase58result) { - if (decodeBase58result.version === network.pubKeyHash) - return payments.p2pkh({ hash: decodeBase58result.hash }).output; - if (decodeBase58result.version === network.scriptHash) - return payments.p2sh({ hash: decodeBase58result.hash }).output; - } - else { - try { - decodeBech32result = fromBech32(address); - } - catch (e) { } - if (decodeBech32result) { - if (decodeBech32result.prefix !== network.bech32) - throw new Error(address + ' has an invalid prefix'); - if (decodeBech32result.version === 0) { - if (decodeBech32result.data.length === 20) - return payments.p2wpkh({ hash: decodeBech32result.data }) - .output; - if (decodeBech32result.data.length === 32) - return payments.p2wsh({ hash: decodeBech32result.data }) - .output; - } - } - else { - try { - decodeConfidentialresult = fromConfidential(address); - } - catch (e) { } - if (decodeConfidentialresult) { - return toOutputScript(decodeConfidentialresult.unconfidentialAddress, network); - } - } - } - throw new Error(address + ' has no matching Script'); + } + throw new Error(address + ' has no matching Script'); } exports.toOutputScript = toOutputScript; function getNetwork(address) { - if (address.startsWith(networks.liquid.blech32) || - address.startsWith(networks.liquid.bech32)) - return networks.liquid; - if (address.startsWith(networks.regtest.blech32) || - address.startsWith(networks.regtest.bech32)) - return networks.regtest; - const payload = bs58check_1.default.decode(address); - const prefix = payload.readUInt8(0); - if (prefix === networks.liquid.confidentialPrefix || - prefix === networks.liquid.pubKeyHash || - prefix === networks.liquid.scriptHash) - return networks.liquid; - if (prefix === networks.regtest.confidentialPrefix || - prefix === networks.regtest.pubKeyHash || - prefix === networks.regtest.scriptHash) - return networks.regtest; - throw new Error(address + ' has an invalid prefix'); + if ( + address.startsWith(networks.liquid.blech32) || + address.startsWith(networks.liquid.bech32) + ) + return networks.liquid; + if ( + address.startsWith(networks.regtest.blech32) || + address.startsWith(networks.regtest.bech32) + ) + return networks.regtest; + const payload = bs58check_1.default.decode(address); + const prefix = payload.readUInt8(0); + if ( + prefix === networks.liquid.confidentialPrefix || + prefix === networks.liquid.pubKeyHash || + prefix === networks.liquid.scriptHash + ) + return networks.liquid; + if ( + prefix === networks.regtest.confidentialPrefix || + prefix === networks.regtest.pubKeyHash || + prefix === networks.regtest.scriptHash + ) + return networks.regtest; + throw new Error(address + ' has an invalid prefix'); } exports.getNetwork = getNetwork; function fromConfidentialLegacy(address, network) { - const payload = bs58check_1.default.decode(address); - const prefix = payload.readUInt8(1); - // Check if address has valid length and prefix - if (prefix !== network.pubKeyHash && prefix !== network.scriptHash) - throw new TypeError(address + 'is not valid'); - if (payload.length < 55) - throw new TypeError(address + ' is too short'); - if (payload.length > 55) - throw new TypeError(address + ' is too long'); - // Blinded decoded haddress has the form: - // BLIND_PREFIX|ADDRESS_PREFIX|BLINDING_KEY|SCRIPT_HASH - // Prefixes are 1 byte long, thus blinding key always starts at 3rd byte - const blindingKey = payload.slice(2, 35); - const unconfidential = payload.slice(35, payload.length); - const versionBuf = Buffer.alloc(1); - versionBuf[0] = prefix; - const unconfidentialAddressBuffer = Buffer.concat([ - versionBuf, - unconfidential, - ]); - const unconfidentialAddress = bs58check_1.default.encode(unconfidentialAddressBuffer); - return { blindingKey, unconfidentialAddress }; + const payload = bs58check_1.default.decode(address); + const prefix = payload.readUInt8(1); + // Check if address has valid length and prefix + if (prefix !== network.pubKeyHash && prefix !== network.scriptHash) + throw new TypeError(address + 'is not valid'); + if (payload.length < 55) throw new TypeError(address + ' is too short'); + if (payload.length > 55) throw new TypeError(address + ' is too long'); + // Blinded decoded haddress has the form: + // BLIND_PREFIX|ADDRESS_PREFIX|BLINDING_KEY|SCRIPT_HASH + // Prefixes are 1 byte long, thus blinding key always starts at 3rd byte + const blindingKey = payload.slice(2, 35); + const unconfidential = payload.slice(35, payload.length); + const versionBuf = Buffer.alloc(1); + versionBuf[0] = prefix; + const unconfidentialAddressBuffer = Buffer.concat([ + versionBuf, + unconfidential, + ]); + const unconfidentialAddress = bs58check_1.default.encode( + unconfidentialAddressBuffer, + ); + return { blindingKey, unconfidentialAddress }; } function fromConfidentialSegwit(address, network) { - const result = fromBlech32(address); - const unconfidentialAddress = fromOutputScript(result.data, network); - return { blindingKey: result.pubkey, unconfidentialAddress }; + const result = fromBlech32(address); + const unconfidentialAddress = fromOutputScript(result.data, network); + return { blindingKey: result.pubkey, unconfidentialAddress }; } function toConfidentialLegacy(address, blindingKey, network) { - const payload = bs58check_1.default.decode(address); - const prefix = payload.readUInt8(0); - // Check if address has valid length and prefix - if (payload.length !== 21 || - (prefix !== network.pubKeyHash && prefix !== network.scriptHash)) - throw new TypeError(address + 'is not valid'); - // Check if blind key has valid length - if (blindingKey.length < 33) - throw new TypeError('Blinding key is too short'); - if (blindingKey.length > 33) - throw new TypeError('Blinding key is too long'); - const prefixBuf = Buffer.alloc(2); - prefixBuf[0] = network.confidentialPrefix; - prefixBuf[1] = prefix; - const confidentialAddress = Buffer.concat([ - prefixBuf, - blindingKey, - Buffer.from(payload.slice(1)), - ]); - return bs58check_1.default.encode(confidentialAddress); + const payload = bs58check_1.default.decode(address); + const prefix = payload.readUInt8(0); + // Check if address has valid length and prefix + if ( + payload.length !== 21 || + (prefix !== network.pubKeyHash && prefix !== network.scriptHash) + ) + throw new TypeError(address + 'is not valid'); + // Check if blind key has valid length + if (blindingKey.length < 33) throw new TypeError('Blinding key is too short'); + if (blindingKey.length > 33) throw new TypeError('Blinding key is too long'); + const prefixBuf = Buffer.alloc(2); + prefixBuf[0] = network.confidentialPrefix; + prefixBuf[1] = prefix; + const confidentialAddress = Buffer.concat([ + prefixBuf, + blindingKey, + Buffer.from(payload.slice(1)), + ]); + return bs58check_1.default.encode(confidentialAddress); } function toConfidentialSegwit(address, blindingKey, network) { - const data = toOutputScript(address, network); - return toBlech32(data, blindingKey, network.blech32); + const data = toOutputScript(address, network); + return toBlech32(data, blindingKey, network.blech32); } function isBlech32(address, network) { - return address.startsWith(network.blech32); + return address.startsWith(network.blech32); } function decodeBlech32(address) { - const blech32addr = fromBlech32(address); - switch (blech32addr.data.length) { - case 20: - return AddressType.ConfidentialP2Wpkh; - case 32: - return AddressType.ConfidentialP2Wsh; - default: - throw new Error('invalid program length'); - } + const blech32addr = fromBlech32(address); + switch (blech32addr.data.length) { + case 20: + return AddressType.ConfidentialP2Wpkh; + case 32: + return AddressType.ConfidentialP2Wsh; + default: + throw new Error('invalid program length'); + } } function isBech32(address, network) { - return address.startsWith(network.bech32); + return address.startsWith(network.bech32); } function decodeBech32(address) { - const bech32addr = fromBech32(address); - switch (bech32addr.data.length) { - case 20: - return AddressType.P2Wpkh; - case 32: - return AddressType.P2Wsh; - default: - throw new Error('invalid program length'); - } + const bech32addr = fromBech32(address); + switch (bech32addr.data.length) { + case 20: + return AddressType.P2Wpkh; + case 32: + return AddressType.P2Wsh; + default: + throw new Error('invalid program length'); + } } function UnkownPrefixError(prefix, network) { - return new Error(`unknown address prefix (${prefix}), need ${network.pubKeyHash} or ${network.scriptHash}`); + return new Error( + `unknown address prefix (${prefix}), need ${network.pubKeyHash} or ${ + network.scriptHash + }`, + ); } function decodeBase58(address, network) { - const payload = bs58check_1.default.decode(address); - // Blinded decoded haddress has the form: - // BLIND_PREFIX|ADDRESS_PREFIX|BLINDING_KEY|SCRIPT_HASH - // Prefixes are 1 byte long, thus blinding key always starts at 3rd byte - const prefix = payload.readUInt8(1); - if (payload.readUInt8(0) === network.confidentialPrefix) { - const unconfidentialPart = payload.slice(35); // ignore the blinding key - if (unconfidentialPart.length !== 20) { - // ripem160 hash size - throw new Error('decoded address is of unknown size'); - } - switch (prefix) { - case network.pubKeyHash: - return AddressType.ConfidentialP2Pkh; - case network.scriptHash: - return AddressType.ConfidentialP2Sh; - default: - throw UnkownPrefixError(prefix, network); - } - } - // unconf case - const unconfidential = payload.slice(2); - if (unconfidential.length !== 20) { - // ripem160 hash size - throw new Error('decoded address is of unknown size'); + const payload = bs58check_1.default.decode(address); + // Blinded decoded haddress has the form: + // BLIND_PREFIX|ADDRESS_PREFIX|BLINDING_KEY|SCRIPT_HASH + // Prefixes are 1 byte long, thus blinding key always starts at 3rd byte + const prefix = payload.readUInt8(1); + if (payload.readUInt8(0) === network.confidentialPrefix) { + const unconfidentialPart = payload.slice(35); // ignore the blinding key + if (unconfidentialPart.length !== 20) { + // ripem160 hash size + throw new Error('decoded address is of unknown size'); } switch (prefix) { - case network.pubKeyHash: - return AddressType.P2Pkh; - case network.scriptHash: - return AddressType.P2Sh; - default: - throw UnkownPrefixError(prefix, network); + case network.pubKeyHash: + return AddressType.ConfidentialP2Pkh; + case network.scriptHash: + return AddressType.ConfidentialP2Sh; + default: + throw UnkownPrefixError(prefix, network); } + } + // unconf case + const unconfidential = payload.slice(2); + if (unconfidential.length !== 20) { + // ripem160 hash size + throw new Error('decoded address is of unknown size'); + } + switch (prefix) { + case network.pubKeyHash: + return AddressType.P2Pkh; + case network.scriptHash: + return AddressType.P2Sh; + default: + throw UnkownPrefixError(prefix, network); + } } function decodeType(address, network) { - network = network || getNetwork(address); - if (isBech32(address, network)) { - return decodeBech32(address); - } - if (isBlech32(address, network)) { - return decodeBlech32(address); - } - return decodeBase58(address, network); + network = network || getNetwork(address); + if (isBech32(address, network)) { + return decodeBech32(address); + } + if (isBlech32(address, network)) { + return decodeBlech32(address); + } + return decodeBase58(address, network); } exports.decodeType = decodeType; /** @@ -327,7 +339,7 @@ exports.decodeType = decodeType; * @param address address to check. */ function isConfidential(address) { - const type = decodeType(address); - return isConfidentialAddressType(type); + const type = decodeType(address); + return isConfidentialAddressType(type); } exports.isConfidential = isConfidential; diff --git a/src/block.js b/src/block.js index 1124f3902..0178f9736 100644 --- a/src/block.js +++ b/src/block.js @@ -1,362 +1,384 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bufferutils_1 = require("./bufferutils"); -const bcrypto = __importStar(require("./crypto")); -const transaction_1 = require("./transaction"); -const types = __importStar(require("./types")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bufferutils_1 = require('./bufferutils'); +const bcrypto = __importStar(require('./crypto')); +const transaction_1 = require('./transaction'); +const types = __importStar(require('./types')); const fastMerkleRoot = require('merkle-lib/fastRoot'); const typeforce = require('typeforce'); const varuint = require('varuint-bitcoin'); -const errorMerkleNoTxes = new TypeError('Cannot compute merkle root for zero transactions'); -const errorWitnessNotSegwit = new TypeError('Cannot compute witness commit for non-segwit block'); +const errorMerkleNoTxes = new TypeError( + 'Cannot compute merkle root for zero transactions', +); +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; - this.blockHeight = 0; - // DYNAMIC FEDERATION PARAMS - // current compact params - this.currentSignBlockScript = undefined; - this.currentSignBlockWitnessLimit = 0; - this.currentElidedRoot = undefined; - // current full param - this.currentSignBlockScriptFull = undefined; - this.currentSignBlockWitnessLimitFull = 0; - this.currentFedpegProgram = undefined; - this.currentFedpegScript = undefined; - this.currentExtensionSpace = undefined; - // proposed compact params - this.proposedSignBlockScript = undefined; - this.proposedSignBlockWitnessLimit = 0; - this.proposedElidedRoot = undefined; - // proposed full param - this.proposedSignBlockScriptFull = undefined; - this.proposedSignBlockWitnessLimitFull = 0; - this.proposedFedpegProgram = undefined; - this.proposedFedpegScript = undefined; - this.proposedExtensionSpace = undefined; - // SignBlockWitness - this.signBlockWitness = undefined; - this.challenge = undefined; - this.solution = undefined; + 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; + this.blockHeight = 0; + // DYNAMIC FEDERATION PARAMS + // current compact params + this.currentSignBlockScript = undefined; + this.currentSignBlockWitnessLimit = 0; + this.currentElidedRoot = undefined; + // current full param + this.currentSignBlockScriptFull = undefined; + this.currentSignBlockWitnessLimitFull = 0; + this.currentFedpegProgram = undefined; + this.currentFedpegScript = undefined; + this.currentExtensionSpace = undefined; + // proposed compact params + this.proposedSignBlockScript = undefined; + this.proposedSignBlockWitnessLimit = 0; + this.proposedElidedRoot = undefined; + // proposed full param + this.proposedSignBlockScriptFull = undefined; + this.proposedSignBlockWitnessLimitFull = 0; + this.proposedFedpegProgram = undefined; + this.proposedFedpegScript = undefined; + this.proposedExtensionSpace = undefined; + // SignBlockWitness + this.signBlockWitness = undefined; + this.challenge = undefined; + this.solution = undefined; + } + static fromBuffer(buffer) { + if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)'); + let offset = 0; + const readSlice = n => { + offset += n; + return buffer.slice(offset - n, offset); + }; + const readUInt32 = () => { + const i = buffer.readUInt32LE(offset); + offset += 4; + return i; + }; + const readUInt8 = () => { + const i = buffer.readUInt8(offset); + offset += 1; + return i; + }; + const readVarInt = () => { + const vi = varuint.decode(buffer, offset); + offset += varuint.decode.bytes; + return vi; + }; + const block = new Block(); + block.version = readUInt32(); + const isDyna = block.version >>> 31 === 1; + if (isDyna) { + block.version &= 2147483647; } - static fromBuffer(buffer) { - if (buffer.length < 80) - throw new Error('Buffer too small (< 80 bytes)'); - let offset = 0; - const readSlice = (n) => { - offset += n; - return buffer.slice(offset - n, offset); - }; - const readUInt32 = () => { - const i = buffer.readUInt32LE(offset); - offset += 4; - return i; - }; - const readUInt8 = () => { - const i = buffer.readUInt8(offset); - offset += 1; - return i; - }; - // const readInt32 = (): number => { - // const i = buffer.readInt32LE(offset); - // offset += 4; - // return i; - // }; - const readVarInt = () => { - const vi = varuint.decode(buffer, offset); - offset += varuint.decode.bytes; - return vi; - }; - const block = new Block(); - block.version = readUInt32(); - const isDyna = block.version >>> 31 === 1; - if (isDyna) { - block.version &= 2147483647; - } - block.prevHash = readSlice(32); - block.merkleRoot = readSlice(32); - block.timestamp = readUInt32(); - block.blockHeight = readUInt32(); - if (isDyna) { - // current params - let serializeType = readUInt8(); - switch (serializeType) { - case 0: // null - break; - case 1: // compact params - const signBlockScriptLengthCompact = readVarInt(); - const signBlockScriptCompact = readSlice(signBlockScriptLengthCompact); - const signBlockWitnessLimitCompact = readUInt32(); - const elidedRootCompact = readSlice(32); - block.currentSignBlockScript = signBlockScriptCompact; - block.currentSignBlockWitnessLimit = signBlockWitnessLimitCompact; - block.currentElidedRoot = elidedRootCompact; - break; - case 2: // full params - const signBlockScriptLengthFull = readVarInt(); - const signBlockScriptFull = readSlice(signBlockScriptLengthFull); - const signBlockWitnessLimitFull = readUInt32(); - const fedpegProgramLength = readVarInt(); - const fedpegProgram = readSlice(fedpegProgramLength); - const fedpegScriptLength = readVarInt(); - const fedpegScript = readSlice(fedpegScriptLength); - const extensionSpaceLength = readVarInt(); - const extensionSpace = []; - for (let i = 0; i < extensionSpaceLength; i++) { - const tmpLen = readVarInt(); - const tmp = readSlice(tmpLen); - extensionSpace.unshift(tmp); - } - block.currentSignBlockScriptFull = signBlockScriptFull; - block.currentSignBlockWitnessLimitFull = signBlockWitnessLimitFull; - block.currentFedpegProgram = fedpegProgram; - block.currentFedpegScript = fedpegScript; - block.currentExtensionSpace = extensionSpace; - break; - default: - throw new Error('bad serialize type for dynafed parameters'); - } - // proposed params - serializeType = readUInt8(); - switch (serializeType) { - case 0: // null - break; - case 1: // compact params - const signBlockScriptLengthCompact = readVarInt(); - const signBlockScriptCompact = readSlice(signBlockScriptLengthCompact); - const signBlockWitnessLimitCompact = readUInt8(); - const elidedRootCompact = readSlice(32); - block.proposedSignBlockScript = signBlockScriptCompact; - block.proposedSignBlockWitnessLimit = signBlockWitnessLimitCompact; - block.proposedElidedRoot = elidedRootCompact; - break; - case 2: // full params - const signBlockScriptLengthFull = readVarInt(); - const signBlockScriptFull = readSlice(signBlockScriptLengthFull); - const signBlockWitnessLimitFull = readUInt32(); - const fedpegProgramLength = readVarInt(); - const fedpegProgram = readSlice(fedpegProgramLength); - const fedpegScriptLength = readVarInt(); - const fedpegScript = readSlice(fedpegScriptLength); - const extensionSpaceLength = readVarInt(); - const extensionSpace = []; - for (let i = 0; i < extensionSpaceLength; i++) { - const tmpLen = readVarInt(); - const tmp = readSlice(tmpLen); - extensionSpace.unshift(tmp); - } - block.proposedSignBlockScriptFull = signBlockScriptFull; - block.proposedSignBlockWitnessLimitFull = signBlockWitnessLimitFull; - block.proposedFedpegProgram = fedpegProgram; - block.proposedFedpegScript = fedpegScript; - block.proposedExtensionSpace = extensionSpace; - break; - default: - throw new Error('bad serialize type for dynafed parameters'); - } - const signBlockWitnessLength = readVarInt(); - const signBlockWitness = []; - for (let i = 0; i < signBlockWitnessLength; i++) { - const tmpLen = readVarInt(); - const tmp = readSlice(tmpLen); - signBlockWitness.unshift(tmp); - } - block.signBlockWitness = signBlockWitness; - } - else { - const challengeLength = readVarInt(); - const challenge = readSlice(challengeLength); - const solutionLength = readVarInt(); - const solution = readSlice(solutionLength); - block.challenge = challenge; - block.solution = solution; - } - if (buffer.length === 80) - return block; - const readTransaction = () => { - const tx = transaction_1.Transaction.fromBuffer(buffer.slice(offset), true); - offset += tx.byteLength(); - return tx; - }; - const nTransactions = readVarInt(); - block.transactions = []; - for (let i = 0; i < nTransactions; ++i) { - const tx = readTransaction(); - block.transactions.push(tx); - } - const witnessCommit = block.getWitnessCommit(); - // This Block contains a witness commit - if (witnessCommit) - block.witnessCommit = witnessCommit; - return block; + block.prevHash = readSlice(32); + block.merkleRoot = readSlice(32); + block.timestamp = readUInt32(); + block.blockHeight = readUInt32(); + if (isDyna) { + // current params + let serializeType = readUInt8(); + switch (serializeType) { + case 0: // null + break; + case 1: // compact params + const signBlockScriptLengthCompact = readVarInt(); + const signBlockScriptCompact = readSlice( + signBlockScriptLengthCompact, + ); + const signBlockWitnessLimitCompact = readUInt32(); + const elidedRootCompact = readSlice(32); + block.currentSignBlockScript = signBlockScriptCompact; + block.currentSignBlockWitnessLimit = signBlockWitnessLimitCompact; + block.currentElidedRoot = elidedRootCompact; + break; + case 2: // full params + const signBlockScriptLengthFull = readVarInt(); + const signBlockScriptFull = readSlice(signBlockScriptLengthFull); + const signBlockWitnessLimitFull = readUInt32(); + const fedpegProgramLength = readVarInt(); + const fedpegProgram = readSlice(fedpegProgramLength); + const fedpegScriptLength = readVarInt(); + const fedpegScript = readSlice(fedpegScriptLength); + const extensionSpaceLength = readVarInt(); + const extensionSpace = []; + for (let i = 0; i < extensionSpaceLength; i++) { + const tmpLen = readVarInt(); + const tmp = readSlice(tmpLen); + extensionSpace.unshift(tmp); + } + block.currentSignBlockScriptFull = signBlockScriptFull; + block.currentSignBlockWitnessLimitFull = signBlockWitnessLimitFull; + block.currentFedpegProgram = fedpegProgram; + block.currentFedpegScript = fedpegScript; + block.currentExtensionSpace = extensionSpace; + break; + default: + throw new Error('bad serialize type for dynafed parameters'); + } + // proposed params + serializeType = readUInt8(); + switch (serializeType) { + case 0: // null + break; + case 1: // compact params + const signBlockScriptLengthCompact = readVarInt(); + const signBlockScriptCompact = readSlice( + signBlockScriptLengthCompact, + ); + const signBlockWitnessLimitCompact = readUInt8(); + const elidedRootCompact = readSlice(32); + block.proposedSignBlockScript = signBlockScriptCompact; + block.proposedSignBlockWitnessLimit = signBlockWitnessLimitCompact; + block.proposedElidedRoot = elidedRootCompact; + break; + case 2: // full params + const signBlockScriptLengthFull = readVarInt(); + const signBlockScriptFull = readSlice(signBlockScriptLengthFull); + const signBlockWitnessLimitFull = readUInt32(); + const fedpegProgramLength = readVarInt(); + const fedpegProgram = readSlice(fedpegProgramLength); + const fedpegScriptLength = readVarInt(); + const fedpegScript = readSlice(fedpegScriptLength); + const extensionSpaceLength = readVarInt(); + const extensionSpace = []; + for (let i = 0; i < extensionSpaceLength; i++) { + const tmpLen = readVarInt(); + const tmp = readSlice(tmpLen); + extensionSpace.unshift(tmp); + } + block.proposedSignBlockScriptFull = signBlockScriptFull; + block.proposedSignBlockWitnessLimitFull = signBlockWitnessLimitFull; + block.proposedFedpegProgram = fedpegProgram; + block.proposedFedpegScript = fedpegScript; + block.proposedExtensionSpace = extensionSpace; + break; + default: + throw new Error('bad serialize type for dynafed parameters'); + } + const signBlockWitnessLength = readVarInt(); + const signBlockWitness = []; + for (let i = 0; i < signBlockWitnessLength; i++) { + const tmpLen = readVarInt(); + const tmp = readSlice(tmpLen); + signBlockWitness.unshift(tmp); + } + block.signBlockWitness = signBlockWitness; + } else { + const challengeLength = readVarInt(); + const challenge = readSlice(challengeLength); + const solutionLength = readVarInt(); + const solution = readSlice(solutionLength); + block.challenge = challenge; + block.solution = solution; } - static fromHex(hex) { - return Block.fromBuffer(Buffer.from(hex, 'hex')); - } - static calculateTarget(bits) { - const exponent = ((bits & 0xff000000) >> 24) - 3; - const mantissa = bits & 0x007fffff; - const target = Buffer.alloc(32, 0); - target.writeUIntBE(mantissa, 29 - exponent, 3); - return target; - } - static calculateMerkleRoot(transactions, forWitness) { - typeforce([{ getHash: types.Function }], transactions); - if (transactions.length === 0) - throw errorMerkleNoTxes; - if (forWitness && !txesHaveWitnessCommit(transactions)) - throw errorWitnessNotSegwit; - const hashes = transactions.map(transaction => transaction.getHash(forWitness)); - const rootHash = fastMerkleRoot(hashes, bcrypto.hash256); - return forWitness - ? bcrypto.hash256(Buffer.concat([rootHash, transactions[0].ins[0].witness[0]])) - : rootHash; - } - getWitnessCommit() { - if (!txesHaveWitnessCommit(this.transactions)) - return null; - // The merkle root for the witness data is in an OP_RETURN output. - // There is no rule for the index of the output, so use filter to find it. - // The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed - // If multiple commits are found, the output with highest index is assumed. - const witnessCommits = this.transactions[0].outs.filter(out => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex'))).map(out => out.script.slice(6, 38)); - if (witnessCommits.length === 0) - return null; - // Use the commit with the highest output (should only be one though) - const result = witnessCommits[witnessCommits.length - 1]; - if (!(result instanceof Buffer && result.length === 32)) - return null; - return result; - } - hasWitnessCommit() { - if (this.witnessCommit instanceof Buffer && - this.witnessCommit.length === 32) - return true; - if (this.getWitnessCommit() !== null) - return true; - return false; - } - hasWitness() { - return anyTxHasWitness(this.transactions); - } - weight() { - const base = this.byteLength(false, false); - const total = this.byteLength(false, true); - return base * 3 + total; - } - byteLength(headersOnly, allowWitness = true) { - if (headersOnly || !this.transactions) - return 80; - return (80 + - varuint.encodingLength(this.transactions.length) + - this.transactions.reduce((a, x) => a + x.byteLength(allowWitness), 0)); - } - getHash() { - return bcrypto.hash256(this.toBuffer(true)); - } - getId() { - return bufferutils_1.reverseBuffer(this.getHash()).toString('hex'); - } - getUTCDate() { - const date = new Date(0); // epoch - date.setUTCSeconds(this.timestamp); - return date; - } - // TODO: buffer, offset compatibility - toBuffer(headersOnly) { - const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)); - let offset = 0; - const writeSlice = (slice) => { - slice.copy(buffer, offset); - offset += slice.length; - }; - const writeInt32 = (i) => { - buffer.writeInt32LE(i, offset); - offset += 4; - }; - const writeUInt32 = (i) => { - buffer.writeUInt32LE(i, offset); - offset += 4; - }; - writeInt32(this.version); - writeSlice(this.prevHash); - writeSlice(this.merkleRoot); - writeUInt32(this.timestamp); - writeUInt32(this.bits); - writeUInt32(this.nonce); - if (headersOnly || !this.transactions) - return buffer; - varuint.encode(this.transactions.length, buffer, offset); - offset += varuint.encode.bytes; - this.transactions.forEach(tx => { - const txSize = tx.byteLength(); // TODO: extract from toBuffer? - tx.toBuffer(buffer, offset); - offset += txSize; - }); - return buffer; - } - toHex(headersOnly) { - return this.toBuffer(headersOnly).toString('hex'); - } - checkTxRoots() { - // If the Block has segwit transactions but no witness commit, - // there's no way it can be valid, so fail the check. - const hasWitnessCommit = this.hasWitnessCommit(); - if (!hasWitnessCommit && this.hasWitness()) - return false; - return (this.__checkMerkleRoot() && - (hasWitnessCommit ? this.__checkWitnessCommit() : true)); - } - checkProofOfWork() { - const hash = bufferutils_1.reverseBuffer(this.getHash()); - const target = Block.calculateTarget(this.bits); - return hash.compare(target) <= 0; - } - __checkMerkleRoot() { - if (!this.transactions) - throw errorMerkleNoTxes; - const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions); - return this.merkleRoot.compare(actualMerkleRoot) === 0; - } - __checkWitnessCommit() { - if (!this.transactions) - throw errorMerkleNoTxes; - if (!this.hasWitnessCommit()) - throw errorWitnessNotSegwit; - const actualWitnessCommit = Block.calculateMerkleRoot(this.transactions, true); - return this.witnessCommit.compare(actualWitnessCommit) === 0; + if (buffer.length === 80) return block; + const readTransaction = () => { + const tx = transaction_1.Transaction.fromBuffer( + buffer.slice(offset), + true, + ); + offset += tx.byteLength(); + return tx; + }; + const nTransactions = readVarInt(); + block.transactions = []; + for (let i = 0; i < nTransactions; ++i) { + const tx = readTransaction(); + block.transactions.push(tx); } + const witnessCommit = block.getWitnessCommit(); + // This Block contains a witness commit + if (witnessCommit) block.witnessCommit = witnessCommit; + return block; + } + static fromHex(hex) { + return Block.fromBuffer(Buffer.from(hex, 'hex')); + } + static calculateTarget(bits) { + const exponent = ((bits & 0xff000000) >> 24) - 3; + const mantissa = bits & 0x007fffff; + const target = Buffer.alloc(32, 0); + target.writeUIntBE(mantissa, 29 - exponent, 3); + return target; + } + static calculateMerkleRoot(transactions, forWitness) { + typeforce([{ getHash: types.Function }], transactions); + if (transactions.length === 0) throw errorMerkleNoTxes; + if (forWitness && !txesHaveWitnessCommit(transactions)) + throw errorWitnessNotSegwit; + const hashes = transactions.map(transaction => + transaction.getHash(forWitness), + ); + const rootHash = fastMerkleRoot(hashes, bcrypto.hash256); + return forWitness + ? bcrypto.hash256( + Buffer.concat([rootHash, transactions[0].ins[0].witness[0]]), + ) + : rootHash; + } + getWitnessCommit() { + if (!txesHaveWitnessCommit(this.transactions)) return null; + // The merkle root for the witness data is in an OP_RETURN output. + // There is no rule for the index of the output, so use filter to find it. + // The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed + // If multiple commits are found, the output with highest index is assumed. + const witnessCommits = this.transactions[0].outs + .filter(out => + out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex')), + ) + .map(out => out.script.slice(6, 38)); + if (witnessCommits.length === 0) return null; + // Use the commit with the highest output (should only be one though) + const result = witnessCommits[witnessCommits.length - 1]; + if (!(result instanceof Buffer && result.length === 32)) return null; + return result; + } + hasWitnessCommit() { + if ( + this.witnessCommit instanceof Buffer && + this.witnessCommit.length === 32 + ) + return true; + if (this.getWitnessCommit() !== null) return true; + return false; + } + hasWitness() { + return anyTxHasWitness(this.transactions); + } + weight() { + const base = this.byteLength(false, false); + const total = this.byteLength(false, true); + return base * 3 + total; + } + byteLength(headersOnly, allowWitness = true) { + if (headersOnly || !this.transactions) return 80; + return ( + 80 + + varuint.encodingLength(this.transactions.length) + + this.transactions.reduce((a, x) => a + x.byteLength(allowWitness), 0) + ); + } + getHash() { + return bcrypto.hash256(this.toBuffer(true)); + } + getId() { + return bufferutils_1.reverseBuffer(this.getHash()).toString('hex'); + } + getUTCDate() { + const date = new Date(0); // epoch + date.setUTCSeconds(this.timestamp); + return date; + } + // TODO: buffer, offset compatibility + toBuffer(headersOnly) { + const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)); + let offset = 0; + const writeSlice = slice => { + slice.copy(buffer, offset); + offset += slice.length; + }; + const writeInt32 = i => { + buffer.writeInt32LE(i, offset); + offset += 4; + }; + const writeUInt32 = i => { + buffer.writeUInt32LE(i, offset); + offset += 4; + }; + writeInt32(this.version); + writeSlice(this.prevHash); + writeSlice(this.merkleRoot); + writeUInt32(this.timestamp); + writeUInt32(this.bits); + writeUInt32(this.nonce); + if (headersOnly || !this.transactions) return buffer; + varuint.encode(this.transactions.length, buffer, offset); + offset += varuint.encode.bytes; + this.transactions.forEach(tx => { + const txSize = tx.byteLength(); // TODO: extract from toBuffer? + tx.toBuffer(buffer, offset); + offset += txSize; + }); + return buffer; + } + toHex(headersOnly) { + return this.toBuffer(headersOnly).toString('hex'); + } + checkTxRoots() { + // If the Block has segwit transactions but no witness commit, + // there's no way it can be valid, so fail the check. + const hasWitnessCommit = this.hasWitnessCommit(); + if (!hasWitnessCommit && this.hasWitness()) return false; + return ( + this.__checkMerkleRoot() && + (hasWitnessCommit ? this.__checkWitnessCommit() : true) + ); + } + checkProofOfWork() { + const hash = bufferutils_1.reverseBuffer(this.getHash()); + const target = Block.calculateTarget(this.bits); + return hash.compare(target) <= 0; + } + __checkMerkleRoot() { + if (!this.transactions) throw errorMerkleNoTxes; + const actualMerkleRoot = Block.calculateMerkleRoot(this.transactions); + return this.merkleRoot.compare(actualMerkleRoot) === 0; + } + __checkWitnessCommit() { + if (!this.transactions) throw errorMerkleNoTxes; + if (!this.hasWitnessCommit()) throw errorWitnessNotSegwit; + const actualWitnessCommit = Block.calculateMerkleRoot( + this.transactions, + true, + ); + return this.witnessCommit.compare(actualWitnessCommit) === 0; + } } exports.Block = Block; function txesHaveWitnessCommit(transactions) { - return (transactions instanceof Array && - transactions[0] && - transactions[0].ins && - transactions[0].ins instanceof Array && - transactions[0].ins[0] && - transactions[0].ins[0].witness && - transactions[0].ins[0].witness instanceof Array && - transactions[0].ins[0].witness.length > 0); + return ( + transactions instanceof Array && + transactions[0] && + transactions[0].ins && + transactions[0].ins instanceof Array && + transactions[0].ins[0] && + transactions[0].ins[0].witness && + transactions[0].ins[0].witness instanceof Array && + transactions[0].ins[0].witness.length > 0 + ); } function anyTxHasWitness(transactions) { - return (transactions instanceof Array && - transactions.some(tx => typeof tx === 'object' && - tx.ins instanceof Array && - tx.ins.some(input => typeof input === 'object' && - input.witness instanceof Array && - input.witness.length > 0))); + return ( + transactions instanceof Array && + transactions.some( + tx => + typeof tx === 'object' && + tx.ins instanceof Array && + tx.ins.some( + input => + typeof input === 'object' && + input.witness instanceof Array && + input.witness.length > 0, + ), + ) + ); } diff --git a/src/bufferutils.js b/src/bufferutils.js index 3493f4b6a..8ce70f504 100644 --- a/src/bufferutils.js +++ b/src/bufferutils.js @@ -1,236 +1,237 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const types = __importStar(require("./types")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const types = __importStar(require('./types')); const typeforce = require('typeforce'); const varuint = require('varuint-bitcoin'); const CONFIDENTIAL_COMMITMENT = 33; // default size of confidential commitments (i.e. asset, value, nonce) const CONFIDENTIAL_VALUE = 9; // explicit size of confidential values // https://github.com/feross/buffer/blob/master/index.js#L1127 function verifuint(value, max) { - if (typeof value !== 'number') - throw new Error('cannot write a non-number as a number'); - if (value < 0) - throw new Error('specified a negative value for writing an unsigned value'); - if (value > max) - throw new Error('RangeError: value out of range'); - if (Math.floor(value) !== value) - throw new Error('value has a fractional component'); + if (typeof value !== 'number') + throw new Error('cannot write a non-number as a number'); + if (value < 0) + throw new Error('specified a negative value for writing an unsigned value'); + if (value > max) throw new Error('RangeError: value out of range'); + if (Math.floor(value) !== value) + throw new Error('value has a fractional component'); } function readUInt64LE(buffer, offset) { - const a = buffer.readUInt32LE(offset); - let b = buffer.readUInt32LE(offset + 4); - b *= 0x100000000; - verifuint(b + a, 0x001fffffffffffff); - return b + a; + const a = buffer.readUInt32LE(offset); + let b = buffer.readUInt32LE(offset + 4); + b *= 0x100000000; + verifuint(b + a, 0x001fffffffffffff); + return b + a; } exports.readUInt64LE = readUInt64LE; function writeUInt64LE(buffer, value, offset) { - verifuint(value, 0x001fffffffffffff); - buffer.writeInt32LE(value & -1, offset); - buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4); - return offset + 8; + verifuint(value, 0x001fffffffffffff); + buffer.writeInt32LE(value & -1, offset); + buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4); + return offset + 8; } exports.writeUInt64LE = writeUInt64LE; function reverseBuffer(buffer) { - if (buffer.length < 1) - return buffer; - let j = buffer.length - 1; - let tmp = 0; - for (let i = 0; i < buffer.length / 2; i++) { - tmp = buffer[i]; - buffer[i] = buffer[j]; - buffer[j] = tmp; - j--; - } - return buffer; + if (buffer.length < 1) return buffer; + let j = buffer.length - 1; + let tmp = 0; + for (let i = 0; i < buffer.length / 2; i++) { + tmp = buffer[i]; + buffer[i] = buffer[j]; + buffer[j] = tmp; + j--; + } + return buffer; } exports.reverseBuffer = reverseBuffer; /** * Helper class for serialization of bitcoin data types into a pre-allocated buffer. */ class BufferWriter { - constructor(buffer, offset = 0) { - this.buffer = buffer; - this.offset = offset; - typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); - } - writeUInt8(i) { - this.offset = this.buffer.writeUInt8(i, this.offset); - } - writeInt32(i) { - this.offset = this.buffer.writeInt32LE(i, this.offset); - } - writeUInt32(i) { - this.offset = this.buffer.writeUInt32LE(i, this.offset); - } - writeUInt64(i) { - this.offset = writeUInt64LE(this.buffer, i, this.offset); - } - writeVarInt(i) { - varuint.encode(i, this.buffer, this.offset); - this.offset += varuint.encode.bytes; - } - writeSlice(slice) { - if (this.buffer.length < this.offset + slice.length) { - throw new Error('Cannot write slice out of bounds'); - } - this.offset += slice.copy(this.buffer, this.offset); - } - writeVarSlice(slice) { - this.writeVarInt(slice.length); - this.writeSlice(slice); - } - writeVector(vector) { - this.writeVarInt(vector.length); - vector.forEach((buf) => this.writeVarSlice(buf)); - } - writeConfidentialInFields(input) { - this.writeVarSlice(input.issuanceRangeProof); - this.writeVarSlice(input.inflationRangeProof); - this.writeVector(input.witness); - this.writeVector(input.peginWitness); - } - writeConfidentialOutFields(output) { - this.writeVarSlice(output.surjectionProof); - this.writeVarSlice(output.rangeProof); - } + constructor(buffer, offset = 0) { + this.buffer = buffer; + this.offset = offset; + typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); + } + writeUInt8(i) { + this.offset = this.buffer.writeUInt8(i, this.offset); + } + writeInt32(i) { + this.offset = this.buffer.writeInt32LE(i, this.offset); + } + writeUInt32(i) { + this.offset = this.buffer.writeUInt32LE(i, this.offset); + } + writeUInt64(i) { + this.offset = writeUInt64LE(this.buffer, i, this.offset); + } + writeVarInt(i) { + varuint.encode(i, this.buffer, this.offset); + this.offset += varuint.encode.bytes; + } + writeSlice(slice) { + if (this.buffer.length < this.offset + slice.length) { + throw new Error('Cannot write slice out of bounds'); + } + this.offset += slice.copy(this.buffer, this.offset); + } + writeVarSlice(slice) { + this.writeVarInt(slice.length); + this.writeSlice(slice); + } + writeVector(vector) { + this.writeVarInt(vector.length); + vector.forEach(buf => this.writeVarSlice(buf)); + } + writeConfidentialInFields(input) { + this.writeVarSlice(input.issuanceRangeProof); + this.writeVarSlice(input.inflationRangeProof); + this.writeVector(input.witness); + this.writeVector(input.peginWitness); + } + writeConfidentialOutFields(output) { + this.writeVarSlice(output.surjectionProof); + this.writeVarSlice(output.rangeProof); + } } exports.BufferWriter = BufferWriter; /** * Helper class for reading of bitcoin data types from a buffer. */ class BufferReader { - constructor(buffer, offset = 0) { - this.buffer = buffer; - this.offset = offset; - typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); - } - readUInt8() { - const result = this.buffer.readUInt8(this.offset); - this.offset++; - return result; - } - readInt32() { - const result = this.buffer.readInt32LE(this.offset); - this.offset += 4; - return result; - } - readUInt32() { - const result = this.buffer.readUInt32LE(this.offset); - this.offset += 4; - return result; - } - readUInt64() { - const result = readUInt64LE(this.buffer, this.offset); - this.offset += 8; - return result; - } - readVarInt() { - const vi = varuint.decode(this.buffer, this.offset); - this.offset += varuint.decode.bytes; - return vi; - } - readSlice(n) { - if (this.buffer.length < this.offset + n) { - throw new Error('Cannot read slice out of bounds'); - } - const result = this.buffer.slice(this.offset, this.offset + n); - this.offset += n; - return result; - } - readVarSlice() { - return this.readSlice(this.readVarInt()); - } - readVector() { - const count = this.readVarInt(); - const vector = []; - for (let i = 0; i < count; i++) - vector.push(this.readVarSlice()); - return vector; - } - // CConfidentialAsset size 33, prefixA 10, prefixB 11 - readConfidentialAsset() { - const version = this.readUInt8(); - const versionBuffer = this.buffer.slice(this.offset - 1, this.offset); - if (version === 1 || version === 0xff) - return Buffer.concat([ - versionBuffer, - this.readSlice(CONFIDENTIAL_COMMITMENT - 1), - ]); - else if (version === 10 || version === 11) - return Buffer.concat([ - versionBuffer, - this.readSlice(CONFIDENTIAL_COMMITMENT - 1), - ]); - return versionBuffer; - } - // CConfidentialNonce size 33, prefixA 2, prefixB 3 - readConfidentialNonce() { - const version = this.readUInt8(); - const versionBuffer = this.buffer.slice(this.offset - 1, this.offset); - if (version === 1 || version === 0xff) - return Buffer.concat([ - versionBuffer, - this.readSlice(CONFIDENTIAL_COMMITMENT - 1), - ]); - else if (version === 2 || version === 3) - return Buffer.concat([ - versionBuffer, - this.readSlice(CONFIDENTIAL_COMMITMENT - 1), - ]); - return versionBuffer; - } - // CConfidentialValue size 9, prefixA 8, prefixB 9 - readConfidentialValue() { - const version = this.readUInt8(); - const versionBuffer = this.buffer.slice(this.offset - 1, this.offset); - if (version === 1 || version === 0xff) - return Buffer.concat([ - versionBuffer, - this.readSlice(CONFIDENTIAL_VALUE - 1), - ]); - else if (version === 8 || version === 9) - return Buffer.concat([ - versionBuffer, - this.readSlice(CONFIDENTIAL_COMMITMENT - 1), - ]); - return versionBuffer; - } - readConfidentialInFields() { - const issuanceRangeProof = this.readVarSlice(); - const inflationRangeProof = this.readVarSlice(); - const witness = this.readVector(); - const peginWitness = this.readVector(); - return { - issuanceRangeProof, - inflationRangeProof, - witness, - peginWitness, - }; - } - readConfidentialOutFields() { - const surjectionProof = this.readVarSlice(); - const rangeProof = this.readVarSlice(); - return { surjectionProof, rangeProof }; - } - readIssuance() { - const issuanceNonce = this.readSlice(32); - const issuanceEntropy = this.readSlice(32); - const amount = this.readConfidentialValue(); - const inflation = this.readConfidentialValue(); - return { - assetBlindingNonce: issuanceNonce, - assetEntropy: issuanceEntropy, - assetAmount: amount, - tokenAmount: inflation, - }; - } + constructor(buffer, offset = 0) { + this.buffer = buffer; + this.offset = offset; + typeforce(types.tuple(types.Buffer, types.UInt32), [buffer, offset]); + } + readUInt8() { + const result = this.buffer.readUInt8(this.offset); + this.offset++; + return result; + } + readInt32() { + const result = this.buffer.readInt32LE(this.offset); + this.offset += 4; + return result; + } + readUInt32() { + const result = this.buffer.readUInt32LE(this.offset); + this.offset += 4; + return result; + } + readUInt64() { + const result = readUInt64LE(this.buffer, this.offset); + this.offset += 8; + return result; + } + readVarInt() { + const vi = varuint.decode(this.buffer, this.offset); + this.offset += varuint.decode.bytes; + return vi; + } + readSlice(n) { + if (this.buffer.length < this.offset + n) { + throw new Error('Cannot read slice out of bounds'); + } + const result = this.buffer.slice(this.offset, this.offset + n); + this.offset += n; + return result; + } + readVarSlice() { + return this.readSlice(this.readVarInt()); + } + readVector() { + const count = this.readVarInt(); + const vector = []; + for (let i = 0; i < count; i++) vector.push(this.readVarSlice()); + return vector; + } + // CConfidentialAsset size 33, prefixA 10, prefixB 11 + readConfidentialAsset() { + const version = this.readUInt8(); + const versionBuffer = this.buffer.slice(this.offset - 1, this.offset); + if (version === 1 || version === 0xff) + return Buffer.concat([ + versionBuffer, + this.readSlice(CONFIDENTIAL_COMMITMENT - 1), + ]); + else if (version === 10 || version === 11) + return Buffer.concat([ + versionBuffer, + this.readSlice(CONFIDENTIAL_COMMITMENT - 1), + ]); + return versionBuffer; + } + // CConfidentialNonce size 33, prefixA 2, prefixB 3 + readConfidentialNonce() { + const version = this.readUInt8(); + const versionBuffer = this.buffer.slice(this.offset - 1, this.offset); + if (version === 1 || version === 0xff) + return Buffer.concat([ + versionBuffer, + this.readSlice(CONFIDENTIAL_COMMITMENT - 1), + ]); + else if (version === 2 || version === 3) + return Buffer.concat([ + versionBuffer, + this.readSlice(CONFIDENTIAL_COMMITMENT - 1), + ]); + return versionBuffer; + } + // CConfidentialValue size 9, prefixA 8, prefixB 9 + readConfidentialValue() { + const version = this.readUInt8(); + const versionBuffer = this.buffer.slice(this.offset - 1, this.offset); + if (version === 1 || version === 0xff) + return Buffer.concat([ + versionBuffer, + this.readSlice(CONFIDENTIAL_VALUE - 1), + ]); + else if (version === 8 || version === 9) + return Buffer.concat([ + versionBuffer, + this.readSlice(CONFIDENTIAL_COMMITMENT - 1), + ]); + return versionBuffer; + } + readConfidentialInFields() { + const issuanceRangeProof = this.readVarSlice(); + const inflationRangeProof = this.readVarSlice(); + const witness = this.readVector(); + const peginWitness = this.readVector(); + return { + issuanceRangeProof, + inflationRangeProof, + witness, + peginWitness, + }; + } + readConfidentialOutFields() { + const surjectionProof = this.readVarSlice(); + const rangeProof = this.readVarSlice(); + return { surjectionProof, rangeProof }; + } + readIssuance() { + const issuanceNonce = this.readSlice(32); + const issuanceEntropy = this.readSlice(32); + const amount = this.readConfidentialValue(); + const inflation = this.readConfidentialValue(); + return { + assetBlindingNonce: issuanceNonce, + assetEntropy: issuanceEntropy, + assetAmount: amount, + tokenAmount: inflation, + }; + } } exports.BufferReader = BufferReader; diff --git a/src/classify.js b/src/classify.js index 25a827793..71e619aa2 100644 --- a/src/classify.js +++ b/src/classify.js @@ -1,82 +1,76 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const script_1 = require("./script"); -const multisig = __importStar(require("./templates/multisig")); -const nullData = __importStar(require("./templates/nulldata")); -const pubKey = __importStar(require("./templates/pubkey")); -const pubKeyHash = __importStar(require("./templates/pubkeyhash")); -const scriptHash = __importStar(require("./templates/scripthash")); -const witnessCommitment = __importStar(require("./templates/witnesscommitment")); -const witnessPubKeyHash = __importStar(require("./templates/witnesspubkeyhash")); -const witnessScriptHash = __importStar(require("./templates/witnessscripthash")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const script_1 = require('./script'); +const multisig = __importStar(require('./templates/multisig')); +const nullData = __importStar(require('./templates/nulldata')); +const pubKey = __importStar(require('./templates/pubkey')); +const pubKeyHash = __importStar(require('./templates/pubkeyhash')); +const scriptHash = __importStar(require('./templates/scripthash')); +const witnessCommitment = __importStar( + require('./templates/witnesscommitment'), +); +const witnessPubKeyHash = __importStar( + require('./templates/witnesspubkeyhash'), +); +const witnessScriptHash = __importStar( + require('./templates/witnessscripthash'), +); const types = { - P2MS: 'multisig', - NONSTANDARD: 'nonstandard', - NULLDATA: 'nulldata', - P2PK: 'pubkey', - P2PKH: 'pubkeyhash', - P2SH: 'scripthash', - P2WPKH: 'witnesspubkeyhash', - P2WSH: 'witnessscripthash', - WITNESS_COMMITMENT: 'witnesscommitment', + P2MS: 'multisig', + NONSTANDARD: 'nonstandard', + NULLDATA: 'nulldata', + P2PK: 'pubkey', + P2PKH: 'pubkeyhash', + P2SH: 'scripthash', + P2WPKH: 'witnesspubkeyhash', + P2WSH: 'witnessscripthash', + WITNESS_COMMITMENT: 'witnesscommitment', }; exports.types = types; function classifyOutput(script) { - if (witnessPubKeyHash.output.check(script)) - return types.P2WPKH; - if (witnessScriptHash.output.check(script)) - return types.P2WSH; - if (pubKeyHash.output.check(script)) - return types.P2PKH; - if (scriptHash.output.check(script)) - return types.P2SH; - // XXX: optimization, below functions .decompile before use - const chunks = script_1.decompile(script); - if (!chunks) - throw new TypeError('Invalid script'); - if (multisig.output.check(chunks)) - return types.P2MS; - if (pubKey.output.check(chunks)) - return types.P2PK; - if (witnessCommitment.output.check(chunks)) - return types.WITNESS_COMMITMENT; - if (nullData.output.check(chunks)) - return types.NULLDATA; - return types.NONSTANDARD; + if (witnessPubKeyHash.output.check(script)) return types.P2WPKH; + if (witnessScriptHash.output.check(script)) return types.P2WSH; + if (pubKeyHash.output.check(script)) return types.P2PKH; + if (scriptHash.output.check(script)) return types.P2SH; + // XXX: optimization, below functions .decompile before use + const chunks = script_1.decompile(script); + if (!chunks) throw new TypeError('Invalid script'); + if (multisig.output.check(chunks)) return types.P2MS; + if (pubKey.output.check(chunks)) return types.P2PK; + if (witnessCommitment.output.check(chunks)) return types.WITNESS_COMMITMENT; + if (nullData.output.check(chunks)) return types.NULLDATA; + return types.NONSTANDARD; } exports.output = classifyOutput; function classifyInput(script, allowIncomplete) { - // XXX: optimization, below functions .decompile before use - const chunks = script_1.decompile(script); - if (!chunks) - throw new TypeError('Invalid script'); - if (pubKeyHash.input.check(chunks)) - return types.P2PKH; - if (scriptHash.input.check(chunks, allowIncomplete)) - return types.P2SH; - if (multisig.input.check(chunks, allowIncomplete)) - return types.P2MS; - if (pubKey.input.check(chunks)) - return types.P2PK; - return types.NONSTANDARD; + // XXX: optimization, below functions .decompile before use + const chunks = script_1.decompile(script); + if (!chunks) throw new TypeError('Invalid script'); + if (pubKeyHash.input.check(chunks)) return types.P2PKH; + if (scriptHash.input.check(chunks, allowIncomplete)) return types.P2SH; + if (multisig.input.check(chunks, allowIncomplete)) return types.P2MS; + if (pubKey.input.check(chunks)) return types.P2PK; + return types.NONSTANDARD; } exports.input = classifyInput; function classifyWitness(script, allowIncomplete) { - // XXX: optimization, below functions .decompile before use - const chunks = script_1.decompile(script); - if (!chunks) - throw new TypeError('Invalid script'); - if (witnessPubKeyHash.input.check(chunks)) - return types.P2WPKH; - if (witnessScriptHash.input.check(chunks, allowIncomplete)) - return types.P2WSH; - return types.NONSTANDARD; + // XXX: optimization, below functions .decompile before use + const chunks = script_1.decompile(script); + if (!chunks) throw new TypeError('Invalid script'); + if (witnessPubKeyHash.input.check(chunks)) return types.P2WPKH; + if (witnessScriptHash.input.check(chunks, allowIncomplete)) + return types.P2WSH; + return types.NONSTANDARD; } exports.witness = classifyWitness; diff --git a/src/confidential.js b/src/confidential.js index 4be84051e..fd67a5214 100644 --- a/src/confidential.js +++ b/src/confidential.js @@ -1,154 +1,271 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); +'use strict'; +var __awaiter = + (this && this.__awaiter) || + function(thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator['throw'](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done + ? resolve(result.value) + : new P(function(resolve) { + resolve(result.value); + }).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); }); -}; -var __importStar = (this && this.__importStar) || function (mod) { + }; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bufferutils = __importStar(require("./bufferutils")); -const crypto = __importStar(require("./crypto")); -const secp256k1_zkp_1 = __importDefault(require("@vulpemventures/secp256k1-zkp")); + }; +var __importDefault = + (this && this.__importDefault) || + function(mod) { + return mod && mod.__esModule ? mod : { default: mod }; + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bufferutils = __importStar(require('./bufferutils')); +const crypto = __importStar(require('./crypto')); +const secp256k1_zkp_1 = __importDefault( + require('@vulpemventures/secp256k1-zkp'), +); const secp256k1Promise = secp256k1_zkp_1.default(); function nonceHash(pubkey, privkey) { - return __awaiter(this, void 0, void 0, function* () { - const { ecdh } = yield secp256k1Promise; - return crypto.sha256(ecdh(pubkey, privkey)); - }); + return __awaiter(this, void 0, void 0, function*() { + const { ecdh } = yield secp256k1Promise; + return crypto.sha256(ecdh(pubkey, privkey)); + }); } -function valueBlindingFactor(inValues, outValues, inGenerators, outGenerators, inFactors, outFactors) { - return __awaiter(this, void 0, void 0, function* () { - const { pedersen } = yield secp256k1Promise; - const values = inValues.concat(outValues); - const nInputs = inValues.length; - const generators = inGenerators.concat(outGenerators); - const factors = inFactors.concat(outFactors); - return pedersen.blindGeneratorBlindSum(values, nInputs, generators, factors); - }); +function valueBlindingFactor( + inValues, + outValues, + inGenerators, + outGenerators, + inFactors, + outFactors, +) { + return __awaiter(this, void 0, void 0, function*() { + const { pedersen } = yield secp256k1Promise; + const values = inValues.concat(outValues); + const nInputs = inValues.length; + const generators = inGenerators.concat(outGenerators); + const factors = inFactors.concat(outFactors); + return pedersen.blindGeneratorBlindSum( + values, + nInputs, + generators, + factors, + ); + }); } exports.valueBlindingFactor = valueBlindingFactor; function valueCommitment(value, gen, factor) { - return __awaiter(this, void 0, void 0, function* () { - const { generator, pedersen } = yield secp256k1Promise; - const generatorParsed = generator.parse(gen); - const commit = pedersen.commit(factor, value, generatorParsed); - return pedersen.commitSerialize(commit); - }); + return __awaiter(this, void 0, void 0, function*() { + const { generator, pedersen } = yield secp256k1Promise; + const generatorParsed = generator.parse(gen); + const commit = pedersen.commit(factor, value, generatorParsed); + return pedersen.commitSerialize(commit); + }); } exports.valueCommitment = valueCommitment; function assetCommitment(asset, factor) { - return __awaiter(this, void 0, void 0, function* () { - const { generator } = yield secp256k1Promise; - const gen = generator.generateBlinded(asset, factor); - return generator.serialize(gen); - }); + return __awaiter(this, void 0, void 0, function*() { + const { generator } = yield secp256k1Promise; + const gen = generator.generateBlinded(asset, factor); + return generator.serialize(gen); + }); } exports.assetCommitment = assetCommitment; function unblindOutputWithKey(out, blindingPrivKey) { - return __awaiter(this, void 0, void 0, function* () { - const nonce = yield nonceHash(out.nonce, blindingPrivKey); - return unblindOutputWithNonce(out, nonce); - }); + return __awaiter(this, void 0, void 0, function*() { + const nonce = yield nonceHash(out.nonce, blindingPrivKey); + return unblindOutputWithNonce(out, nonce); + }); } exports.unblindOutputWithKey = unblindOutputWithKey; function unblindOutputWithNonce(out, nonce) { - return __awaiter(this, void 0, void 0, function* () { - const secp = yield secp256k1Promise; - const gen = secp.generator.parse(out.asset); - const { value, blindFactor, message } = secp.rangeproof.rewind(out.value, out.rangeProof, nonce, gen, out.script); - return { - value, - asset: message.slice(0, 32), - valueBlindingFactor: blindFactor, - assetBlindingFactor: message.slice(32), - }; - }); + return __awaiter(this, void 0, void 0, function*() { + const secp = yield secp256k1Promise; + const gen = secp.generator.parse(out.asset); + const { value, blindFactor, message } = secp.rangeproof.rewind( + out.value, + out.rangeProof, + nonce, + gen, + out.script, + ); + return { + value, + asset: message.slice(0, 32), + valueBlindingFactor: blindFactor, + assetBlindingFactor: message.slice(32), + }; + }); } exports.unblindOutputWithNonce = unblindOutputWithNonce; function rangeProofInfo(proof) { - return __awaiter(this, void 0, void 0, function* () { - const { rangeproof } = yield secp256k1Promise; - const { exp, mantissa, minValue, maxValue } = rangeproof.info(proof); - return { - minValue: parseInt(minValue, 10), - maxValue: parseInt(maxValue, 10), - ctExp: exp, - ctBits: parseInt(mantissa, 10), - }; - }); + return __awaiter(this, void 0, void 0, function*() { + const { rangeproof } = yield secp256k1Promise; + const { exp, mantissa, minValue, maxValue } = rangeproof.info(proof); + return { + minValue: parseInt(minValue, 10), + maxValue: parseInt(maxValue, 10), + ctExp: exp, + ctBits: parseInt(mantissa, 10), + }; + }); } exports.rangeProofInfo = rangeProofInfo; /** * nonceHash from blinding key + ephemeral key and then rangeProof computation */ -function rangeProofWithNonceHash(value, blindingPubkey, ephemeralPrivkey, asset, assetBlindingFactor, valueBlindFactor, valueCommit, scriptPubkey, minValue, exp, minBits) { - return __awaiter(this, void 0, void 0, function* () { - const nonce = yield nonceHash(blindingPubkey, ephemeralPrivkey); - return rangeProof(value, nonce, asset, assetBlindingFactor, valueBlindFactor, valueCommit, scriptPubkey, minValue, exp, minBits); - }); +function rangeProofWithNonceHash( + value, + blindingPubkey, + ephemeralPrivkey, + asset, + assetBlindingFactor, + valueBlindFactor, + valueCommit, + scriptPubkey, + minValue, + exp, + minBits, +) { + return __awaiter(this, void 0, void 0, function*() { + const nonce = yield nonceHash(blindingPubkey, ephemeralPrivkey); + return rangeProof( + value, + nonce, + asset, + assetBlindingFactor, + valueBlindFactor, + valueCommit, + scriptPubkey, + minValue, + exp, + minBits, + ); + }); } exports.rangeProofWithNonceHash = rangeProofWithNonceHash; /** * rangeProof computation without nonceHash step. */ -function rangeProof(value, nonce, asset, assetBlindingFactor, valueBlindFactor, valueCommit, scriptPubkey, minValue, exp, minBits) { - return __awaiter(this, void 0, void 0, function* () { - const { generator, pedersen, rangeproof } = yield secp256k1Promise; - const gen = generator.generateBlinded(asset, assetBlindingFactor); - const message = Buffer.concat([asset, assetBlindingFactor]); - const commit = pedersen.commitParse(valueCommit); - const mv = minValue ? minValue : '1'; - const e = exp ? exp : 0; - const mb = minBits ? minBits : 36; - return rangeproof.sign(commit, valueBlindFactor, nonce, value, gen, mv, e, mb, message, scriptPubkey); - }); +function rangeProof( + value, + nonce, + asset, + assetBlindingFactor, + valueBlindFactor, + valueCommit, + scriptPubkey, + minValue, + exp, + minBits, +) { + return __awaiter(this, void 0, void 0, function*() { + const { generator, pedersen, rangeproof } = yield secp256k1Promise; + const gen = generator.generateBlinded(asset, assetBlindingFactor); + const message = Buffer.concat([asset, assetBlindingFactor]); + const commit = pedersen.commitParse(valueCommit); + const mv = minValue ? minValue : '1'; + const e = exp ? exp : 0; + const mb = minBits ? minBits : 36; + return rangeproof.sign( + commit, + valueBlindFactor, + nonce, + value, + gen, + mv, + e, + mb, + message, + scriptPubkey, + ); + }); } exports.rangeProof = rangeProof; -function surjectionProof(outputAsset, outputAssetBlindingFactor, inputAssets, inputAssetBlindingFactors, seed) { - return __awaiter(this, void 0, void 0, function* () { - const { generator, surjectionproof } = yield secp256k1Promise; - const outputGenerator = generator.generateBlinded(outputAsset, outputAssetBlindingFactor); - const inputGenerators = inputAssets.map((v, i) => generator.generateBlinded(v, inputAssetBlindingFactors[i])); - const nInputsToUse = inputAssets.length > 3 ? 3 : inputAssets.length; - const maxIterations = 100; - const init = surjectionproof.initialize(inputAssets, nInputsToUse, outputAsset, maxIterations, seed); - const proof = surjectionproof.generate(init.proof, inputGenerators, outputGenerator, init.inputIndex, inputAssetBlindingFactors[init.inputIndex], outputAssetBlindingFactor); - return surjectionproof.serialize(proof); - }); +function surjectionProof( + outputAsset, + outputAssetBlindingFactor, + inputAssets, + inputAssetBlindingFactors, + seed, +) { + return __awaiter(this, void 0, void 0, function*() { + const { generator, surjectionproof } = yield secp256k1Promise; + const outputGenerator = generator.generateBlinded( + outputAsset, + outputAssetBlindingFactor, + ); + const inputGenerators = inputAssets.map((v, i) => + generator.generateBlinded(v, inputAssetBlindingFactors[i]), + ); + const nInputsToUse = inputAssets.length > 3 ? 3 : inputAssets.length; + const maxIterations = 100; + const init = surjectionproof.initialize( + inputAssets, + nInputsToUse, + outputAsset, + maxIterations, + seed, + ); + const proof = surjectionproof.generate( + init.proof, + inputGenerators, + outputGenerator, + init.inputIndex, + inputAssetBlindingFactors[init.inputIndex], + outputAssetBlindingFactor, + ); + return surjectionproof.serialize(proof); + }); } exports.surjectionProof = surjectionProof; const CONFIDENTIAL_VALUE = 9; // explicit size of confidential values function confidentialValueToSatoshi(value) { - if (!isUnconfidentialValue(value)) { - throw new Error('Value must be unconfidential, length or the prefix are not valid'); - } - const reverseValueBuffer = Buffer.allocUnsafe(CONFIDENTIAL_VALUE - 1); - value.slice(1, CONFIDENTIAL_VALUE).copy(reverseValueBuffer, 0); - bufferutils.reverseBuffer(reverseValueBuffer); - return bufferutils.readUInt64LE(reverseValueBuffer, 0); + if (!isUnconfidentialValue(value)) { + throw new Error( + 'Value must be unconfidential, length or the prefix are not valid', + ); + } + const reverseValueBuffer = Buffer.allocUnsafe(CONFIDENTIAL_VALUE - 1); + value.slice(1, CONFIDENTIAL_VALUE).copy(reverseValueBuffer, 0); + bufferutils.reverseBuffer(reverseValueBuffer); + return bufferutils.readUInt64LE(reverseValueBuffer, 0); } exports.confidentialValueToSatoshi = confidentialValueToSatoshi; function satoshiToConfidentialValue(amount) { - const unconfPrefix = Buffer.allocUnsafe(1); - const valueBuffer = Buffer.allocUnsafe(CONFIDENTIAL_VALUE - 1); - unconfPrefix.writeUInt8(1, 0); - bufferutils.writeUInt64LE(valueBuffer, amount, 0); - return Buffer.concat([unconfPrefix, bufferutils.reverseBuffer(valueBuffer)]); + const unconfPrefix = Buffer.allocUnsafe(1); + const valueBuffer = Buffer.allocUnsafe(CONFIDENTIAL_VALUE - 1); + unconfPrefix.writeUInt8(1, 0); + bufferutils.writeUInt64LE(valueBuffer, amount, 0); + return Buffer.concat([unconfPrefix, bufferutils.reverseBuffer(valueBuffer)]); } exports.satoshiToConfidentialValue = satoshiToConfidentialValue; function isUnconfidentialValue(value) { - return value.length === CONFIDENTIAL_VALUE && value.readUInt8(0) === 1; + return value.length === CONFIDENTIAL_VALUE && value.readUInt8(0) === 1; } exports.isUnconfidentialValue = isUnconfidentialValue; diff --git a/src/crypto.js b/src/crypto.js index 38ec4f9b1..e7dd596bd 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -1,36 +1,35 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); const createHash = require('create-hash'); function ripemd160(buffer) { - try { - return createHash('rmd160') - .update(buffer) - .digest(); - } - catch (err) { - return createHash('ripemd160') - .update(buffer) - .digest(); - } + try { + return createHash('rmd160') + .update(buffer) + .digest(); + } catch (err) { + return createHash('ripemd160') + .update(buffer) + .digest(); + } } exports.ripemd160 = ripemd160; function sha1(buffer) { - return createHash('sha1') - .update(buffer) - .digest(); + return createHash('sha1') + .update(buffer) + .digest(); } exports.sha1 = sha1; function sha256(buffer) { - return createHash('sha256') - .update(buffer) - .digest(); + return createHash('sha256') + .update(buffer) + .digest(); } exports.sha256 = sha256; function hash160(buffer) { - return ripemd160(sha256(buffer)); + return ripemd160(sha256(buffer)); } exports.hash160 = hash160; function hash256(buffer) { - return sha256(sha256(buffer)); + return sha256(sha256(buffer)); } exports.hash256 = hash256; diff --git a/src/ecpair.js b/src/ecpair.js index 19613c6b7..f96e8243d 100644 --- a/src/ecpair.js +++ b/src/ecpair.js @@ -1,123 +1,118 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const NETWORKS = __importStar(require("./networks")); -const types = __importStar(require("./types")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const NETWORKS = __importStar(require('./networks')); +const types = __importStar(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({ +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.liquid; - 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); + 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.liquid; + 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); + 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); + 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.liquid; - if (version !== network.wif) - throw new Error('Invalid network version'); - } - return fromPrivateKey(decoded.privateKey, { - compressed: decoded.compressed, - network: 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.liquid; + 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); + 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/src/index.js b/src/index.js index ec8c16b80..125764819 100644 --- a/src/index.js +++ b/src/index.js @@ -1,33 +1,37 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bip32 = __importStar(require("bip32")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bip32 = __importStar(require('bip32')); exports.bip32 = bip32; -const address = __importStar(require("./address")); +const address = __importStar(require('./address')); exports.address = address; -const confidential = __importStar(require("./confidential")); +const confidential = __importStar(require('./confidential')); exports.confidential = confidential; -const crypto = __importStar(require("./crypto")); +const crypto = __importStar(require('./crypto')); exports.crypto = crypto; -const ECPair = __importStar(require("./ecpair")); +const ECPair = __importStar(require('./ecpair')); exports.ECPair = ECPair; -const networks = __importStar(require("./networks")); +const networks = __importStar(require('./networks')); exports.networks = networks; -const payments = __importStar(require("./payments")); +const payments = __importStar(require('./payments')); exports.payments = payments; -const script = __importStar(require("./script")); +const script = __importStar(require('./script')); exports.script = script; -var block_1 = require("./block"); +var block_1 = require('./block'); exports.Block = block_1.Block; -var psbt_1 = require("./psbt"); +var psbt_1 = require('./psbt'); exports.Psbt = psbt_1.Psbt; -var script_1 = require("./script"); +var script_1 = require('./script'); exports.opcodes = script_1.OPS; -var transaction_1 = require("./transaction"); +var transaction_1 = require('./transaction'); exports.Transaction = transaction_1.Transaction; diff --git a/src/issuance.js b/src/issuance.js index cf739d1d0..64ff7e670 100644 --- a/src/issuance.js +++ b/src/issuance.js @@ -1,25 +1,28 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const address_1 = require("./address"); -const bufferutils_1 = require("./bufferutils"); -const confidential_1 = require("./confidential"); -const bcrypto = __importStar(require("./crypto")); -const sha256d_1 = require("./sha256d"); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const address_1 = require('./address'); +const bufferutils_1 = require('./bufferutils'); +const confidential_1 = require('./confidential'); +const bcrypto = __importStar(require('./crypto')); +const sha256d_1 = require('./sha256d'); /** * returns true if the issuance's token amount is not 0x00 or null buffer. * @param issuance issuance to test */ function hasTokenAmount(issuance) { - if (issuance.tokenAmount && issuance.tokenAmount.length > 1) - return true; - return false; + if (issuance.tokenAmount && issuance.tokenAmount.length > 1) return true; + return false; } exports.hasTokenAmount = hasTokenAmount; /** @@ -27,8 +30,8 @@ exports.hasTokenAmount = hasTokenAmount; * @param contract contract to validate. */ function validateIssuanceContract(contract) { - const precisionIsValid = contract.precision >= 0 && contract.precision <= 8; - return precisionIsValid; + const precisionIsValid = contract.precision >= 0 && contract.precision <= 8; + return precisionIsValid; } exports.validateIssuanceContract = validateIssuanceContract; /** @@ -36,9 +39,9 @@ exports.validateIssuanceContract = validateIssuanceContract; * @param contract the contract to digest. */ function hashContract(contract) { - if (!validateIssuanceContract(contract)) - throw new Error('Invalid asset contract'); - return bcrypto.sha256(Buffer.from(JSON.stringify(contract))); + if (!validateIssuanceContract(contract)) + throw new Error('Invalid asset contract'); + return bcrypto.sha256(Buffer.from(JSON.stringify(contract))); } exports.hashContract = hashContract; /** @@ -49,26 +52,23 @@ exports.hashContract = hashContract; * @param contract the asset ricarding contract of the issuance. */ function newIssuance(assetAmount, tokenAmount, precision = 8, contract) { - if (assetAmount < 0) - throw new Error('Invalid asset amount'); - if (tokenAmount < 0) - throw new Error('Invalid token amount'); - if (precision < 0 || precision > 8) - throw new Error('Invalid precision'); - let contractHash = Buffer.alloc(32); - if (contract) { - if (contract.precision !== precision) - throw new Error('precision is not equal to the asset contract precision'); - contractHash = hashContract(contract); - } - const iss = { - assetAmount: toConfidentialAssetAmount(assetAmount, precision), - tokenAmount: toConfidentialTokenAmount(tokenAmount, precision), - assetBlindingNonce: Buffer.alloc(32), - // in case of issuance, the asset entropy = the contract hash. - assetEntropy: contractHash, - }; - return iss; + if (assetAmount < 0) throw new Error('Invalid asset amount'); + if (tokenAmount < 0) throw new Error('Invalid token amount'); + if (precision < 0 || precision > 8) throw new Error('Invalid precision'); + let contractHash = Buffer.alloc(32); + if (contract) { + if (contract.precision !== precision) + throw new Error('precision is not equal to the asset contract precision'); + contractHash = hashContract(contract); + } + const iss = { + assetAmount: toConfidentialAssetAmount(assetAmount, precision), + tokenAmount: toConfidentialTokenAmount(tokenAmount, precision), + assetBlindingNonce: Buffer.alloc(32), + // in case of issuance, the asset entropy = the contract hash. + assetEntropy: contractHash, + }; + return iss; } exports.newIssuance = newIssuance; /** @@ -77,16 +77,16 @@ exports.newIssuance = newIssuance; * @param contractHash the 32 bytes contract hash. */ function generateEntropy(outPoint, contractHash = Buffer.alloc(32)) { - if (outPoint.txHash.length !== 32) { - throw new Error('Invalid txHash length'); - } - const tBuffer = Buffer.allocUnsafe(36); - const s = new bufferutils_1.BufferWriter(tBuffer, 0); - s.writeSlice(outPoint.txHash); - s.writeInt32(outPoint.vout); - const prevoutHash = bcrypto.hash256(s.buffer); - const concatened = Buffer.concat([prevoutHash, contractHash]); - return sha256d_1.sha256Midstate(concatened); + if (outPoint.txHash.length !== 32) { + throw new Error('Invalid txHash length'); + } + const tBuffer = Buffer.allocUnsafe(36); + const s = new bufferutils_1.BufferWriter(tBuffer, 0); + s.writeSlice(outPoint.txHash); + s.writeInt32(outPoint.vout); + const prevoutHash = bcrypto.hash256(s.buffer); + const concatened = Buffer.concat([prevoutHash, contractHash]); + return sha256d_1.sha256Midstate(concatened); } exports.generateEntropy = generateEntropy; /** @@ -94,10 +94,9 @@ exports.generateEntropy = generateEntropy; * @param entropy the entropy used to compute the asset tag. */ function calculateAsset(entropy) { - if (entropy.length !== 32) - throw new Error('Invalid entropy length'); - const kZero = Buffer.alloc(32); - return sha256d_1.sha256Midstate(Buffer.concat([entropy, kZero])); + if (entropy.length !== 32) throw new Error('Invalid entropy length'); + const kZero = Buffer.alloc(32); + return sha256d_1.sha256Midstate(Buffer.concat([entropy, kZero])); } exports.calculateAsset = calculateAsset; /** @@ -106,19 +105,19 @@ exports.calculateAsset = calculateAsset; * @param confidential true if confidential. */ function calculateReissuanceToken(entropy, confidential = false) { - if (entropy.length !== 32) - throw new Error('Invalid entropy length'); - return sha256d_1.sha256Midstate(Buffer.concat([ - entropy, - Buffer.of(getTokenFlag(confidential) + 1), - Buffer.alloc(31), - ])); + if (entropy.length !== 32) throw new Error('Invalid entropy length'); + return sha256d_1.sha256Midstate( + Buffer.concat([ + entropy, + Buffer.of(getTokenFlag(confidential) + 1), + Buffer.alloc(31), + ]), + ); } exports.calculateReissuanceToken = calculateReissuanceToken; function getTokenFlag(confidential) { - if (confidential) - return 1; - return 0; + if (confidential) return 1; + return 0; } /** * converts asset amount to confidential value. @@ -126,8 +125,8 @@ function getTokenFlag(confidential) { * @param precision the precision, 8 by default. */ function toConfidentialAssetAmount(assetAmount, precision = 8) { - const amount = Math.pow(10, precision) * assetAmount; - return confidential_1.satoshiToConfidentialValue(amount); + const amount = Math.pow(10, precision) * assetAmount; + return confidential_1.satoshiToConfidentialValue(amount); } /** * converts token amount to confidential value. @@ -135,19 +134,22 @@ function toConfidentialAssetAmount(assetAmount, precision = 8) { * @param precision the precision, 8 by default. */ function toConfidentialTokenAmount(tokenAmount, precision = 8) { - if (tokenAmount === 0) - return Buffer.from('00', 'hex'); - return toConfidentialAssetAmount(tokenAmount, precision); + if (tokenAmount === 0) return Buffer.from('00', 'hex'); + return toConfidentialAssetAmount(tokenAmount, precision); } function validateAddIssuanceArgs(args) { - if (args.assetAmount <= 0) - throw new Error('asset amount must be greater than zero.'); - if (args.tokenAmount < 0) - throw new Error('token amount must be positive.'); - if (args.tokenAddress) { - if (address_1.isConfidential(args.assetAddress) !== address_1.isConfidential(args.tokenAddress)) { - throw new Error('tokenAddress and assetAddress are not of the same type (confidential or unconfidential).'); - } + if (args.assetAmount <= 0) + throw new Error('asset amount must be greater than zero.'); + if (args.tokenAmount < 0) throw new Error('token amount must be positive.'); + if (args.tokenAddress) { + if ( + address_1.isConfidential(args.assetAddress) !== + address_1.isConfidential(args.tokenAddress) + ) { + throw new Error( + 'tokenAddress and assetAddress are not of the same type (confidential or unconfidential).', + ); } + } } exports.validateAddIssuanceArgs = validateAddIssuanceArgs; diff --git a/src/networks.js b/src/networks.js index 0200bf8b0..66872d2d1 100644 --- a/src/networks.js +++ b/src/networks.js @@ -1,30 +1,30 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); exports.liquid = { - messagePrefix: '\x18Liquid Signed Message:\n', - bech32: 'ex', - blech32: 'lq', - bip32: { - public: 0x0488b21e, - private: 0x0488ade4, - }, - pubKeyHash: 57, - scriptHash: 39, - wif: 0x80, - confidentialPrefix: 12, - assetHash: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d', + messagePrefix: '\x18Liquid Signed Message:\n', + bech32: 'ex', + blech32: 'lq', + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 57, + scriptHash: 39, + wif: 0x80, + confidentialPrefix: 12, + assetHash: '6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d', }; exports.regtest = { - messagePrefix: '\x18Liquid Signed Message:\n', - bech32: 'ert', - blech32: 'el', - bip32: { - public: 0x043587cf, - private: 0x04358394, - }, - pubKeyHash: 235, - scriptHash: 75, - wif: 0xef, - confidentialPrefix: 4, - assetHash: '5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225', + messagePrefix: '\x18Liquid Signed Message:\n', + bech32: 'ert', + blech32: 'el', + bip32: { + public: 0x043587cf, + private: 0x04358394, + }, + pubKeyHash: 235, + scriptHash: 75, + wif: 0xef, + confidentialPrefix: 4, + assetHash: '5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225', }; diff --git a/src/payments/embed.js b/src/payments/embed.js index df4c9bc57..817de800a 100644 --- a/src/payments/embed.js +++ b/src/payments/embed.js @@ -1,58 +1,60 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const networks_1 = require("../networks"); -const bscript = __importStar(require("../script")); -const lazy = __importStar(require("./lazy")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const networks_1 = require('../networks'); +const bscript = __importStar(require('../script')); +const lazy = __importStar(require('./lazy')); const typef = require('typeforce'); const OPS = bscript.OPS; function stacksEqual(a, b) { - if (a.length !== b.length) - return false; - return a.every((x, i) => { - return x.equals(b[i]); - }); + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); } // output: OP_RETURN ... function p2data(a, opts) { - if (!a.data && !a.output) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef({ - network: typef.maybe(typef.Object), - output: typef.maybe(typef.Buffer), - data: typef.maybe(typef.arrayOf(typef.Buffer)), - }, a); - const network = a.network || networks_1.liquid; - const o = { name: 'embed', network }; - lazy.prop(o, 'output', () => { - if (!a.data) - return; - return bscript.compile([OPS.OP_RETURN].concat(a.data)); - }); - lazy.prop(o, 'data', () => { - if (!a.output) - return; - return bscript.decompile(a.output).slice(1); - }); - // extended validation - if (opts.validate) { - 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)) - throw new TypeError('Output is invalid'); - if (a.data && !stacksEqual(a.data, o.data)) - throw new TypeError('Data mismatch'); - } + if (!a.data && !a.output) throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + network: typef.maybe(typef.Object), + output: typef.maybe(typef.Buffer), + data: typef.maybe(typef.arrayOf(typef.Buffer)), + }, + a, + ); + const network = a.network || networks_1.liquid; + const o = { name: 'embed', network }; + lazy.prop(o, 'output', () => { + if (!a.data) return; + return bscript.compile([OPS.OP_RETURN].concat(a.data)); + }); + lazy.prop(o, 'data', () => { + if (!a.output) return; + return bscript.decompile(a.output).slice(1); + }); + // extended validation + if (opts.validate) { + 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)) + throw new TypeError('Output is invalid'); + if (a.data && !stacksEqual(a.data, o.data)) + throw new TypeError('Data mismatch'); } - return Object.assign(o, a); + } + return Object.assign(o, a); } exports.p2data = p2data; diff --git a/src/payments/index.js b/src/payments/index.js index f21762ddd..ddab97768 100644 --- a/src/payments/index.js +++ b/src/payments/index.js @@ -1,18 +1,18 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const embed_1 = require("./embed"); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); +const embed_1 = require('./embed'); exports.embed = embed_1.p2data; -const p2ms_1 = require("./p2ms"); +const p2ms_1 = require('./p2ms'); exports.p2ms = p2ms_1.p2ms; -const p2pk_1 = require("./p2pk"); +const p2pk_1 = require('./p2pk'); exports.p2pk = p2pk_1.p2pk; -const p2pkh_1 = require("./p2pkh"); +const p2pkh_1 = require('./p2pkh'); exports.p2pkh = p2pkh_1.p2pkh; -const p2sh_1 = require("./p2sh"); +const p2sh_1 = require('./p2sh'); exports.p2sh = p2sh_1.p2sh; -const p2wpkh_1 = require("./p2wpkh"); +const p2wpkh_1 = require('./p2wpkh'); exports.p2wpkh = p2wpkh_1.p2wpkh; -const p2wsh_1 = require("./p2wsh"); +const p2wsh_1 = require('./p2wsh'); exports.p2wsh = p2wsh_1.p2wsh; // TODO // witness commitment diff --git a/src/payments/lazy.js b/src/payments/lazy.js index d8494fdda..1a7152158 100644 --- a/src/payments/lazy.js +++ b/src/payments/lazy.js @@ -1,32 +1,31 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); function prop(object, name, f) { - Object.defineProperty(object, name, { + Object.defineProperty(object, name, { + configurable: true, + enumerable: true, + get() { + const _value = f.call(this); + this[name] = _value; + return _value; + }, + set(_value) { + Object.defineProperty(this, name, { configurable: true, enumerable: true, - get() { - const _value = f.call(this); - this[name] = _value; - return _value; - }, - set(_value) { - Object.defineProperty(this, name, { - configurable: true, - enumerable: true, - value: _value, - writable: true, - }); - }, - }); + value: _value, + writable: true, + }); + }, + }); } exports.prop = prop; function value(f) { - let _value; - return () => { - if (_value !== undefined) - return _value; - _value = f(); - return _value; - }; + let _value; + return () => { + if (_value !== undefined) return _value; + _value = f(); + return _value; + }; } exports.value = value; diff --git a/src/payments/p2ms.js b/src/payments/p2ms.js index ad05744d0..a441379c5 100644 --- a/src/payments/p2ms.js +++ b/src/payments/p2ms.js @@ -1,153 +1,156 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const networks_1 = require("../networks"); -const bscript = __importStar(require("../script")); -const lazy = __importStar(require("./lazy")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const networks_1 = require('../networks'); +const bscript = __importStar(require('../script')); +const lazy = __importStar(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; - return a.every((x, i) => { - return x.equals(b[i]); - }); + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); } // input: OP_0 [signatures ...] // output: m [pubKeys ...] n OP_CHECKMULTISIG function p2ms(a, opts) { - if (!a.input && - !a.output && - !(a.pubkeys && a.m !== undefined) && - !a.signatures) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - function isAcceptableSignature(x) { - return (bscript.isCanonicalScriptSignature(x) || - (opts.allowIncomplete && x === OPS.OP_0) !== undefined); + if ( + !a.input && + !a.output && + !(a.pubkeys && a.m !== undefined) && + !a.signatures + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + function isAcceptableSignature(x) { + return ( + bscript.isCanonicalScriptSignature(x) || + (opts.allowIncomplete && x === OPS.OP_0) !== undefined + ); + } + typef( + { + 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), + }, + a, + ); + const network = a.network || networks_1.liquid; + const o = { network }; + let chunks = []; + let decoded = false; + function decode(output) { + if (decoded) return; + decoded = true; + chunks = bscript.decompile(output); + o.m = chunks[0] - OP_INT_BASE; + o.n = chunks[chunks.length - 2] - OP_INT_BASE; + o.pubkeys = chunks.slice(1, -2); + } + lazy.prop(o, 'output', () => { + if (!a.m) return; + if (!o.n) return; + if (!a.pubkeys) return; + return bscript.compile( + [].concat( + OP_INT_BASE + a.m, + a.pubkeys, + OP_INT_BASE + o.n, + OPS.OP_CHECKMULTISIG, + ), + ); + }); + lazy.prop(o, 'm', () => { + if (!o.output) return; + decode(o.output); + return o.m; + }); + lazy.prop(o, 'n', () => { + if (!o.pubkeys) return; + return o.pubkeys.length; + }); + lazy.prop(o, 'pubkeys', () => { + if (!a.output) return; + decode(a.output); + return o.pubkeys; + }); + lazy.prop(o, 'signatures', () => { + if (!a.input) return; + return bscript.decompile(a.input).slice(1); + }); + lazy.prop(o, 'input', () => { + if (!a.signatures) return; + return bscript.compile([OPS.OP_0].concat(a.signatures)); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + lazy.prop(o, 'name', () => { + if (!o.m || !o.n) return; + return `p2ms(${o.m} of ${o.n})`; + }); + // extended validation + 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])) + 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))) + 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'); + if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) + throw new TypeError('Pubkeys mismatch'); } - typef({ - 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), - }, a); - const network = a.network || networks_1.liquid; - const o = { network }; - let chunks = []; - let decoded = false; - function decode(output) { - if (decoded) - return; - decoded = true; - chunks = bscript.decompile(output); - o.m = chunks[0] - OP_INT_BASE; - o.n = chunks[chunks.length - 2] - OP_INT_BASE; - o.pubkeys = chunks.slice(1, -2); + if (a.pubkeys) { + if (a.n !== undefined && a.n !== a.pubkeys.length) + throw new TypeError('Pubkey count mismatch'); + o.n = a.pubkeys.length; + if (o.n < o.m) throw new TypeError('Pubkey count cannot be less than m'); } - lazy.prop(o, 'output', () => { - if (!a.m) - return; - if (!o.n) - return; - if (!a.pubkeys) - return; - return bscript.compile([].concat(OP_INT_BASE + a.m, a.pubkeys, OP_INT_BASE + o.n, OPS.OP_CHECKMULTISIG)); - }); - lazy.prop(o, 'm', () => { - if (!o.output) - return; - decode(o.output); - return o.m; - }); - lazy.prop(o, 'n', () => { - if (!o.pubkeys) - return; - return o.pubkeys.length; - }); - lazy.prop(o, 'pubkeys', () => { - if (!a.output) - return; - decode(a.output); - return o.pubkeys; - }); - lazy.prop(o, 'signatures', () => { - if (!a.input) - return; - return bscript.decompile(a.input).slice(1); - }); - lazy.prop(o, 'input', () => { - if (!a.signatures) - return; - return bscript.compile([OPS.OP_0].concat(a.signatures)); - }); - lazy.prop(o, 'witness', () => { - if (!o.input) - return; - return []; - }); - lazy.prop(o, 'name', () => { - if (!o.m || !o.n) - return; - return `p2ms(${o.m} of ${o.n})`; - }); - // extended validation - 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])) - 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))) - 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'); - if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) - throw new TypeError('Pubkeys mismatch'); - } - if (a.pubkeys) { - if (a.n !== undefined && a.n !== a.pubkeys.length) - throw new TypeError('Pubkey count mismatch'); - o.n = a.pubkeys.length; - if (o.n < o.m) - throw new TypeError('Pubkey count cannot be less than m'); - } - if (a.signatures) { - if (a.signatures.length < o.m) - throw new TypeError('Not enough signatures provided'); - if (a.signatures.length > o.m) - throw new TypeError('Too many signatures provided'); - } - if (a.input) { - if (a.input[0] !== OPS.OP_0) - throw new TypeError('Input is invalid'); - if (o.signatures.length === 0 || - !o.signatures.every(isAcceptableSignature)) - throw new TypeError('Input has invalid signature(s)'); - if (a.signatures && !stacksEqual(a.signatures, o.signatures)) - throw new TypeError('Signature mismatch'); - if (a.m !== undefined && a.m !== a.signatures.length) - throw new TypeError('Signature count mismatch'); - } + if (a.signatures) { + if (a.signatures.length < o.m) + throw new TypeError('Not enough signatures provided'); + if (a.signatures.length > o.m) + throw new TypeError('Too many signatures provided'); } - return Object.assign(o, a); + if (a.input) { + if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid'); + if ( + o.signatures.length === 0 || + !o.signatures.every(isAcceptableSignature) + ) + throw new TypeError('Input has invalid signature(s)'); + if (a.signatures && !stacksEqual(a.signatures, o.signatures)) + throw new TypeError('Signature mismatch'); + if (a.m !== undefined && a.m !== a.signatures.length) + throw new TypeError('Signature count mismatch'); + } + } + return Object.assign(o, a); } exports.p2ms = p2ms; diff --git a/src/payments/p2pk.js b/src/payments/p2pk.js index bc9cebae0..fb874ece9 100644 --- a/src/payments/p2pk.js +++ b/src/payments/p2pk.js @@ -1,82 +1,83 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const networks_1 = require("../networks"); -const bscript = __importStar(require("../script")); -const lazy = __importStar(require("./lazy")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const networks_1 = require('../networks'); +const bscript = __importStar(require('../script')); +const lazy = __importStar(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({ - 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), - }, a); - const _chunks = lazy.value(() => { - return bscript.decompile(a.input); - }); - const network = a.network || networks_1.liquid; - const o = { name: 'p2pk', network }; - lazy.prop(o, 'output', () => { - if (!a.pubkey) - return; - return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]); - }); - lazy.prop(o, 'pubkey', () => { - if (!a.output) - return; - return a.output.slice(1, -1); - }); - lazy.prop(o, 'signature', () => { - if (!a.input) - return; - return _chunks()[0]; - }); - lazy.prop(o, 'input', () => { - if (!a.signature) - return; - return bscript.compile([a.signature]); - }); - lazy.prop(o, 'witness', () => { - if (!o.input) - return; - return []; - }); - // extended validation - if (opts.validate) { - 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 (a.pubkey && !a.pubkey.equals(o.pubkey)) - throw new TypeError('Pubkey mismatch'); - } - if (a.signature) { - if (a.input && !a.input.equals(o.input)) - throw new TypeError('Signature mismatch'); - } - if (a.input) { - if (_chunks().length !== 1) - throw new TypeError('Input is invalid'); - if (!bscript.isCanonicalScriptSignature(o.signature)) - throw new TypeError('Input has invalid signature'); - } + if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + 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), + }, + a, + ); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input); + }); + const network = a.network || networks_1.liquid; + const o = { name: 'p2pk', network }; + lazy.prop(o, 'output', () => { + if (!a.pubkey) return; + return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]); + }); + lazy.prop(o, 'pubkey', () => { + if (!a.output) return; + return a.output.slice(1, -1); + }); + lazy.prop(o, 'signature', () => { + if (!a.input) return; + return _chunks()[0]; + }); + lazy.prop(o, 'input', () => { + if (!a.signature) return; + return bscript.compile([a.signature]); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + // extended validation + if (opts.validate) { + 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 (a.pubkey && !a.pubkey.equals(o.pubkey)) + throw new TypeError('Pubkey mismatch'); } - return Object.assign(o, a); + if (a.signature) { + if (a.input && !a.input.equals(o.input)) + throw new TypeError('Signature mismatch'); + } + if (a.input) { + if (_chunks().length !== 1) throw new TypeError('Input is invalid'); + if (!bscript.isCanonicalScriptSignature(o.signature)) + throw new TypeError('Input has invalid signature'); + } + } + return Object.assign(o, a); } exports.p2pk = p2pk; diff --git a/src/payments/p2pkh.js b/src/payments/p2pkh.js index 07ac251d0..eed38a92e 100644 --- a/src/payments/p2pkh.js +++ b/src/payments/p2pkh.js @@ -1,16 +1,20 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bcrypto = __importStar(require("../crypto")); -const networks_1 = require("../networks"); -const bscript = __importStar(require("../script")); -const lazy = __importStar(require("./lazy")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bcrypto = __importStar(require('../crypto')); +const networks_1 = require('../networks'); +const bscript = __importStar(require('../script')); +const lazy = __importStar(require('./lazy')); const typef = require('typeforce'); const OPS = bscript.OPS; const ecc = require('tiny-secp256k1'); @@ -18,191 +22,180 @@ const bs58check = require('bs58check'); // 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 && - !a.confidentialAddress) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef({ - 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), - blindkey: typef.maybe(ecc.isPoint), - confidentialAddress: typef.maybe(typef.String), - }, a); - const _address = lazy.value(() => { - const payload = bs58check.decode(a.address); - const version = payload.readUInt8(0); - const hash = payload.slice(1); - return { version, hash }; - }); - const _chunks = lazy.value(() => { - return bscript.decompile(a.input); - }); - const _confidentialAddress = lazy.value(() => { - const payload = bs58check.decode(a.confidentialAddress); - const blindkey = payload.slice(2, 35); - const unconfidentialAddressBuffer = Buffer.concat([ - Buffer.from([payload.readUInt8(1)]), - payload.slice(35), - ]); - const unconfidentialAddress = bs58check.encode(unconfidentialAddressBuffer); - return { blindkey, unconfidentialAddress }; - }); - const network = a.network || networks_1.liquid; - const o = { name: 'p2pkh', network }; - lazy.prop(o, 'address', () => { - if (!o.hash) - return; - const payload = Buffer.allocUnsafe(21); - payload.writeUInt8(network.pubKeyHash, 0); - o.hash.copy(payload, 1); - return bs58check.encode(payload); - }); - lazy.prop(o, 'hash', () => { - if (a.output) - return a.output.slice(3, 23); - if (a.address) - return _address().hash; - if (a.pubkey || o.pubkey) - return bcrypto.hash160(a.pubkey || o.pubkey); - if (a.confidentialAddress) { - const address = _confidentialAddress().unconfidentialAddress; - return bs58check.decode(address).slice(1); - } - }); - lazy.prop(o, 'output', () => { - if (!o.hash) - return; - return bscript.compile([ - OPS.OP_DUP, - OPS.OP_HASH160, - o.hash, - OPS.OP_EQUALVERIFY, - OPS.OP_CHECKSIG, - ]); - }); - lazy.prop(o, 'pubkey', () => { - if (!a.input) - return; - return _chunks()[1]; - }); - lazy.prop(o, 'signature', () => { - if (!a.input) - return; - return _chunks()[0]; - }); - lazy.prop(o, 'input', () => { - if (!a.pubkey) - return; - if (!a.signature) - return; - return bscript.compile([a.signature, a.pubkey]); - }); - lazy.prop(o, 'witness', () => { - if (!o.input) - return; - return []; - }); - lazy.prop(o, 'blindkey', () => { - if (a.confidentialAddress) - return _confidentialAddress().blindkey; - if (a.blindkey) - return a.blindkey; - }); - lazy.prop(o, 'confidentialAddress', () => { - if (!o.address) - return; - if (!o.blindkey) - return; - const payload = bs58check.decode(o.address); - const confidentialAddress = Buffer.concat([ - Buffer.from([network.confidentialPrefix, payload.readUInt8(0)]), - o.blindkey, - Buffer.from(payload.slice(1)), - ]); - return bs58check.encode(confidentialAddress); - }); - // extended validation - if (opts.validate) { - let hash = Buffer.from([]); - let blindkey = Buffer.from([]); - if (a.address) { - if (_address().version !== network.pubKeyHash) - throw new TypeError('Invalid version or Network mismatch'); - if (_address().hash.length !== 20) - throw new TypeError('Invalid address'); - hash = _address().hash; - } - if (a.hash) { - if (hash.length > 0 && !hash.equals(a.hash)) - throw new TypeError('Hash mismatch'); - else - hash = a.hash; - } - if (a.output) { - if (a.output.length !== 25 || - a.output[0] !== OPS.OP_DUP || - a.output[1] !== OPS.OP_HASH160 || - a.output[2] !== 0x14 || - a.output[23] !== OPS.OP_EQUALVERIFY || - a.output[24] !== OPS.OP_CHECKSIG) - throw new TypeError('Output is invalid'); - const hash2 = a.output.slice(3, 23); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else - hash = hash2; - } - if (a.pubkey) { - const pkh = bcrypto.hash160(a.pubkey); - if (hash.length > 0 && !hash.equals(pkh)) - throw new TypeError('Hash mismatch'); - else - hash = pkh; - } - if (a.input) { - const chunks = _chunks(); - 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])) - throw new TypeError('Input has invalid pubkey'); - if (a.signature && !a.signature.equals(chunks[0])) - throw new TypeError('Signature mismatch'); - if (a.pubkey && !a.pubkey.equals(chunks[1])) - throw new TypeError('Pubkey mismatch'); - const pkh = bcrypto.hash160(chunks[1]); - if (hash.length > 0 && !hash.equals(pkh)) - throw new TypeError('Hash mismatch'); - } - if (a.confidentialAddress) { - if (a.address && - a.address !== _confidentialAddress().unconfidentialAddress) - throw new TypeError('Address mismatch'); - if (blindkey.length > 0 && - !blindkey.equals(_confidentialAddress().blindkey)) - throw new TypeError('Blindkey mismatch'); - else - blindkey = _confidentialAddress().blindkey; - } - if (a.blindkey) { - if (!ecc.isPoint(a.blindkey)) - throw new TypeError('Blindkey is invalid'); - if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) - throw new TypeError('Blindkey mismatch'); - else - blindkey = a.blindkey; - } + if ( + !a.address && + !a.hash && + !a.output && + !a.pubkey && + !a.input && + !a.confidentialAddress + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + 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), + blindkey: typef.maybe(ecc.isPoint), + confidentialAddress: typef.maybe(typef.String), + }, + a, + ); + const _address = lazy.value(() => { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input); + }); + const _confidentialAddress = lazy.value(() => { + const payload = bs58check.decode(a.confidentialAddress); + const blindkey = payload.slice(2, 35); + const unconfidentialAddressBuffer = Buffer.concat([ + Buffer.from([payload.readUInt8(1)]), + payload.slice(35), + ]); + const unconfidentialAddress = bs58check.encode(unconfidentialAddressBuffer); + return { blindkey, unconfidentialAddress }; + }); + const network = a.network || networks_1.liquid; + const o = { name: 'p2pkh', network }; + lazy.prop(o, 'address', () => { + if (!o.hash) return; + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(network.pubKeyHash, 0); + o.hash.copy(payload, 1); + return bs58check.encode(payload); + }); + lazy.prop(o, 'hash', () => { + if (a.output) return a.output.slice(3, 23); + if (a.address) return _address().hash; + if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey); + if (a.confidentialAddress) { + const address = _confidentialAddress().unconfidentialAddress; + return bs58check.decode(address).slice(1); } - return Object.assign(o, a); + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([ + OPS.OP_DUP, + OPS.OP_HASH160, + o.hash, + OPS.OP_EQUALVERIFY, + OPS.OP_CHECKSIG, + ]); + }); + lazy.prop(o, 'pubkey', () => { + if (!a.input) return; + return _chunks()[1]; + }); + lazy.prop(o, 'signature', () => { + if (!a.input) return; + return _chunks()[0]; + }); + lazy.prop(o, 'input', () => { + if (!a.pubkey) return; + if (!a.signature) return; + return bscript.compile([a.signature, a.pubkey]); + }); + lazy.prop(o, 'witness', () => { + if (!o.input) return; + return []; + }); + lazy.prop(o, 'blindkey', () => { + if (a.confidentialAddress) return _confidentialAddress().blindkey; + if (a.blindkey) return a.blindkey; + }); + lazy.prop(o, 'confidentialAddress', () => { + if (!o.address) return; + if (!o.blindkey) return; + const payload = bs58check.decode(o.address); + const confidentialAddress = Buffer.concat([ + Buffer.from([network.confidentialPrefix, payload.readUInt8(0)]), + o.blindkey, + Buffer.from(payload.slice(1)), + ]); + return bs58check.encode(confidentialAddress); + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + let blindkey = Buffer.from([]); + if (a.address) { + if (_address().version !== network.pubKeyHash) + throw new TypeError('Invalid version or Network mismatch'); + if (_address().hash.length !== 20) throw new TypeError('Invalid address'); + hash = _address().hash; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + if (a.output) { + if ( + a.output.length !== 25 || + a.output[0] !== OPS.OP_DUP || + a.output[1] !== OPS.OP_HASH160 || + a.output[2] !== 0x14 || + a.output[23] !== OPS.OP_EQUALVERIFY || + a.output[24] !== OPS.OP_CHECKSIG + ) + throw new TypeError('Output is invalid'); + const hash2 = a.output.slice(3, 23); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + if (a.pubkey) { + const pkh = bcrypto.hash160(a.pubkey); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + else hash = pkh; + } + if (a.input) { + const chunks = _chunks(); + 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])) + throw new TypeError('Input has invalid pubkey'); + if (a.signature && !a.signature.equals(chunks[0])) + throw new TypeError('Signature mismatch'); + if (a.pubkey && !a.pubkey.equals(chunks[1])) + throw new TypeError('Pubkey mismatch'); + const pkh = bcrypto.hash160(chunks[1]); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + } + if (a.confidentialAddress) { + if ( + a.address && + a.address !== _confidentialAddress().unconfidentialAddress + ) + throw new TypeError('Address mismatch'); + if ( + blindkey.length > 0 && + !blindkey.equals(_confidentialAddress().blindkey) + ) + throw new TypeError('Blindkey mismatch'); + else blindkey = _confidentialAddress().blindkey; + } + if (a.blindkey) { + if (!ecc.isPoint(a.blindkey)) throw new TypeError('Blindkey is invalid'); + if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) + throw new TypeError('Blindkey mismatch'); + else blindkey = a.blindkey; + } + } + return Object.assign(o, a); } exports.p2pkh = p2pkh; diff --git a/src/payments/p2sh.js b/src/payments/p2sh.js index 9b8f34f0c..d00ebcb3d 100644 --- a/src/payments/p2sh.js +++ b/src/payments/p2sh.js @@ -1,258 +1,253 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bcrypto = __importStar(require("../crypto")); -const networks_1 = require("../networks"); -const bscript = __importStar(require("../script")); -const lazy = __importStar(require("./lazy")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bcrypto = __importStar(require('../crypto')); +const networks_1 = require('../networks'); +const bscript = __importStar(require('../script')); +const lazy = __importStar(require('./lazy')); const typef = require('typeforce'); const OPS = bscript.OPS; const ecc = require('tiny-secp256k1'); const bs58check = require('bs58check'); function stacksEqual(a, b) { - if (a.length !== b.length) - return false; - return a.every((x, i) => { - return x.equals(b[i]); - }); + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); } // input: [redeemScriptSig ...] {redeemScript} // witness: // output: OP_HASH160 {hash160(redeemScript)} OP_EQUAL function p2sh(a, opts) { - if (!a.address && - !a.hash && - !a.output && - !a.redeem && - !a.input && - !a.confidentialAddress) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef({ + if ( + !a.address && + !a.hash && + !a.output && + !a.redeem && + !a.input && + !a.confidentialAddress + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + 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), - 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)), - }), + output: typef.maybe(typef.Buffer), input: typef.maybe(typef.Buffer), witness: typef.maybe(typef.arrayOf(typef.Buffer)), - blindkey: typef.maybe(ecc.isPoint), - confidentialAddress: typef.maybe(typef.String), - }, a); - let network = a.network; - if (!network) { - network = (a.redeem && a.redeem.network) || networks_1.liquid; + }), + input: typef.maybe(typef.Buffer), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + blindkey: typef.maybe(ecc.isPoint), + confidentialAddress: typef.maybe(typef.String), + }, + a, + ); + let network = a.network; + if (!network) { + network = (a.redeem && a.redeem.network) || networks_1.liquid; + } + const o = { network }; + const _address = lazy.value(() => { + const payload = bs58check.decode(a.address); + const version = payload.readUInt8(0); + const hash = payload.slice(1); + return { version, hash }; + }); + const _chunks = lazy.value(() => { + return bscript.decompile(a.input); + }); + const _redeem = lazy.value(() => { + const chunks = _chunks(); + return { + network, + output: chunks[chunks.length - 1], + input: bscript.compile(chunks.slice(0, -1)), + witness: a.witness || [], + }; + }); + const _confidentialAddress = lazy.value(() => { + const payload = bs58check.decode(a.confidentialAddress); + const blindkey = payload.slice(2, 35); + const unconfidentialAddressBuffer = Buffer.concat([ + Buffer.from([payload.readUInt8(1)]), + payload.slice(35), + ]); + const unconfidentialAddress = bs58check.encode(unconfidentialAddressBuffer); + return { blindkey, unconfidentialAddress }; + }); + // output dependents + lazy.prop(o, 'address', () => { + if (!o.hash) return; + const payload = Buffer.allocUnsafe(21); + payload.writeUInt8(o.network.scriptHash, 0); + o.hash.copy(payload, 1); + return bs58check.encode(payload); + }); + lazy.prop(o, 'hash', () => { + // in order of least effort + if (a.output) return a.output.slice(2, 22); + if (a.address) return _address().hash; + if (o.redeem && o.redeem.output) return bcrypto.hash160(o.redeem.output); + if (a.confidentialAddress) { + const address = _confidentialAddress().unconfidentialAddress; + return bs58check.decode(address).slice(1); } - const o = { network }; - const _address = lazy.value(() => { - const payload = bs58check.decode(a.address); - const version = payload.readUInt8(0); - const hash = payload.slice(1); - return { version, hash }; - }); - const _chunks = lazy.value(() => { - return bscript.decompile(a.input); - }); - const _redeem = lazy.value(() => { - const chunks = _chunks(); - return { - network, - output: chunks[chunks.length - 1], - input: bscript.compile(chunks.slice(0, -1)), - witness: a.witness || [], - }; - }); - const _confidentialAddress = lazy.value(() => { - const payload = bs58check.decode(a.confidentialAddress); - const blindkey = payload.slice(2, 35); - const unconfidentialAddressBuffer = Buffer.concat([ - Buffer.from([payload.readUInt8(1)]), - payload.slice(35), - ]); - const unconfidentialAddress = bs58check.encode(unconfidentialAddressBuffer); - return { blindkey, unconfidentialAddress }; - }); - // output dependents - lazy.prop(o, 'address', () => { - if (!o.hash) - return; - const payload = Buffer.allocUnsafe(21); - payload.writeUInt8(o.network.scriptHash, 0); - o.hash.copy(payload, 1); - return bs58check.encode(payload); - }); - lazy.prop(o, 'hash', () => { - // in order of least effort - if (a.output) - return a.output.slice(2, 22); - if (a.address) - return _address().hash; - if (o.redeem && o.redeem.output) - return bcrypto.hash160(o.redeem.output); - if (a.confidentialAddress) { - const address = _confidentialAddress().unconfidentialAddress; - return bs58check.decode(address).slice(1); - } - }); - lazy.prop(o, 'output', () => { - if (!o.hash) - return; - return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]); - }); - // input dependents - lazy.prop(o, 'redeem', () => { - if (!a.input) - return; - return _redeem(); - }); - lazy.prop(o, 'input', () => { - if (!a.redeem || !a.redeem.input || !a.redeem.output) - return; - return bscript.compile([].concat(bscript.decompile(a.redeem.input), a.redeem.output)); - }); - lazy.prop(o, 'witness', () => { - if (o.redeem && o.redeem.witness) - return o.redeem.witness; - if (o.input) - return []; - }); - lazy.prop(o, 'name', () => { - const nameParts = ['p2sh']; - if (o.redeem !== undefined) - nameParts.push(o.redeem.name); - return nameParts.join('-'); - }); - lazy.prop(o, 'blindkey', () => { - if (a.confidentialAddress) - return _confidentialAddress().blindkey; - if (a.blindkey) - return a.blindkey; - }); - lazy.prop(o, 'confidentialAddress', () => { - if (!o.address) - return; - if (!o.blindkey) - return; - const payload = bs58check.decode(o.address); - const confidentialAddress = Buffer.concat([ - Buffer.from([network.confidentialPrefix, payload.readUInt8(0)]), - o.blindkey, - Buffer.from(payload.slice(1)), - ]); - return bs58check.encode(confidentialAddress); - }); - if (opts.validate) { - let hash = Buffer.from([]); - let blindkey = Buffer.from([]); - if (a.address) { - if (_address().version !== network.scriptHash) - throw new TypeError('Invalid version or Network mismatch'); - if (_address().hash.length !== 20) - throw new TypeError('Invalid address'); - hash = _address().hash; - } - if (a.hash) { - if (hash.length > 0 && !hash.equals(a.hash)) - throw new TypeError('Hash mismatch'); - else - hash = a.hash; - } - if (a.output) { - if (a.output.length !== 23 || - a.output[0] !== OPS.OP_HASH160 || - a.output[1] !== 0x14 || - a.output[22] !== OPS.OP_EQUAL) - throw new TypeError('Output is invalid'); - const hash2 = a.output.slice(2, 22); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else - hash = hash2; - } - // inlined to prevent 'no-inner-declarations' failing - const checkRedeem = (redeem) => { - // is the redeem output empty/invalid? - if (redeem.output) { - const decompile = bscript.decompile(redeem.output); - if (!decompile || decompile.length < 1) - throw new TypeError('Redeem.output too short'); - // match hash against other sources - const hash2 = bcrypto.hash160(redeem.output); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else - hash = hash2; - } - if (redeem.input) { - const hasInput = redeem.input.length > 0; - const hasWitness = redeem.witness && redeem.witness.length > 0; - if (!hasInput && !hasWitness) - throw new TypeError('Empty input'); - if (hasInput && hasWitness) - throw new TypeError('Input and witness provided'); - if (hasInput) { - const richunks = bscript.decompile(redeem.input); - if (!bscript.isPushOnly(richunks)) - throw new TypeError('Non push-only scriptSig'); - } - } - }; - if (a.input) { - const chunks = _chunks(); - if (!chunks || chunks.length < 1) - throw new TypeError('Input too short'); - if (!Buffer.isBuffer(_redeem().output)) - throw new TypeError('Input is invalid'); - checkRedeem(_redeem()); - } - if (a.redeem) { - if (a.redeem.network && a.redeem.network !== network) - throw new TypeError('Network mismatch'); - if (a.input) { - const redeem = _redeem(); - if (a.redeem.output && !a.redeem.output.equals(redeem.output)) - throw new TypeError('Redeem.output mismatch'); - if (a.redeem.input && !a.redeem.input.equals(redeem.input)) - throw new TypeError('Redeem.input mismatch'); - } - checkRedeem(a.redeem); - } - if (a.witness) { - if (a.redeem && - a.redeem.witness && - !stacksEqual(a.redeem.witness, a.witness)) - throw new TypeError('Witness and redeem.witness mismatch'); - } - if (a.confidentialAddress) { - if (a.address && - a.address !== _confidentialAddress().unconfidentialAddress) - throw new TypeError('Address mismatch'); - if (blindkey.length > 0 && - !blindkey.equals(_confidentialAddress().blindkey)) - throw new TypeError('Blindkey mismatch'); - else - blindkey = _confidentialAddress().blindkey; - } - if (a.blindkey) { - if (!ecc.isPoint(a.blindkey)) - throw new TypeError('Blindkey is invalid'); - if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) - throw new TypeError('Blindkey mismatch'); - else - blindkey = a.blindkey; + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([OPS.OP_HASH160, o.hash, OPS.OP_EQUAL]); + }); + // input dependents + lazy.prop(o, 'redeem', () => { + if (!a.input) return; + return _redeem(); + }); + lazy.prop(o, 'input', () => { + if (!a.redeem || !a.redeem.input || !a.redeem.output) return; + return bscript.compile( + [].concat(bscript.decompile(a.redeem.input), a.redeem.output), + ); + }); + lazy.prop(o, 'witness', () => { + if (o.redeem && o.redeem.witness) return o.redeem.witness; + if (o.input) return []; + }); + lazy.prop(o, 'name', () => { + const nameParts = ['p2sh']; + if (o.redeem !== undefined) nameParts.push(o.redeem.name); + return nameParts.join('-'); + }); + lazy.prop(o, 'blindkey', () => { + if (a.confidentialAddress) return _confidentialAddress().blindkey; + if (a.blindkey) return a.blindkey; + }); + lazy.prop(o, 'confidentialAddress', () => { + if (!o.address) return; + if (!o.blindkey) return; + const payload = bs58check.decode(o.address); + const confidentialAddress = Buffer.concat([ + Buffer.from([network.confidentialPrefix, payload.readUInt8(0)]), + o.blindkey, + Buffer.from(payload.slice(1)), + ]); + return bs58check.encode(confidentialAddress); + }); + if (opts.validate) { + let hash = Buffer.from([]); + let blindkey = Buffer.from([]); + if (a.address) { + if (_address().version !== network.scriptHash) + throw new TypeError('Invalid version or Network mismatch'); + if (_address().hash.length !== 20) throw new TypeError('Invalid address'); + hash = _address().hash; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + if (a.output) { + if ( + a.output.length !== 23 || + a.output[0] !== OPS.OP_HASH160 || + a.output[1] !== 0x14 || + a.output[22] !== OPS.OP_EQUAL + ) + throw new TypeError('Output is invalid'); + const hash2 = a.output.slice(2, 22); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + // inlined to prevent 'no-inner-declarations' failing + const checkRedeem = redeem => { + // is the redeem output empty/invalid? + if (redeem.output) { + const decompile = bscript.decompile(redeem.output); + if (!decompile || decompile.length < 1) + throw new TypeError('Redeem.output too short'); + // match hash against other sources + const hash2 = bcrypto.hash160(redeem.output); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + if (redeem.input) { + const hasInput = redeem.input.length > 0; + const hasWitness = redeem.witness && redeem.witness.length > 0; + if (!hasInput && !hasWitness) throw new TypeError('Empty input'); + if (hasInput && hasWitness) + throw new TypeError('Input and witness provided'); + if (hasInput) { + const richunks = bscript.decompile(redeem.input); + if (!bscript.isPushOnly(richunks)) + throw new TypeError('Non push-only scriptSig'); } + } + }; + if (a.input) { + const chunks = _chunks(); + if (!chunks || chunks.length < 1) throw new TypeError('Input too short'); + if (!Buffer.isBuffer(_redeem().output)) + throw new TypeError('Input is invalid'); + checkRedeem(_redeem()); + } + if (a.redeem) { + if (a.redeem.network && a.redeem.network !== network) + throw new TypeError('Network mismatch'); + if (a.input) { + const redeem = _redeem(); + if (a.redeem.output && !a.redeem.output.equals(redeem.output)) + throw new TypeError('Redeem.output mismatch'); + if (a.redeem.input && !a.redeem.input.equals(redeem.input)) + throw new TypeError('Redeem.input mismatch'); + } + checkRedeem(a.redeem); + } + if (a.witness) { + if ( + a.redeem && + a.redeem.witness && + !stacksEqual(a.redeem.witness, a.witness) + ) + throw new TypeError('Witness and redeem.witness mismatch'); + } + if (a.confidentialAddress) { + if ( + a.address && + a.address !== _confidentialAddress().unconfidentialAddress + ) + throw new TypeError('Address mismatch'); + if ( + blindkey.length > 0 && + !blindkey.equals(_confidentialAddress().blindkey) + ) + throw new TypeError('Blindkey mismatch'); + else blindkey = _confidentialAddress().blindkey; + } + if (a.blindkey) { + if (!ecc.isPoint(a.blindkey)) throw new TypeError('Blindkey is invalid'); + if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) + throw new TypeError('Blindkey mismatch'); + else blindkey = a.blindkey; } - return Object.assign(o, a); + } + return Object.assign(o, a); } exports.p2sh = p2sh; diff --git a/src/payments/p2wpkh.js b/src/payments/p2wpkh.js index 21b2b7a46..4f9956aad 100644 --- a/src/payments/p2wpkh.js +++ b/src/payments/p2wpkh.js @@ -1,17 +1,21 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const baddress = __importStar(require("../address")); -const bcrypto = __importStar(require("../crypto")); -const networks_1 = require("../networks"); -const bscript = __importStar(require("../script")); -const lazy = __importStar(require("./lazy")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const baddress = __importStar(require('../address')); +const bcrypto = __importStar(require('../crypto')); +const networks_1 = require('../networks'); +const bscript = __importStar(require('../script')); +const lazy = __importStar(require('./lazy')); const typef = require('typeforce'); const OPS = bscript.OPS; const ecc = require('tiny-secp256k1'); @@ -21,179 +25,172 @@ const EMPTY_BUFFER = Buffer.alloc(0); // input: <> // output: OP_0 {pubKeyHash} function p2wpkh(a, opts) { - if (!a.address && - !a.hash && - !a.output && - !a.pubkey && - !a.witness && - !a.confidentialAddress) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef({ - 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)), - }, a); - const network = a.network || networks_1.liquid; - const _address = lazy.value(() => { - const result = bech32.decode(a.address); - const version = result.words.shift(); - const data = bech32.fromWords(result.words); - return { - version, - prefix: result.prefix, - data: Buffer.from(data), - }; - }); - const _confidentialAddress = lazy.value(() => { - const result = baddress.fromBlech32(a.confidentialAddress); - return { - blindingKey: result.pubkey, - unconfidentialAddress: baddress.toBech32(result.data.slice(2), result.version, network.bech32), - }; - }); - const o = { name: 'p2wpkh', network }; - lazy.prop(o, 'address', () => { - if (!o.hash) - return; - const words = bech32.toWords(o.hash); - words.unshift(0x00); - return bech32.encode(network.bech32, words); - }); - lazy.prop(o, 'hash', () => { - if (a.output) - return a.output.slice(2, 22); - if (a.address) - return _address().data; - if (a.pubkey || o.pubkey) - return bcrypto.hash160(a.pubkey || o.pubkey); - if (a.confidentialAddress) { - const addr = _confidentialAddress().unconfidentialAddress; - return baddress.fromBech32(addr).data; - } - }); - lazy.prop(o, 'output', () => { - if (!o.hash) - return; - return bscript.compile([OPS.OP_0, o.hash]); - }); - lazy.prop(o, 'pubkey', () => { - if (a.pubkey) - return a.pubkey; - if (!a.witness) - return; - return a.witness[1]; - }); - lazy.prop(o, 'signature', () => { - if (!a.witness) - return; - return a.witness[0]; - }); - lazy.prop(o, 'input', () => { - if (!o.witness) - return; - return EMPTY_BUFFER; - }); - lazy.prop(o, 'witness', () => { - if (!a.pubkey) - return; - if (!a.signature) - return; - return [a.signature, a.pubkey]; - }); - lazy.prop(o, 'blindkey', () => { - if (a.confidentialAddress) - return _confidentialAddress().blindingKey; - if (a.blindkey) - return a.blindkey; - }); - lazy.prop(o, 'confidentialAddress', () => { - if (!o.address) - return; - if (!o.blindkey) - return; - const res = baddress.fromBech32(o.address); - const data = Buffer.concat([ - Buffer.from([res.version, res.data.length]), - res.data, - ]); - return baddress.toBlech32(data, o.blindkey, o.network.blech32); - }); - // extended validation - if (opts.validate) { - let hash = Buffer.from([]); - let blindkey = Buffer.from([]); - if (a.address) { - if (network && network.bech32 !== _address().prefix) - throw new TypeError('Invalid prefix or Network mismatch'); - if (_address().version !== 0x00) - throw new TypeError('Invalid address version'); - if (_address().data.length !== 20) - throw new TypeError('Invalid address data'); - hash = _address().data; - } - if (a.hash) { - if (hash.length > 0 && !hash.equals(a.hash)) - throw new TypeError('Hash mismatch'); - else - hash = a.hash; - } - if (a.output) { - if (a.output.length !== 22 || - a.output[0] !== OPS.OP_0 || - a.output[1] !== 0x14) - throw new TypeError('Output is invalid'); - if (hash.length > 0 && !hash.equals(a.output.slice(2))) - throw new TypeError('Hash mismatch'); - else - hash = a.output.slice(2); - } - if (a.pubkey) { - const pkh = bcrypto.hash160(a.pubkey); - if (hash.length > 0 && !hash.equals(pkh)) - throw new TypeError('Hash mismatch'); - else - hash = pkh; - } - 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])) - throw new TypeError('Witness has invalid pubkey'); - if (a.signature && !a.signature.equals(a.witness[0])) - throw new TypeError('Signature mismatch'); - if (a.pubkey && !a.pubkey.equals(a.witness[1])) - throw new TypeError('Pubkey mismatch'); - const pkh = bcrypto.hash160(a.witness[1]); - if (hash.length > 0 && !hash.equals(pkh)) - throw new TypeError('Hash mismatch'); - } - if (a.confidentialAddress) { - if (a.address && - a.address !== _confidentialAddress().unconfidentialAddress) - throw new TypeError('Address mismatch'); - if (blindkey.length > 0 && - !blindkey.equals(_confidentialAddress().blindingKey)) - throw new TypeError('Blindkey mismatch'); - else - blindkey = _confidentialAddress().blindingKey; - } - if (a.blindkey) { - if (!ecc.isPoint(a.blindkey)) - throw new TypeError('Blindkey is invalid'); - if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) - throw new TypeError('Blindkey mismatch'); - else - blindkey = a.blindkey; - } + if ( + !a.address && + !a.hash && + !a.output && + !a.pubkey && + !a.witness && + !a.confidentialAddress + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + 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)), + }, + a, + ); + const network = a.network || networks_1.liquid; + const _address = lazy.value(() => { + const result = bech32.decode(a.address); + const version = result.words.shift(); + const data = bech32.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: Buffer.from(data), + }; + }); + const _confidentialAddress = lazy.value(() => { + const result = baddress.fromBlech32(a.confidentialAddress); + return { + blindingKey: result.pubkey, + unconfidentialAddress: baddress.toBech32( + result.data.slice(2), + result.version, + network.bech32, + ), + }; + }); + const o = { name: 'p2wpkh', network }; + lazy.prop(o, 'address', () => { + if (!o.hash) return; + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', () => { + if (a.output) return a.output.slice(2, 22); + if (a.address) return _address().data; + if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey); + if (a.confidentialAddress) { + const addr = _confidentialAddress().unconfidentialAddress; + return baddress.fromBech32(addr).data; } - return Object.assign(o, a); + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([OPS.OP_0, o.hash]); + }); + lazy.prop(o, 'pubkey', () => { + if (a.pubkey) return a.pubkey; + if (!a.witness) return; + return a.witness[1]; + }); + lazy.prop(o, 'signature', () => { + if (!a.witness) return; + return a.witness[0]; + }); + lazy.prop(o, 'input', () => { + if (!o.witness) return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', () => { + if (!a.pubkey) return; + if (!a.signature) return; + return [a.signature, a.pubkey]; + }); + lazy.prop(o, 'blindkey', () => { + if (a.confidentialAddress) return _confidentialAddress().blindingKey; + if (a.blindkey) return a.blindkey; + }); + lazy.prop(o, 'confidentialAddress', () => { + if (!o.address) return; + if (!o.blindkey) return; + const res = baddress.fromBech32(o.address); + const data = Buffer.concat([ + Buffer.from([res.version, res.data.length]), + res.data, + ]); + return baddress.toBlech32(data, o.blindkey, o.network.blech32); + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + let blindkey = Buffer.from([]); + if (a.address) { + if (network && network.bech32 !== _address().prefix) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== 0x00) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 20) + throw new TypeError('Invalid address data'); + hash = _address().data; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + if (a.output) { + if ( + a.output.length !== 22 || + a.output[0] !== OPS.OP_0 || + a.output[1] !== 0x14 + ) + throw new TypeError('Output is invalid'); + if (hash.length > 0 && !hash.equals(a.output.slice(2))) + throw new TypeError('Hash mismatch'); + else hash = a.output.slice(2); + } + if (a.pubkey) { + const pkh = bcrypto.hash160(a.pubkey); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + else hash = pkh; + } + 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])) + throw new TypeError('Witness has invalid pubkey'); + if (a.signature && !a.signature.equals(a.witness[0])) + throw new TypeError('Signature mismatch'); + if (a.pubkey && !a.pubkey.equals(a.witness[1])) + throw new TypeError('Pubkey mismatch'); + const pkh = bcrypto.hash160(a.witness[1]); + if (hash.length > 0 && !hash.equals(pkh)) + throw new TypeError('Hash mismatch'); + } + if (a.confidentialAddress) { + if ( + a.address && + a.address !== _confidentialAddress().unconfidentialAddress + ) + throw new TypeError('Address mismatch'); + if ( + blindkey.length > 0 && + !blindkey.equals(_confidentialAddress().blindingKey) + ) + throw new TypeError('Blindkey mismatch'); + else blindkey = _confidentialAddress().blindingKey; + } + if (a.blindkey) { + if (!ecc.isPoint(a.blindkey)) throw new TypeError('Blindkey is invalid'); + if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) + throw new TypeError('Blindkey mismatch'); + else blindkey = a.blindkey; + } + } + return Object.assign(o, a); } exports.p2wpkh = p2wpkh; diff --git a/src/payments/p2wsh.js b/src/payments/p2wsh.js index a4d53ba9f..db2211941 100644 --- a/src/payments/p2wsh.js +++ b/src/payments/p2wsh.js @@ -1,247 +1,252 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const baddress = __importStar(require("../address")); -const bcrypto = __importStar(require("../crypto")); -const networks_1 = require("../networks"); -const bscript = __importStar(require("../script")); -const lazy = __importStar(require("./lazy")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const baddress = __importStar(require('../address')); +const bcrypto = __importStar(require('../crypto')); +const networks_1 = require('../networks'); +const bscript = __importStar(require('../script')); +const lazy = __importStar(require('./lazy')); const typef = require('typeforce'); 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; - return a.every((x, i) => { - return x.equals(b[i]); - }); + if (a.length !== b.length) return false; + return a.every((x, i) => { + return x.equals(b[i]); + }); } // input: <> // witness: [redeemScriptSig ...] {redeemScript} // output: OP_0 {sha256(redeemScript)} function p2wsh(a, opts) { - if (!a.address && - !a.hash && - !a.output && - !a.redeem && - !a.witness && - !a.confidentialAddress) - throw new TypeError('Not enough data'); - opts = Object.assign({ validate: true }, opts || {}); - typef({ + if ( + !a.address && + !a.hash && + !a.output && + !a.redeem && + !a.witness && + !a.confidentialAddress + ) + throw new TypeError('Not enough data'); + opts = Object.assign({ validate: true }, opts || {}); + typef( + { + 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), - 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)), - }), - input: typef.maybe(typef.BufferN(0)), + output: typef.maybe(typef.Buffer), witness: typef.maybe(typef.arrayOf(typef.Buffer)), - blindkey: typef.maybe(ecc.isPoint), - confidentialAddress: typef.maybe(typef.String), - }, a); - let network = a.network; - if (!network) { - network = (a.redeem && a.redeem.network) || networks_1.liquid; + }), + input: typef.maybe(typef.BufferN(0)), + witness: typef.maybe(typef.arrayOf(typef.Buffer)), + blindkey: typef.maybe(ecc.isPoint), + confidentialAddress: typef.maybe(typef.String), + }, + a, + ); + let network = a.network; + if (!network) { + network = (a.redeem && a.redeem.network) || networks_1.liquid; + } + const _address = lazy.value(() => { + const result = bech32.decode(a.address); + const version = result.words.shift(); + const data = bech32.fromWords(result.words); + return { + version, + prefix: result.prefix, + data: Buffer.from(data), + }; + }); + const _rchunks = lazy.value(() => { + return bscript.decompile(a.redeem.input); + }); + const _confidentialAddress = lazy.value(() => { + const result = baddress.fromBlech32(a.confidentialAddress); + return { + blindingKey: result.pubkey, + unconfidentialAddress: baddress.toBech32( + result.data.slice(2), + result.version, + network.bech32, + ), + }; + }); + const o = { network }; + lazy.prop(o, 'address', () => { + if (!o.hash) return; + const words = bech32.toWords(o.hash); + words.unshift(0x00); + return bech32.encode(network.bech32, words); + }); + lazy.prop(o, 'hash', () => { + if (a.output) return a.output.slice(2); + if (a.address) return _address().data; + if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output); + if (a.confidentialAddress) { + const addr = _confidentialAddress().unconfidentialAddress; + return baddress.fromBech32(addr).data; } - const _address = lazy.value(() => { - const result = bech32.decode(a.address); - const version = result.words.shift(); - const data = bech32.fromWords(result.words); - return { - version, - prefix: result.prefix, - data: Buffer.from(data), - }; - }); - const _rchunks = lazy.value(() => { - return bscript.decompile(a.redeem.input); - }); - const _confidentialAddress = lazy.value(() => { - const result = baddress.fromBlech32(a.confidentialAddress); - return { - blindingKey: result.pubkey, - unconfidentialAddress: baddress.toBech32(result.data.slice(2), result.version, network.bech32), - }; - }); - const o = { network }; - lazy.prop(o, 'address', () => { - if (!o.hash) - return; - const words = bech32.toWords(o.hash); - words.unshift(0x00); - return bech32.encode(network.bech32, words); - }); - lazy.prop(o, 'hash', () => { - if (a.output) - return a.output.slice(2); - if (a.address) - return _address().data; - if (o.redeem && o.redeem.output) - return bcrypto.sha256(o.redeem.output); - if (a.confidentialAddress) { - const addr = _confidentialAddress().unconfidentialAddress; - return baddress.fromBech32(addr).data; - } - }); - lazy.prop(o, 'output', () => { - if (!o.hash) - return; - return bscript.compile([OPS.OP_0, o.hash]); - }); - lazy.prop(o, 'redeem', () => { - if (!a.witness) - return; - return { - output: a.witness[a.witness.length - 1], - input: EMPTY_BUFFER, - witness: a.witness.slice(0, -1), - }; - }); - lazy.prop(o, 'input', () => { - if (!o.witness) - return; - return EMPTY_BUFFER; - }); - lazy.prop(o, 'witness', () => { - // transform redeem input to witness stack? - if (a.redeem && - a.redeem.input && - a.redeem.input.length > 0 && - a.redeem.output && - a.redeem.output.length > 0) { - const stack = bscript.toStack(_rchunks()); - // assign, and blank the existing input - o.redeem = Object.assign({ witness: stack }, a.redeem); - o.redeem.input = EMPTY_BUFFER; - return [].concat(stack, a.redeem.output); - } - if (!a.redeem) - return; - if (!a.redeem.output) - return; - if (!a.redeem.witness) - return; - return [].concat(a.redeem.witness, a.redeem.output); - }); - lazy.prop(o, 'name', () => { - const nameParts = ['p2wsh']; - if (o.redeem !== undefined) - nameParts.push(o.redeem.name); - return nameParts.join('-'); - }); - lazy.prop(o, 'blindkey', () => { - if (a.confidentialAddress) - return _confidentialAddress().blindingKey; - if (a.blindkey) - return a.blindkey; - }); - lazy.prop(o, 'confidentialAddress', () => { - if (!o.address) - return; - if (!o.blindkey) - return; - const res = baddress.fromBech32(o.address); - const data = Buffer.concat([ - Buffer.from([res.version, res.data.length]), - res.data, - ]); - return baddress.toBlech32(data, o.blindkey, o.network.blech32); - }); - // extended validation - if (opts.validate) { - let hash = Buffer.from([]); - let blindkey = Buffer.from([]); - if (a.address) { - if (_address().prefix !== network.bech32) - throw new TypeError('Invalid prefix or Network mismatch'); - if (_address().version !== 0x00) - throw new TypeError('Invalid address version'); - if (_address().data.length !== 32) - throw new TypeError('Invalid address data'); - hash = _address().data; - } - if (a.hash) { - if (hash.length > 0 && !hash.equals(a.hash)) - throw new TypeError('Hash mismatch'); - else - hash = a.hash; - } - if (a.output) { - if (a.output.length !== 34 || - a.output[0] !== OPS.OP_0 || - a.output[1] !== 0x20) - throw new TypeError('Output is invalid'); - const hash2 = a.output.slice(2); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else - hash = hash2; - } - if (a.redeem) { - if (a.redeem.network && a.redeem.network !== network) - throw new TypeError('Network mismatch'); - // is there two redeem sources? - if (a.redeem.input && - a.redeem.input.length > 0 && - a.redeem.witness && - a.redeem.witness.length > 0) - throw new TypeError('Ambiguous witness source'); - // is the redeem output non-empty? - if (a.redeem.output) { - if (bscript.decompile(a.redeem.output).length === 0) - throw new TypeError('Redeem.output is invalid'); - // match hash against other sources - const hash2 = bcrypto.sha256(a.redeem.output); - if (hash.length > 0 && !hash.equals(hash2)) - throw new TypeError('Hash mismatch'); - else - hash = hash2; - } - if (a.redeem.input && !bscript.isPushOnly(_rchunks())) - throw new TypeError('Non push-only scriptSig'); - if (a.witness && - a.redeem.witness && - !stacksEqual(a.witness, a.redeem.witness)) - throw new TypeError('Witness and redeem.witness mismatch'); - } - if (a.witness) { - if (a.redeem && - a.redeem.output && - !a.redeem.output.equals(a.witness[a.witness.length - 1])) - throw new TypeError('Witness and redeem.output mismatch'); - } - if (a.confidentialAddress) { - if (a.address && - a.address !== _confidentialAddress().unconfidentialAddress) - throw new TypeError('Address mismatch'); - if (blindkey.length > 0 && - !blindkey.equals(_confidentialAddress().blindingKey)) - throw new TypeError('Blindkey mismatch'); - else - blindkey = _confidentialAddress().blindingKey; - } - if (a.blindkey) { - if (!ecc.isPoint(a.blindkey)) - throw new TypeError('Blindkey is invalid'); - if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) - throw new TypeError('Blindkey mismatch'); - else - blindkey = a.blindkey; - } + }); + lazy.prop(o, 'output', () => { + if (!o.hash) return; + return bscript.compile([OPS.OP_0, o.hash]); + }); + lazy.prop(o, 'redeem', () => { + if (!a.witness) return; + return { + output: a.witness[a.witness.length - 1], + input: EMPTY_BUFFER, + witness: a.witness.slice(0, -1), + }; + }); + lazy.prop(o, 'input', () => { + if (!o.witness) return; + return EMPTY_BUFFER; + }); + lazy.prop(o, 'witness', () => { + // transform redeem input to witness stack? + if ( + a.redeem && + a.redeem.input && + a.redeem.input.length > 0 && + a.redeem.output && + a.redeem.output.length > 0 + ) { + const stack = bscript.toStack(_rchunks()); + // assign, and blank the existing input + o.redeem = Object.assign({ witness: stack }, a.redeem); + o.redeem.input = EMPTY_BUFFER; + return [].concat(stack, a.redeem.output); } - return Object.assign(o, a); + if (!a.redeem) return; + if (!a.redeem.output) return; + if (!a.redeem.witness) return; + return [].concat(a.redeem.witness, a.redeem.output); + }); + lazy.prop(o, 'name', () => { + const nameParts = ['p2wsh']; + if (o.redeem !== undefined) nameParts.push(o.redeem.name); + return nameParts.join('-'); + }); + lazy.prop(o, 'blindkey', () => { + if (a.confidentialAddress) return _confidentialAddress().blindingKey; + if (a.blindkey) return a.blindkey; + }); + lazy.prop(o, 'confidentialAddress', () => { + if (!o.address) return; + if (!o.blindkey) return; + const res = baddress.fromBech32(o.address); + const data = Buffer.concat([ + Buffer.from([res.version, res.data.length]), + res.data, + ]); + return baddress.toBlech32(data, o.blindkey, o.network.blech32); + }); + // extended validation + if (opts.validate) { + let hash = Buffer.from([]); + let blindkey = Buffer.from([]); + if (a.address) { + if (_address().prefix !== network.bech32) + throw new TypeError('Invalid prefix or Network mismatch'); + if (_address().version !== 0x00) + throw new TypeError('Invalid address version'); + if (_address().data.length !== 32) + throw new TypeError('Invalid address data'); + hash = _address().data; + } + if (a.hash) { + if (hash.length > 0 && !hash.equals(a.hash)) + throw new TypeError('Hash mismatch'); + else hash = a.hash; + } + if (a.output) { + if ( + a.output.length !== 34 || + a.output[0] !== OPS.OP_0 || + a.output[1] !== 0x20 + ) + throw new TypeError('Output is invalid'); + const hash2 = a.output.slice(2); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + if (a.redeem) { + if (a.redeem.network && a.redeem.network !== network) + throw new TypeError('Network mismatch'); + // is there two redeem sources? + if ( + a.redeem.input && + a.redeem.input.length > 0 && + a.redeem.witness && + a.redeem.witness.length > 0 + ) + throw new TypeError('Ambiguous witness source'); + // is the redeem output non-empty? + if (a.redeem.output) { + if (bscript.decompile(a.redeem.output).length === 0) + throw new TypeError('Redeem.output is invalid'); + // match hash against other sources + const hash2 = bcrypto.sha256(a.redeem.output); + if (hash.length > 0 && !hash.equals(hash2)) + throw new TypeError('Hash mismatch'); + else hash = hash2; + } + if (a.redeem.input && !bscript.isPushOnly(_rchunks())) + throw new TypeError('Non push-only scriptSig'); + if ( + a.witness && + a.redeem.witness && + !stacksEqual(a.witness, a.redeem.witness) + ) + throw new TypeError('Witness and redeem.witness mismatch'); + } + if (a.witness) { + if ( + a.redeem && + a.redeem.output && + !a.redeem.output.equals(a.witness[a.witness.length - 1]) + ) + throw new TypeError('Witness and redeem.output mismatch'); + } + if (a.confidentialAddress) { + if ( + a.address && + a.address !== _confidentialAddress().unconfidentialAddress + ) + throw new TypeError('Address mismatch'); + if ( + blindkey.length > 0 && + !blindkey.equals(_confidentialAddress().blindingKey) + ) + throw new TypeError('Blindkey mismatch'); + else blindkey = _confidentialAddress().blindingKey; + } + if (a.blindkey) { + if (!ecc.isPoint(a.blindkey)) throw new TypeError('Blindkey is invalid'); + if (blindkey.length > 0 && !blindkey.equals(a.blindkey)) + throw new TypeError('Blindkey mismatch'); + else blindkey = a.blindkey; + } + } + return Object.assign(o, a); } exports.p2wsh = p2wsh; diff --git a/src/psbt.js b/src/psbt.js index 5382d5f75..814654c28 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -1,49 +1,73 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); +'use strict'; +var __awaiter = + (this && this.__awaiter) || + function(thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function(resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + } + function rejected(value) { + try { + step(generator['throw'](value)); + } catch (e) { + reject(e); + } + } + function step(result) { + result.done + ? resolve(result.value) + : new P(function(resolve) { + resolve(result.value); + }).then(fulfilled, rejected); + } + step((generator = generator.apply(thisArg, _arguments || [])).next()); }); -}; -var __importStar = (this && this.__importStar) || function (mod) { + }; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const confidential = __importStar(require("./confidential")); -const varuint = __importStar(require("bip174-liquid/src/lib/converter/varint")); -const address_1 = require("./address"); -const bufferutils_1 = require("./bufferutils"); -const crypto_1 = require("./crypto"); -const networks_1 = require("./networks"); -const transaction_1 = require("./transaction"); -const ecpair_1 = require("./ecpair"); -const issuance_1 = require("./issuance"); -const payments = __importStar(require("./payments")); -const bscript = __importStar(require("./script")); -const bip174_liquid_1 = require("bip174-liquid"); -const utils_1 = require("bip174-liquid/src/lib/utils"); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const confidential = __importStar(require('./confidential')); +const varuint = __importStar(require('bip174-liquid/src/lib/converter/varint')); +const address_1 = require('./address'); +const bufferutils_1 = require('./bufferutils'); +const crypto_1 = require('./crypto'); +const networks_1 = require('./networks'); +const transaction_1 = require('./transaction'); +const ecpair_1 = require('./ecpair'); +const issuance_1 = require('./issuance'); +const payments = __importStar(require('./payments')); +const bscript = __importStar(require('./script')); +const bip174_liquid_1 = require('bip174-liquid'); +const utils_1 = require('bip174-liquid/src/lib/utils'); const _randomBytes = require('randombytes'); /** * These are the default arguments for a Psbt instance. */ const DEFAULT_OPTS = { - /** - * A bitcoinjs Network object. This is only used if you pass an `address` - * parameter to addOutput. Otherwise it is not needed and can be left default. - */ - network: networks_1.liquid, - /** - * When extractTransaction is called, the fee rate is checked. - * 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, + /** + * A bitcoinjs Network object. This is only used if you pass an `address` + * parameter to addOutput. Otherwise it is not needed and can be left default. + */ + network: networks_1.liquid, + /** + * When extractTransaction is called, the fee rate is checked. + * 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, }; /** * Psbt class can parse and generate a PSBT binary based off of the BIP174. @@ -78,672 +102,902 @@ const DEFAULT_OPTS = { * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. */ class Psbt { - constructor(opts = {}, data = new bip174_liquid_1.Psbt(new PsbtTransaction())) { - this.data = data; - // set defaults - this.opts = Object.assign({}, DEFAULT_OPTS, opts); - this.__CACHE = { - __NON_WITNESS_UTXO_TX_CACHE: [], - __NON_WITNESS_UTXO_BUF_CACHE: [], - __TX_IN_CACHE: {}, - __TX: this.data.globalMap.unsignedTx.tx, - }; - if (this.data.inputs.length === 0) - this.setVersion(2); - // Make data hidden when enumerating - const dpew = (obj, attr, enumerable, writable) => Object.defineProperty(obj, attr, { - enumerable, - writable, - }); - 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_liquid_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; - } - combine(...those) { - this.data.combine(...those.map(o => o.data)); - return this; - } - clone() { - // TODO: more efficient cloning - const res = Psbt.fromBuffer(this.data.toBuffer()); - res.opts = JSON.parse(JSON.stringify(this.opts)); - return res; - } - setMaximumFeeRate(satoshiPerByte) { - check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw - this.opts.maximumFeeRate = satoshiPerByte; - } - setVersion(version) { - check32Bit(version); - checkInputsForPartialSig(this.data.inputs, 'setVersion'); - const c = this.__CACHE; - c.__TX.version = version; - c.__EXTRACTED_TX = undefined; - return this; - } - setLocktime(locktime) { - check32Bit(locktime); - checkInputsForPartialSig(this.data.inputs, 'setLocktime'); - const c = this.__CACHE; - c.__TX.locktime = locktime; - c.__EXTRACTED_TX = undefined; - return this; - } - setInputSequence(inputIndex, sequence) { - check32Bit(sequence); - checkInputsForPartialSig(this.data.inputs, 'setInputSequence'); - const c = this.__CACHE; - if (c.__TX.ins.length <= inputIndex) { - throw new Error('Input index too high'); - } - c.__TX.ins[inputIndex].sequence = sequence; - c.__EXTRACTED_TX = undefined; - return this; - } - addInputs(inputDatas) { - inputDatas.forEach(inputData => this.addInput(inputData)); - return this; - } - addInput(inputData) { - if (arguments.length > 1 || - !inputData || - inputData.hash === undefined || - inputData.index === undefined) { - throw new Error(`Invalid arguments for Psbt.addInput. ` + - `Requires single object with at least [hash] and [index]`); - } - checkInputsForPartialSig(this.data.inputs, 'addInput'); - const c = this.__CACHE; - this.data.addInput(inputData); - const txIn = c.__TX.ins[c.__TX.ins.length - 1]; - checkTxInputCache(c, txIn); - const inputIndex = this.data.inputs.length - 1; - const input = this.data.inputs[inputIndex]; - if (input.nonWitnessUtxo) { - addNonWitnessTxCache(this.__CACHE, input, inputIndex); - } - c.__FEE = undefined; - c.__FEE_RATE = undefined; - c.__EXTRACTED_TX = undefined; - return this; - } - addIssuance(args, inputIndex) { - issuance_1.validateAddIssuanceArgs(args); // throw an error if args are invalid - if (inputIndex && !this.data.inputs[inputIndex]) { - throw new Error(`The input ${inputIndex} does not exist.`); - // check if the input is available for issuance. - } - else { - // verify if there is at least one input available. - if (this.__CACHE.__TX.ins.filter(i => !i.issuance).length === 0) - throw new Error('transaction needs at least one input without issuance data.'); - // search and extract the input index. - inputIndex = this.__CACHE.__TX.ins.findIndex(i => !i.issuance); - } - if (this.__CACHE.__TX.ins[inputIndex].issuance) - throw new Error(`The input ${inputIndex} already has issuance data.`); - const { hash, index } = this.__CACHE.__TX.ins[inputIndex]; - // create an issuance object using the vout and the args - const issuance = issuance_1.newIssuance(args.assetAmount, args.tokenAmount, args.precision, args.contract); - // generate the entropy - const entropy = issuance_1.generateEntropy({ txHash: hash, vout: index }, issuance.assetEntropy); - // add the issuance to the input. - this.__CACHE.__TX.ins[inputIndex].issuance = issuance; - const asset = Buffer.concat([ - Buffer.of(args.confidential ? 0x0a : 0x01), - issuance_1.calculateAsset(entropy), - ]); - const assetScript = address_1.toOutputScript(args.assetAddress); - // send the asset amount to the asset address. - this.addOutput({ - value: issuance.assetAmount, - script: assetScript, - asset, - nonce: Buffer.from('00', 'hex'), - }); - // check if the token amount is not 0 - if (args.tokenAmount !== 0) { - if (!args.tokenAddress) - throw new Error("tokenAddress can't be undefined if tokenAmount > 0"); - const token = issuance_1.calculateReissuanceToken(entropy, args.confidential); - const tokenScript = address_1.toOutputScript(args.tokenAddress); - // send the token amount to the token address. - this.addOutput({ - script: tokenScript, - value: issuance.tokenAmount, - asset: Buffer.concat([Buffer.of(0x01), token]), - nonce: Buffer.from('00', 'hex'), - }); - } - return this; - } - addOutputs(outputDatas) { - outputDatas.forEach(outputData => this.addOutput(outputData)); - return this; - } - addOutput(outputData) { - if (arguments.length > 1 || - !outputData || - outputData.value === undefined || - (outputData.address === undefined && - outputData.script === undefined)) { - throw new Error(`Invalid arguments for Psbt.addOutput. ` + - `Requires single object with at least [script or address] and [value]`); - } - checkInputsForPartialSig(this.data.inputs, 'addOutput'); - const { address } = outputData; - if (typeof address === 'string') { - const { network } = this.opts; - const script = address_1.toOutputScript(address, network); - outputData = Object.assign(outputData, { script }); - } - const c = this.__CACHE; - this.data.addOutput(outputData); - c.__FEE = undefined; - c.__FEE_RATE = undefined; - c.__EXTRACTED_TX = undefined; - return this; - } - extractTransaction(disableFeeCheck) { - if (!this.data.inputs.every(isFinalized)) - throw new Error('Not finalized'); - const c = this.__CACHE; - if (!disableFeeCheck) { - checkFees(this, c, this.opts); - } - if (c.__EXTRACTED_TX) - return c.__EXTRACTED_TX; - const tx = c.__TX.clone(); - inputFinalizeGetAmts(this.data.inputs, tx, c, true); - return tx; - } - getFeeRate() { - return getTxCacheValue('__FEE_RATE', 'fee rate', this.data.inputs, this.__CACHE); - } - getFee() { - return getTxCacheValue('__FEE', 'fee', this.data.inputs, this.__CACHE); - } - finalizeAllInputs() { - 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) { - const input = utils_1.checkForInput(this.data.inputs, inputIndex); - const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(inputIndex, input, this.__CACHE); - if (!script) - throw new Error(`No script found for input #${inputIndex}`); - const scriptType = classifyScript(script); - if (!canFinalize(input, script, scriptType)) - throw new Error(`Can not finalize input #${inputIndex}`); - checkPartialSigSighashes(input); - const { finalScriptSig, finalScriptWitness } = getFinalScripts(script, scriptType, input.partialSig, isSegwit, isP2SH, isP2WSH); - if (finalScriptSig) - this.data.updateInput(inputIndex, { finalScriptSig }); - if (finalScriptWitness) - this.data.updateInput(inputIndex, { finalScriptWitness }); - if (!finalScriptSig && !finalScriptWitness) - throw new Error(`Unknown error finalizing input #${inputIndex}`); - this.data.clearFinalizedInput(inputIndex); - return this; - } - validateSignaturesOfAllInputs() { - 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)); - return results.reduce((final, res) => res === true && final, true); - } - validateSignaturesOfInput(inputIndex, pubkey) { - const input = this.data.inputs[inputIndex]; - const partialSig = (input || {}).partialSig; - if (!input || !partialSig || partialSig.length < 1) - throw new Error('No signatures to validate'); - const mySigs = pubkey - ? partialSig.filter(sig => sig.pubkey.equals(pubkey)) - : partialSig; - if (mySigs.length < 1) - throw new Error('No signatures for this pubkey'); - const results = []; - let hashCache; - let scriptCache; - let sighashCache; - for (const pSig of mySigs) { - const sig = bscript.signature.decode(pSig.signature); - const { hash, script } = sighashCache !== sig.hashType - ? getHashForSig(inputIndex, Object.assign({}, input, { sighashType: sig.hashType }), this.__CACHE) - : { hash: hashCache, script: scriptCache }; - sighashCache = sig.hashType; - hashCache = hash; - scriptCache = script; - checkScriptForPubkey(pSig.pubkey, script, 'verify'); - const keypair = ecpair_1.fromPublicKey(pSig.pubkey); - results.push(keypair.verify(hash, sig.signature)); - } - return results.every(res => res === true); - } - signAllInputsHD(hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { - if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { - throw new Error('Need HDSigner to sign input'); - } - const results = []; - for (const i of range(this.data.inputs.length)) { - try { - this.signInputHD(i, hdKeyPair, sighashTypes); - results.push(true); - } - catch (err) { - results.push(false); - } - } + constructor( + opts = {}, + data = new bip174_liquid_1.Psbt(new PsbtTransaction()), + ) { + this.data = data; + // set defaults + this.opts = Object.assign({}, DEFAULT_OPTS, opts); + this.__CACHE = { + __NON_WITNESS_UTXO_TX_CACHE: [], + __NON_WITNESS_UTXO_BUF_CACHE: [], + __TX_IN_CACHE: {}, + __TX: this.data.globalMap.unsignedTx.tx, + }; + if (this.data.inputs.length === 0) this.setVersion(2); + // Make data hidden when enumerating + const dpew = (obj, attr, enumerable, writable) => + Object.defineProperty(obj, attr, { + enumerable, + writable, + }); + 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_liquid_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; + } + combine(...those) { + this.data.combine(...those.map(o => o.data)); + return this; + } + clone() { + // TODO: more efficient cloning + const res = Psbt.fromBuffer(this.data.toBuffer()); + res.opts = JSON.parse(JSON.stringify(this.opts)); + return res; + } + setMaximumFeeRate(satoshiPerByte) { + check32Bit(satoshiPerByte); // 42.9 BTC per byte IS excessive... so throw + this.opts.maximumFeeRate = satoshiPerByte; + } + setVersion(version) { + check32Bit(version); + checkInputsForPartialSig(this.data.inputs, 'setVersion'); + const c = this.__CACHE; + c.__TX.version = version; + c.__EXTRACTED_TX = undefined; + return this; + } + setLocktime(locktime) { + check32Bit(locktime); + checkInputsForPartialSig(this.data.inputs, 'setLocktime'); + const c = this.__CACHE; + c.__TX.locktime = locktime; + c.__EXTRACTED_TX = undefined; + return this; + } + setInputSequence(inputIndex, sequence) { + check32Bit(sequence); + checkInputsForPartialSig(this.data.inputs, 'setInputSequence'); + const c = this.__CACHE; + if (c.__TX.ins.length <= inputIndex) { + throw new Error('Input index too high'); + } + c.__TX.ins[inputIndex].sequence = sequence; + c.__EXTRACTED_TX = undefined; + return this; + } + addInputs(inputDatas) { + inputDatas.forEach(inputData => this.addInput(inputData)); + return this; + } + addInput(inputData) { + if ( + arguments.length > 1 || + !inputData || + inputData.hash === undefined || + inputData.index === undefined + ) { + throw new Error( + `Invalid arguments for Psbt.addInput. ` + + `Requires single object with at least [hash] and [index]`, + ); + } + checkInputsForPartialSig(this.data.inputs, 'addInput'); + const c = this.__CACHE; + this.data.addInput(inputData); + const txIn = c.__TX.ins[c.__TX.ins.length - 1]; + checkTxInputCache(c, txIn); + const inputIndex = this.data.inputs.length - 1; + const input = this.data.inputs[inputIndex]; + if (input.nonWitnessUtxo) { + addNonWitnessTxCache(this.__CACHE, input, inputIndex); + } + c.__FEE = undefined; + c.__FEE_RATE = undefined; + c.__EXTRACTED_TX = undefined; + return this; + } + addIssuance(args, inputIndex) { + issuance_1.validateAddIssuanceArgs(args); // throw an error if args are invalid + if (inputIndex && !this.data.inputs[inputIndex]) { + throw new Error(`The input ${inputIndex} does not exist.`); + // check if the input is available for issuance. + } else { + // verify if there is at least one input available. + if (this.__CACHE.__TX.ins.filter(i => !i.issuance).length === 0) + throw new Error( + 'transaction needs at least one input without issuance data.', + ); + // search and extract the input index. + inputIndex = this.__CACHE.__TX.ins.findIndex(i => !i.issuance); + } + if (this.__CACHE.__TX.ins[inputIndex].issuance) + throw new Error(`The input ${inputIndex} already has issuance data.`); + const { hash, index } = this.__CACHE.__TX.ins[inputIndex]; + // create an issuance object using the vout and the args + const issuance = issuance_1.newIssuance( + args.assetAmount, + args.tokenAmount, + args.precision, + args.contract, + ); + // generate the entropy + const entropy = issuance_1.generateEntropy( + { txHash: hash, vout: index }, + issuance.assetEntropy, + ); + // add the issuance to the input. + this.__CACHE.__TX.ins[inputIndex].issuance = issuance; + const asset = Buffer.concat([ + Buffer.of(args.confidential ? 0x0a : 0x01), + issuance_1.calculateAsset(entropy), + ]); + const assetScript = address_1.toOutputScript(args.assetAddress); + // send the asset amount to the asset address. + this.addOutput({ + value: issuance.assetAmount, + script: assetScript, + asset, + nonce: Buffer.from('00', 'hex'), + }); + // check if the token amount is not 0 + if (args.tokenAmount !== 0) { + if (!args.tokenAddress) + throw new Error("tokenAddress can't be undefined if tokenAmount > 0"); + const token = issuance_1.calculateReissuanceToken( + entropy, + args.confidential, + ); + const tokenScript = address_1.toOutputScript(args.tokenAddress); + // send the token amount to the token address. + this.addOutput({ + script: tokenScript, + value: issuance.tokenAmount, + asset: Buffer.concat([Buffer.of(0x01), token]), + nonce: Buffer.from('00', 'hex'), + }); + } + return this; + } + addOutputs(outputDatas) { + outputDatas.forEach(outputData => this.addOutput(outputData)); + return this; + } + addOutput(outputData) { + if ( + arguments.length > 1 || + !outputData || + outputData.value === undefined || + (outputData.address === undefined && outputData.script === undefined) + ) { + throw new Error( + `Invalid arguments for Psbt.addOutput. ` + + `Requires single object with at least [script or address] and [value]`, + ); + } + checkInputsForPartialSig(this.data.inputs, 'addOutput'); + const { address } = outputData; + if (typeof address === 'string') { + const { network } = this.opts; + const script = address_1.toOutputScript(address, network); + outputData = Object.assign(outputData, { script }); + } + const c = this.__CACHE; + this.data.addOutput(outputData); + c.__FEE = undefined; + c.__FEE_RATE = undefined; + c.__EXTRACTED_TX = undefined; + return this; + } + extractTransaction(disableFeeCheck) { + if (!this.data.inputs.every(isFinalized)) throw new Error('Not finalized'); + const c = this.__CACHE; + if (!disableFeeCheck) { + checkFees(this, c, this.opts); + } + if (c.__EXTRACTED_TX) return c.__EXTRACTED_TX; + const tx = c.__TX.clone(); + inputFinalizeGetAmts(this.data.inputs, tx, c, true); + return tx; + } + getFeeRate() { + return getTxCacheValue( + '__FEE_RATE', + 'fee rate', + this.data.inputs, + this.__CACHE, + ); + } + getFee() { + return getTxCacheValue('__FEE', 'fee', this.data.inputs, this.__CACHE); + } + finalizeAllInputs() { + 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) { + const input = utils_1.checkForInput(this.data.inputs, inputIndex); + const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput( + inputIndex, + input, + this.__CACHE, + ); + if (!script) throw new Error(`No script found for input #${inputIndex}`); + const scriptType = classifyScript(script); + if (!canFinalize(input, script, scriptType)) + throw new Error(`Can not finalize input #${inputIndex}`); + checkPartialSigSighashes(input); + const { finalScriptSig, finalScriptWitness } = getFinalScripts( + script, + scriptType, + input.partialSig, + isSegwit, + isP2SH, + isP2WSH, + ); + if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig }); + if (finalScriptWitness) + this.data.updateInput(inputIndex, { finalScriptWitness }); + if (!finalScriptSig && !finalScriptWitness) + throw new Error(`Unknown error finalizing input #${inputIndex}`); + this.data.clearFinalizedInput(inputIndex); + return this; + } + validateSignaturesOfAllInputs() { + 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), + ); + return results.reduce((final, res) => res === true && final, true); + } + validateSignaturesOfInput(inputIndex, pubkey) { + const input = this.data.inputs[inputIndex]; + const partialSig = (input || {}).partialSig; + if (!input || !partialSig || partialSig.length < 1) + throw new Error('No signatures to validate'); + const mySigs = pubkey + ? partialSig.filter(sig => sig.pubkey.equals(pubkey)) + : partialSig; + if (mySigs.length < 1) throw new Error('No signatures for this pubkey'); + const results = []; + let hashCache; + let scriptCache; + let sighashCache; + for (const pSig of mySigs) { + const sig = bscript.signature.decode(pSig.signature); + const { hash, script } = + sighashCache !== sig.hashType + ? getHashForSig( + inputIndex, + Object.assign({}, input, { sighashType: sig.hashType }), + this.__CACHE, + ) + : { hash: hashCache, script: scriptCache }; + sighashCache = sig.hashType; + hashCache = hash; + scriptCache = script; + checkScriptForPubkey(pSig.pubkey, script, 'verify'); + const keypair = ecpair_1.fromPublicKey(pSig.pubkey); + results.push(keypair.verify(hash, sig.signature)); + } + return results.every(res => res === true); + } + signAllInputsHD( + hdKeyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + throw new Error('Need HDSigner to sign input'); + } + const results = []; + for (const i of range(this.data.inputs.length)) { + try { + this.signInputHD(i, hdKeyPair, sighashTypes); + results.push(true); + } catch (err) { + results.push(false); + } + } + if (results.every(v => v === false)) { + throw new Error('No inputs were signed'); + } + return this; + } + signAllInputsHDAsync( + hdKeyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + return new Promise((resolve, reject) => { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + return reject(new Error('Need HDSigner to sign input')); + } + const results = []; + const promises = []; + for (const i of range(this.data.inputs.length)) { + promises.push( + this.signInputHDAsync(i, hdKeyPair, sighashTypes).then( + () => { + results.push(true); + }, + () => { + results.push(false); + }, + ), + ); + } + return Promise.all(promises).then(() => { if (results.every(v => v === false)) { - throw new Error('No inputs were signed'); - } - return this; - } - signAllInputsHDAsync(hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { - return new Promise((resolve, reject) => { - if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { - return reject(new Error('Need HDSigner to sign input')); - } - const results = []; - const promises = []; - for (const i of range(this.data.inputs.length)) { - promises.push(this.signInputHDAsync(i, hdKeyPair, sighashTypes).then(() => { - results.push(true); - }, () => { - results.push(false); - })); - } - return Promise.all(promises).then(() => { - if (results.every(v => v === false)) { - return reject(new Error('No inputs were signed')); - } - resolve(); - }); - }); - } - signInputHD(inputIndex, hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { - if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { - throw new Error('Need HDSigner to sign input'); - } - const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair); - signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes)); - return this; - } - signInputHDAsync(inputIndex, hdKeyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { - return new Promise((resolve, reject) => { - if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { - return reject(new Error('Need HDSigner to sign input')); - } - const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair); - const promises = signers.map(signer => this.signInputAsync(inputIndex, signer, sighashTypes)); - return Promise.all(promises) - .then(() => { - resolve(); - }) - .catch(reject); - }); - } - signAllInputs(keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { - if (!keyPair || !keyPair.publicKey) - throw new Error('Need Signer to sign input'); - // TODO: Add a pubkey/pubkeyhash cache to each input - // as input information is added, then eventually - // optimize this method. - const results = []; - for (const i of range(this.data.inputs.length)) { - try { - this.signInput(i, keyPair, sighashTypes); - results.push(true); - } - catch (err) { - results.push(false); - } + return reject(new Error('No inputs were signed')); } + resolve(); + }); + }); + } + signInputHD( + inputIndex, + hdKeyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + throw new Error('Need HDSigner to sign input'); + } + const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair); + signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes)); + return this; + } + signInputHDAsync( + inputIndex, + hdKeyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + return new Promise((resolve, reject) => { + if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) { + return reject(new Error('Need HDSigner to sign input')); + } + const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair); + const promises = signers.map(signer => + this.signInputAsync(inputIndex, signer, sighashTypes), + ); + return Promise.all(promises) + .then(() => { + resolve(); + }) + .catch(reject); + }); + } + signAllInputs( + keyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + // TODO: Add a pubkey/pubkeyhash cache to each input + // as input information is added, then eventually + // optimize this method. + const results = []; + for (const i of range(this.data.inputs.length)) { + try { + this.signInput(i, keyPair, sighashTypes); + results.push(true); + } catch (err) { + results.push(false); + } + } + if (results.every(v => v === false)) { + throw new Error('No inputs were signed'); + } + return this; + } + signAllInputsAsync( + keyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + return new Promise((resolve, reject) => { + if (!keyPair || !keyPair.publicKey) + return reject(new Error('Need Signer to sign input')); + // TODO: Add a pubkey/pubkeyhash cache to each input + // as input information is added, then eventually + // optimize this method. + const results = []; + const promises = []; + for (const [i] of this.data.inputs.entries()) { + promises.push( + this.signInputAsync(i, keyPair, sighashTypes).then( + () => { + results.push(true); + }, + () => { + results.push(false); + }, + ), + ); + } + return Promise.all(promises).then(() => { if (results.every(v => v === false)) { - throw new Error('No inputs were signed'); + return reject(new Error('No inputs were signed')); } - return this; - } - signAllInputsAsync(keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { - return new Promise((resolve, reject) => { - if (!keyPair || !keyPair.publicKey) - return reject(new Error('Need Signer to sign input')); - // TODO: Add a pubkey/pubkeyhash cache to each input - // as input information is added, then eventually - // optimize this method. - const results = []; - const promises = []; - for (const [i] of this.data.inputs.entries()) { - promises.push(this.signInputAsync(i, keyPair, sighashTypes).then(() => { - results.push(true); - }, () => { - results.push(false); - })); - } - return Promise.all(promises).then(() => { - if (results.every(v => v === false)) { - return reject(new Error('No inputs were signed')); - } - resolve(); - }); - }); - } - signInput(inputIndex, keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { - if (!keyPair || !keyPair.publicKey) - throw new Error('Need Signer to sign input'); - const { hash, sighashType } = getHashAndSighashType(this.data.inputs, inputIndex, keyPair.publicKey, this.__CACHE, sighashTypes); + resolve(); + }); + }); + } + signInput( + inputIndex, + keyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + if (!keyPair || !keyPair.publicKey) + throw new Error('Need Signer to sign input'); + const { hash, sighashType } = getHashAndSighashType( + this.data.inputs, + inputIndex, + keyPair.publicKey, + this.__CACHE, + sighashTypes, + ); + const partialSig = [ + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(keyPair.sign(hash), sighashType), + }, + ]; + this.data.updateInput(inputIndex, { partialSig }); + return this; + } + signInputAsync( + inputIndex, + keyPair, + sighashTypes = [transaction_1.Transaction.SIGHASH_ALL], + ) { + return new Promise((resolve, reject) => { + if (!keyPair || !keyPair.publicKey) + return reject(new Error('Need Signer to sign input')); + const { hash, sighashType } = getHashAndSighashType( + this.data.inputs, + inputIndex, + keyPair.publicKey, + this.__CACHE, + sighashTypes, + ); + Promise.resolve(keyPair.sign(hash)).then(signature => { const partialSig = [ - { - pubkey: keyPair.publicKey, - signature: bscript.signature.encode(keyPair.sign(hash), sighashType), - }, + { + pubkey: keyPair.publicKey, + signature: bscript.signature.encode(signature, sighashType), + }, ]; this.data.updateInput(inputIndex, { partialSig }); - return this; - } - signInputAsync(inputIndex, keyPair, sighashTypes = [transaction_1.Transaction.SIGHASH_ALL]) { - return new Promise((resolve, reject) => { - if (!keyPair || !keyPair.publicKey) - return reject(new Error('Need Signer to sign input')); - const { hash, sighashType } = getHashAndSighashType(this.data.inputs, inputIndex, keyPair.publicKey, this.__CACHE, sighashTypes); - Promise.resolve(keyPair.sign(hash)).then(signature => { - const partialSig = [ - { - pubkey: keyPair.publicKey, - signature: bscript.signature.encode(signature, sighashType), - }, - ]; - this.data.updateInput(inputIndex, { partialSig }); - resolve(); - }); - }); - } - toBuffer() { - return this.data.toBuffer(); - } - toHex() { - return this.data.toHex(); - } - toBase64() { - return this.data.toBase64(); - } - updateGlobal(updateData) { - this.data.updateGlobal(updateData); - return this; - } - updateInput(inputIndex, updateData) { - if (updateData.witnessUtxo) { - const { witnessUtxo } = updateData; - const script = Buffer.isBuffer(witnessUtxo.script) - ? witnessUtxo.script - : Buffer.from(witnessUtxo.script, 'hex'); - const value = Buffer.isBuffer(witnessUtxo.value) - ? witnessUtxo.value - : typeof witnessUtxo.value === 'string' - ? Buffer.from(witnessUtxo.value, 'hex') - : confidential.satoshiToConfidentialValue(witnessUtxo.value); - // if the asset is a string, by checking the first byte we can determine if - // it's an asset commitment, in this case we decode the hex string as buffer, - // or if it's an asset hash, in this case we put the unconf prefix in front of the reversed the buffer - const asset = Buffer.isBuffer(witnessUtxo.asset) - ? witnessUtxo.asset - : witnessUtxo.asset.startsWith('0a') || - witnessUtxo.asset.startsWith('0b') - ? Buffer.from(witnessUtxo.asset, 'hex') - : Buffer.concat([ - Buffer.alloc(1, 1), - bufferutils_1.reverseBuffer(Buffer.from(witnessUtxo.asset, 'hex')), - ]); - const nonce = witnessUtxo.nonce - ? Buffer.isBuffer(witnessUtxo.nonce) - ? witnessUtxo.nonce - : Buffer.from(witnessUtxo.nonce, 'hex') - : Buffer.alloc(1, 0); - const rangeProof = witnessUtxo.rangeProof - ? Buffer.isBuffer(witnessUtxo.rangeProof) - ? witnessUtxo.rangeProof - : Buffer.from(witnessUtxo.rangeProof, 'hex') - : undefined; - const surjectionProof = witnessUtxo.surjectionProof - ? Buffer.isBuffer(witnessUtxo.surjectionProof) - ? witnessUtxo.surjectionProof - : Buffer.from(witnessUtxo.surjectionProof, 'hex') - : undefined; - updateData = Object.assign(updateData, { - witnessUtxo: { - script, - value, - asset, - nonce, - rangeProof, - surjectionProof, - }, - }); - } - this.data.updateInput(inputIndex, updateData); - if (updateData.nonWitnessUtxo) { - addNonWitnessTxCache(this.__CACHE, this.data.inputs[inputIndex], inputIndex); + resolve(); + }); + }); + } + toBuffer() { + return this.data.toBuffer(); + } + toHex() { + return this.data.toHex(); + } + toBase64() { + return this.data.toBase64(); + } + updateGlobal(updateData) { + this.data.updateGlobal(updateData); + return this; + } + updateInput(inputIndex, updateData) { + if (updateData.witnessUtxo) { + const { witnessUtxo } = updateData; + const script = Buffer.isBuffer(witnessUtxo.script) + ? witnessUtxo.script + : Buffer.from(witnessUtxo.script, 'hex'); + const value = Buffer.isBuffer(witnessUtxo.value) + ? witnessUtxo.value + : typeof witnessUtxo.value === 'string' + ? Buffer.from(witnessUtxo.value, 'hex') + : confidential.satoshiToConfidentialValue(witnessUtxo.value); + // if the asset is a string, by checking the first byte we can determine if + // it's an asset commitment, in this case we decode the hex string as buffer, + // or if it's an asset hash, in this case we put the unconf prefix in front of the reversed the buffer + const asset = Buffer.isBuffer(witnessUtxo.asset) + ? witnessUtxo.asset + : witnessUtxo.asset.startsWith('0a') || + witnessUtxo.asset.startsWith('0b') + ? Buffer.from(witnessUtxo.asset, 'hex') + : Buffer.concat([ + Buffer.alloc(1, 1), + bufferutils_1.reverseBuffer(Buffer.from(witnessUtxo.asset, 'hex')), + ]); + const nonce = witnessUtxo.nonce + ? Buffer.isBuffer(witnessUtxo.nonce) + ? witnessUtxo.nonce + : Buffer.from(witnessUtxo.nonce, 'hex') + : Buffer.alloc(1, 0); + const rangeProof = witnessUtxo.rangeProof + ? Buffer.isBuffer(witnessUtxo.rangeProof) + ? witnessUtxo.rangeProof + : Buffer.from(witnessUtxo.rangeProof, 'hex') + : undefined; + const surjectionProof = witnessUtxo.surjectionProof + ? Buffer.isBuffer(witnessUtxo.surjectionProof) + ? witnessUtxo.surjectionProof + : Buffer.from(witnessUtxo.surjectionProof, 'hex') + : undefined; + updateData = Object.assign(updateData, { + witnessUtxo: { + script, + value, + asset, + nonce, + rangeProof, + surjectionProof, + }, + }); + } + this.data.updateInput(inputIndex, updateData); + if (updateData.nonWitnessUtxo) { + addNonWitnessTxCache( + this.__CACHE, + this.data.inputs[inputIndex], + inputIndex, + ); + } + return this; + } + updateOutput(outputIndex, updateData) { + this.data.updateOutput(outputIndex, updateData); + return this; + } + blindOutputs(blindingDataLike, blindingPubkeys, opts) { + return this.rawBlindOutputs( + blindingDataLike, + blindingPubkeys, + undefined, + undefined, + opts, + ); + } + blindOutputsByIndex( + inputsBlindingData, + outputsBlindingPubKeys, + issuancesBlindingKeys, + opts, + ) { + const blindingPrivKeysArgs = range(this.__CACHE.__TX.ins.length).map( + inputIndex => inputsBlindingData.get(inputIndex), + ); + const blindingPrivKeysIssuancesArgs = issuancesBlindingKeys + ? range(this.__CACHE.__TX.ins.length).map(inputIndex => + issuancesBlindingKeys.get(inputIndex), + ) + : []; + const outputIndexes = []; + const blindingPublicKey = []; + for (const [outputIndex, pubBlindingKey] of outputsBlindingPubKeys) { + outputIndexes.push(outputIndex); + blindingPublicKey.push(pubBlindingKey); + } + return this.rawBlindOutputs( + blindingPrivKeysArgs, + blindingPublicKey, + blindingPrivKeysIssuancesArgs, + outputIndexes, + opts, + ); + } + addUnknownKeyValToGlobal(keyVal) { + this.data.addUnknownKeyValToGlobal(keyVal); + return this; + } + addUnknownKeyValToInput(inputIndex, keyVal) { + this.data.addUnknownKeyValToInput(inputIndex, keyVal); + return this; + } + addUnknownKeyValToOutput(outputIndex, keyVal) { + this.data.addUnknownKeyValToOutput(outputIndex, keyVal); + return this; + } + clearFinalizedInput(inputIndex) { + this.data.clearFinalizedInput(inputIndex); + return this; + } + unblindInputsToIssuanceBlindingData(issuanceBlindingPrivKeys = []) { + const pseudoBlindingDataFromIssuances = []; + let inputIndex = 0; + for (const input of this.__CACHE.__TX.ins) { + if (input.issuance) { + const isConfidentialIssuance = + issuanceBlindingPrivKeys && issuanceBlindingPrivKeys[inputIndex] + ? true + : false; + const entropy = issuance_1.generateEntropy( + { txHash: input.hash, vout: input.index }, + input.issuance.assetEntropy, + ); + const asset = issuance_1.calculateAsset(entropy); + const value = confidential + .confidentialValueToSatoshi(input.issuance.assetAmount) + .toString(10); + const assetBlindingData = { + value, + asset, + assetBlindingFactor: transaction_1.ZERO, + valueBlindingFactor: isConfidentialIssuance + ? randomBytes() + : transaction_1.ZERO, + }; + pseudoBlindingDataFromIssuances.push(assetBlindingData); + if (issuance_1.hasTokenAmount(input.issuance)) { + const token = issuance_1.calculateReissuanceToken( + entropy, + isConfidentialIssuance, + ); + const tokenValue = confidential + .confidentialValueToSatoshi(input.issuance.tokenAmount) + .toString(10); + const tokenBlindingData = { + value: tokenValue, + asset: token, + assetBlindingFactor: transaction_1.ZERO, + valueBlindingFactor: isConfidentialIssuance + ? randomBytes() + : transaction_1.ZERO, + }; + pseudoBlindingDataFromIssuances.push(tokenBlindingData); } - return this; - } - updateOutput(outputIndex, updateData) { - this.data.updateOutput(outputIndex, updateData); - return this; - } - blindOutputs(blindingDataLike, blindingPubkeys, opts) { - return this.rawBlindOutputs(blindingDataLike, blindingPubkeys, undefined, undefined, opts); - } - blindOutputsByIndex(inputsBlindingData, outputsBlindingPubKeys, issuancesBlindingKeys, opts) { - const blindingPrivKeysArgs = range(this.__CACHE.__TX.ins.length).map((inputIndex) => inputsBlindingData.get(inputIndex)); - const blindingPrivKeysIssuancesArgs = issuancesBlindingKeys - ? range(this.__CACHE.__TX.ins.length).map((inputIndex) => issuancesBlindingKeys.get(inputIndex)) - : []; - const outputIndexes = []; - const blindingPublicKey = []; - for (const [outputIndex, pubBlindingKey] of outputsBlindingPubKeys) { - outputIndexes.push(outputIndex); - blindingPublicKey.push(pubBlindingKey); + } + inputIndex++; + } + return pseudoBlindingDataFromIssuances; + } + blindInputs(blindingData, issuanceBlindingPrivKeys = []) { + return __awaiter(this, void 0, void 0, function*() { + if (!issuanceBlindingPrivKeys || issuanceBlindingPrivKeys.length === 0) + return this; // skip if no issuance blind keys + function getBlindingFactors(asset) { + for (const blindData of blindingData) { + if (asset.equals(blindData.asset)) { + return blindData; + } } - return this.rawBlindOutputs(blindingPrivKeysArgs, blindingPublicKey, blindingPrivKeysIssuancesArgs, outputIndexes, opts); - } - addUnknownKeyValToGlobal(keyVal) { - this.data.addUnknownKeyValToGlobal(keyVal); - return this; - } - addUnknownKeyValToInput(inputIndex, keyVal) { - this.data.addUnknownKeyValToInput(inputIndex, keyVal); - return this; - } - addUnknownKeyValToOutput(outputIndex, keyVal) { - this.data.addUnknownKeyValToOutput(outputIndex, keyVal); - return this; - } - clearFinalizedInput(inputIndex) { - this.data.clearFinalizedInput(inputIndex); - return this; - } - unblindInputsToIssuanceBlindingData(issuanceBlindingPrivKeys = []) { - const pseudoBlindingDataFromIssuances = []; - let inputIndex = 0; - for (const input of this.__CACHE.__TX.ins) { - if (input.issuance) { - const isConfidentialIssuance = issuanceBlindingPrivKeys && issuanceBlindingPrivKeys[inputIndex] - ? true - : false; - const entropy = issuance_1.generateEntropy({ txHash: input.hash, vout: input.index }, input.issuance.assetEntropy); - const asset = issuance_1.calculateAsset(entropy); - const value = confidential - .confidentialValueToSatoshi(input.issuance.assetAmount) - .toString(10); - const assetBlindingData = { - value, - asset, - assetBlindingFactor: transaction_1.ZERO, - valueBlindingFactor: isConfidentialIssuance ? randomBytes() : transaction_1.ZERO, - }; - pseudoBlindingDataFromIssuances.push(assetBlindingData); - if (issuance_1.hasTokenAmount(input.issuance)) { - const token = issuance_1.calculateReissuanceToken(entropy, isConfidentialIssuance); - const tokenValue = confidential - .confidentialValueToSatoshi(input.issuance.tokenAmount) - .toString(10); - const tokenBlindingData = { - value: tokenValue, - asset: token, - assetBlindingFactor: transaction_1.ZERO, - valueBlindingFactor: isConfidentialIssuance ? randomBytes() : transaction_1.ZERO, - }; - pseudoBlindingDataFromIssuances.push(tokenBlindingData); - } - } + throw new Error( + 'no blinding factors generated for pseudo issuance inputs', + ); + } + // loop over inputs and create blindingData object in case of issuance + let inputIndex = 0; + for (const input of this.__CACHE.__TX.ins) { + if (input.issuance) { + if (!issuanceBlindingPrivKeys[inputIndex]) { + // check if the user has provided blinding key inputIndex++; + continue; + } + const entropy = issuance_1.generateEntropy( + { txHash: input.hash, vout: input.index }, + input.issuance.assetEntropy, + ); + const issuedAsset = issuance_1.calculateAsset(entropy); + const blindingFactorsAsset = getBlindingFactors(issuedAsset); + const assetCommitment = yield confidential.assetCommitment( + blindingFactorsAsset.asset, + blindingFactorsAsset.assetBlindingFactor, + ); + const valueCommitment = yield confidential.valueCommitment( + blindingFactorsAsset.value, + assetCommitment, + blindingFactorsAsset.valueBlindingFactor, + ); + const assetBlindingPrivateKey = issuanceBlindingPrivKeys[inputIndex] + ? issuanceBlindingPrivKeys[inputIndex].assetKey + : undefined; + if (!assetBlindingPrivateKey) { + throw new Error( + `missing asset blinding private key for issuance #${inputIndex}`, + ); + } + const issuanceRangeProof = yield confidential.rangeProof( + blindingFactorsAsset.value, + assetBlindingPrivateKey, + blindingFactorsAsset.asset, + blindingFactorsAsset.assetBlindingFactor, + blindingFactorsAsset.valueBlindingFactor, + valueCommitment, + Buffer.alloc(0), + '1', + 0, + 52, + ); + this.__CACHE.__TX.ins[ + inputIndex + ].issuanceRangeProof = issuanceRangeProof; + this.__CACHE.__TX.ins[ + inputIndex + ].issuance.assetAmount = valueCommitment; + if (issuance_1.hasTokenAmount(input.issuance)) { + const token = issuance_1.calculateReissuanceToken(entropy, true); + const blindingFactorsToken = getBlindingFactors(token); + const issuedTokenCommitment = yield confidential.assetCommitment( + token, + blindingFactorsToken.assetBlindingFactor, + ); + const tokenValueCommitment = yield confidential.valueCommitment( + blindingFactorsToken.value, + issuedTokenCommitment, + blindingFactorsToken.valueBlindingFactor, + ); + const inflationRangeProof = yield confidential.rangeProof( + blindingFactorsToken.value, + issuanceBlindingPrivKeys[inputIndex].tokenKey, + token, + blindingFactorsToken.assetBlindingFactor, + blindingFactorsToken.valueBlindingFactor, + tokenValueCommitment, + Buffer.alloc(0), + '1', + 0, + 52, + ); + this.__CACHE.__TX.ins[ + inputIndex + ].inflationRangeProof = inflationRangeProof; + this.__CACHE.__TX.ins[ + inputIndex + ].issuance.tokenAmount = tokenValueCommitment; + } } - return pseudoBlindingDataFromIssuances; - } - blindInputs(blindingData, issuanceBlindingPrivKeys = []) { - return __awaiter(this, void 0, void 0, function* () { - if (!issuanceBlindingPrivKeys || issuanceBlindingPrivKeys.length === 0) - return this; // skip if no issuance blind keys - function getBlindingFactors(asset) { - for (const blindData of blindingData) { - if (asset.equals(blindData.asset)) { - return blindData; - } - } - throw new Error('no blinding factors generated for pseudo issuance inputs'); - } - // loop over inputs and create blindingData object in case of issuance - let inputIndex = 0; - for (const input of this.__CACHE.__TX.ins) { - if (input.issuance) { - if (!issuanceBlindingPrivKeys[inputIndex]) { - // check if the user has provided blinding key - inputIndex++; - continue; - } - const entropy = issuance_1.generateEntropy({ txHash: input.hash, vout: input.index }, input.issuance.assetEntropy); - const issuedAsset = issuance_1.calculateAsset(entropy); - const blindingFactorsAsset = getBlindingFactors(issuedAsset); - const assetCommitment = yield confidential.assetCommitment(blindingFactorsAsset.asset, blindingFactorsAsset.assetBlindingFactor); - const valueCommitment = yield confidential.valueCommitment(blindingFactorsAsset.value, assetCommitment, blindingFactorsAsset.valueBlindingFactor); - const assetBlindingPrivateKey = issuanceBlindingPrivKeys[inputIndex] - ? issuanceBlindingPrivKeys[inputIndex].assetKey - : undefined; - if (!assetBlindingPrivateKey) { - throw new Error(`missing asset blinding private key for issuance #${inputIndex}`); - } - const issuanceRangeProof = yield confidential.rangeProof(blindingFactorsAsset.value, assetBlindingPrivateKey, blindingFactorsAsset.asset, blindingFactorsAsset.assetBlindingFactor, blindingFactorsAsset.valueBlindingFactor, valueCommitment, Buffer.alloc(0), '1', 0, 52); - this.__CACHE.__TX.ins[inputIndex].issuanceRangeProof = issuanceRangeProof; - this.__CACHE.__TX.ins[inputIndex].issuance.assetAmount = valueCommitment; - if (issuance_1.hasTokenAmount(input.issuance)) { - const token = issuance_1.calculateReissuanceToken(entropy, true); - const blindingFactorsToken = getBlindingFactors(token); - const issuedTokenCommitment = yield confidential.assetCommitment(token, blindingFactorsToken.assetBlindingFactor); - const tokenValueCommitment = yield confidential.valueCommitment(blindingFactorsToken.value, issuedTokenCommitment, blindingFactorsToken.valueBlindingFactor); - const inflationRangeProof = yield confidential.rangeProof(blindingFactorsToken.value, issuanceBlindingPrivKeys[inputIndex].tokenKey, token, blindingFactorsToken.assetBlindingFactor, blindingFactorsToken.valueBlindingFactor, tokenValueCommitment, Buffer.alloc(0), '1', 0, 52); - this.__CACHE.__TX.ins[inputIndex].inflationRangeProof = inflationRangeProof; - this.__CACHE.__TX.ins[inputIndex].issuance.tokenAmount = tokenValueCommitment; - } - } - inputIndex++; - } - return this; - }); - } - blindOutputsRaw(blindingData, blindingPubkeys, outputIndexes, opts) { - return __awaiter(this, void 0, void 0, function* () { - // get data (satoshis & asset) outputs to blind - const outputsData = outputIndexes.map((index) => { - const output = this.__CACHE.__TX.outs[index]; - // prevent blinding the fee output - if (output.script.length === 0) - throw new Error("cant't blind the fee output"); - const value = confidential - .confidentialValueToSatoshi(output.value) - .toString(10); - return [value, output.asset.slice(1)]; - }); - // compute the outputs blinders - const outputsBlindingData = yield computeOutputsBlindingData(blindingData, outputsData); - // use blinders to compute proofs & commitments - let indexInArray = 0; - for (const outputIndex of outputIndexes) { - const randomSeed = randomBytes(opts); - const ephemeralPrivKey = randomBytes(opts); - const outputNonce = ecpair_1.fromPrivateKey(ephemeralPrivKey).publicKey; - const outputBlindingData = outputsBlindingData[indexInArray]; - // commitments - const assetCommitment = yield confidential.assetCommitment(outputBlindingData.asset, outputBlindingData.assetBlindingFactor); - const valueCommitment = yield confidential.valueCommitment(outputBlindingData.value, assetCommitment, outputBlindingData.valueBlindingFactor); - // proofs - const rangeProof = yield confidential.rangeProofWithNonceHash(outputBlindingData.value, blindingPubkeys[indexInArray], ephemeralPrivKey, outputBlindingData.asset, outputBlindingData.assetBlindingFactor, outputBlindingData.valueBlindingFactor, valueCommitment, this.__CACHE.__TX.outs[outputIndex].script); - const surjectionProof = yield confidential.surjectionProof(outputBlindingData.asset, outputBlindingData.assetBlindingFactor, blindingData.map(({ asset }) => asset), blindingData.map(({ assetBlindingFactor }) => assetBlindingFactor), randomSeed); - // set commitments & proofs & nonce - this.__CACHE.__TX.outs[outputIndex].asset = assetCommitment; - this.__CACHE.__TX.outs[outputIndex].value = valueCommitment; - this.__CACHE.__TX.setOutputNonce(outputIndex, outputNonce); - this.__CACHE.__TX.setOutputRangeProof(outputIndex, rangeProof); - this.__CACHE.__TX.setOutputSurjectionProof(outputIndex, surjectionProof); - indexInArray++; - } - return this; - }); - } - rawBlindOutputs(blindingDataLike, blindingPubkeys, issuanceBlindingPrivKeys = [], outputIndexes, opts) { - return __awaiter(this, void 0, void 0, function* () { - if (this.data.inputs.some((v) => !v.nonWitnessUtxo && !v.witnessUtxo)) - throw new Error('All inputs must contain a non witness utxo or a witness utxo'); - if (this.__CACHE.__TX.ins.length !== blindingDataLike.length) { - throw new Error('blindingDataLike length does not match the number of inputs (undefined for unconfidential utxo)'); - } - if (!outputIndexes) { - outputIndexes = []; - // fill the outputIndexes array with all the output index (except the fee output) - this.__CACHE.__TX.outs.forEach((out, index) => { - if (out.script.length > 0) - outputIndexes.push(index); - }); - } - if (outputIndexes.length !== blindingPubkeys.length) - throw new Error('not enough blinding public keys to blind the requested outputs'); - const witnesses = this.data.inputs.map((input, index) => { - if (input.nonWitnessUtxo) { - const prevTx = nonWitnessUtxoTxFromCache(this.__CACHE, input, index); - const prevoutIndex = this.__CACHE.__TX.ins[index].index; - return prevTx.outs[prevoutIndex]; - } - if (input.witnessUtxo) { - return input.witnessUtxo; - } - throw new Error('input data needs witness utxo or nonwitness utxo'); - }); - const inputsBlindingData = yield Promise.all(blindingDataLike.map((data, i) => toBlindingData(data, witnesses[i]))); - const pseudoInputsBlindingData = this.unblindInputsToIssuanceBlindingData(issuanceBlindingPrivKeys); - const totalBlindingData = inputsBlindingData.concat(pseudoInputsBlindingData); - yield this.blindOutputsRaw(totalBlindingData, blindingPubkeys, outputIndexes, opts); - yield this.blindInputs(totalBlindingData, issuanceBlindingPrivKeys); - this.__CACHE.__FEE = undefined; - this.__CACHE.__FEE_RATE = undefined; - this.__CACHE.__EXTRACTED_TX = undefined; - return this; + inputIndex++; + } + return this; + }); + } + blindOutputsRaw(blindingData, blindingPubkeys, outputIndexes, opts) { + return __awaiter(this, void 0, void 0, function*() { + // get data (satoshis & asset) outputs to blind + const outputsData = outputIndexes.map(index => { + const output = this.__CACHE.__TX.outs[index]; + // prevent blinding the fee output + if (output.script.length === 0) + throw new Error("cant't blind the fee output"); + const value = confidential + .confidentialValueToSatoshi(output.value) + .toString(10); + return [value, output.asset.slice(1)]; + }); + // compute the outputs blinders + const outputsBlindingData = yield computeOutputsBlindingData( + blindingData, + outputsData, + ); + // use blinders to compute proofs & commitments + let indexInArray = 0; + for (const outputIndex of outputIndexes) { + const randomSeed = randomBytes(opts); + const ephemeralPrivKey = randomBytes(opts); + const outputNonce = ecpair_1.fromPrivateKey(ephemeralPrivKey).publicKey; + const outputBlindingData = outputsBlindingData[indexInArray]; + // commitments + const assetCommitment = yield confidential.assetCommitment( + outputBlindingData.asset, + outputBlindingData.assetBlindingFactor, + ); + const valueCommitment = yield confidential.valueCommitment( + outputBlindingData.value, + assetCommitment, + outputBlindingData.valueBlindingFactor, + ); + // proofs + const rangeProof = yield confidential.rangeProofWithNonceHash( + outputBlindingData.value, + blindingPubkeys[indexInArray], + ephemeralPrivKey, + outputBlindingData.asset, + outputBlindingData.assetBlindingFactor, + outputBlindingData.valueBlindingFactor, + valueCommitment, + this.__CACHE.__TX.outs[outputIndex].script, + ); + const surjectionProof = yield confidential.surjectionProof( + outputBlindingData.asset, + outputBlindingData.assetBlindingFactor, + blindingData.map(({ asset }) => asset), + blindingData.map(({ assetBlindingFactor }) => assetBlindingFactor), + randomSeed, + ); + // set commitments & proofs & nonce + this.__CACHE.__TX.outs[outputIndex].asset = assetCommitment; + this.__CACHE.__TX.outs[outputIndex].value = valueCommitment; + this.__CACHE.__TX.setOutputNonce(outputIndex, outputNonce); + this.__CACHE.__TX.setOutputRangeProof(outputIndex, rangeProof); + this.__CACHE.__TX.setOutputSurjectionProof( + outputIndex, + surjectionProof, + ); + indexInArray++; + } + return this; + }); + } + rawBlindOutputs( + blindingDataLike, + blindingPubkeys, + issuanceBlindingPrivKeys = [], + outputIndexes, + opts, + ) { + return __awaiter(this, void 0, void 0, function*() { + if (this.data.inputs.some(v => !v.nonWitnessUtxo && !v.witnessUtxo)) + throw new Error( + 'All inputs must contain a non witness utxo or a witness utxo', + ); + if (this.__CACHE.__TX.ins.length !== blindingDataLike.length) { + throw new Error( + 'blindingDataLike length does not match the number of inputs (undefined for unconfidential utxo)', + ); + } + if (!outputIndexes) { + outputIndexes = []; + // fill the outputIndexes array with all the output index (except the fee output) + this.__CACHE.__TX.outs.forEach((out, index) => { + if (out.script.length > 0) outputIndexes.push(index); }); - } + } + if (outputIndexes.length !== blindingPubkeys.length) + throw new Error( + 'not enough blinding public keys to blind the requested outputs', + ); + const witnesses = this.data.inputs.map((input, index) => { + if (input.nonWitnessUtxo) { + const prevTx = nonWitnessUtxoTxFromCache(this.__CACHE, input, index); + const prevoutIndex = this.__CACHE.__TX.ins[index].index; + return prevTx.outs[prevoutIndex]; + } + if (input.witnessUtxo) { + return input.witnessUtxo; + } + throw new Error('input data needs witness utxo or nonwitness utxo'); + }); + const inputsBlindingData = yield Promise.all( + blindingDataLike.map((data, i) => toBlindingData(data, witnesses[i])), + ); + const pseudoInputsBlindingData = this.unblindInputsToIssuanceBlindingData( + issuanceBlindingPrivKeys, + ); + const totalBlindingData = inputsBlindingData.concat( + pseudoInputsBlindingData, + ); + yield this.blindOutputsRaw( + totalBlindingData, + blindingPubkeys, + outputIndexes, + opts, + ); + yield this.blindInputs(totalBlindingData, issuanceBlindingPrivKeys); + this.__CACHE.__FEE = undefined; + this.__CACHE.__FEE_RATE = undefined; + this.__CACHE.__EXTRACTED_TX = undefined; + return this; + }); + } } exports.Psbt = Psbt; /** @@ -751,116 +1005,113 @@ exports.Psbt = Psbt; * It takes the "transaction buffer" portion of the psbt buffer and returns a * Transaction (From the bip174 library) interface. */ -const transactionFromBuffer = (buffer) => new PsbtTransaction(buffer); +const transactionFromBuffer = buffer => new PsbtTransaction(buffer); /** * This class implements the Transaction interface from bip174 library. * It contains a liquidjs-lib Transaction object. */ class PsbtTransaction { - constructor(buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) { - this.tx = transaction_1.Transaction.fromBuffer(buffer); - checkTxEmpty(this.tx); - Object.defineProperty(this, 'tx', { - enumerable: false, - writable: true, - }); - } - getInputOutputCounts() { - return { - inputCount: this.tx.ins.length, - outputCount: this.tx.outs.length, - }; - } - addInput(input) { - if (input.hash === undefined || - input.index === undefined || - (!Buffer.isBuffer(input.hash) && - typeof input.hash !== 'string') || - typeof input.index !== 'number') { - throw new Error('Error adding input.'); - } - const hash = typeof input.hash === 'string' - ? bufferutils_1.reverseBuffer(Buffer.from(input.hash, 'hex')) - : input.hash; - this.tx.addInput(hash, input.index, input.sequence); - } - addOutput(output) { - if (output.script === undefined || - (!Buffer.isBuffer(output.script) && - typeof output.script !== 'string') || - output.value === undefined || - (!Buffer.isBuffer(output.value) && - typeof output.value !== 'number') || - output.asset === undefined || - (!Buffer.isBuffer(output.asset) && - typeof output.asset !== 'string')) { - throw new Error('Error adding output.'); - } - const nonce = Buffer.alloc(1, 0); - const script = Buffer.isBuffer(output.script) - ? output.script - : Buffer.from(output.script, 'hex'); - const value = Buffer.isBuffer(output.value) - ? output.value - : confidential.satoshiToConfidentialValue(output.value); - const asset = Buffer.isBuffer(output.asset) - ? output.asset - : Buffer.concat([ - Buffer.alloc(1, 1), - bufferutils_1.reverseBuffer(Buffer.from(output.asset, 'hex')), - ]); - this.tx.addOutput(script, value, asset, nonce); - } - toBuffer() { - return this.tx.toBuffer(); - } + constructor(buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])) { + this.tx = transaction_1.Transaction.fromBuffer(buffer); + checkTxEmpty(this.tx); + Object.defineProperty(this, 'tx', { + enumerable: false, + writable: true, + }); + } + getInputOutputCounts() { + return { + inputCount: this.tx.ins.length, + outputCount: this.tx.outs.length, + }; + } + addInput(input) { + if ( + input.hash === undefined || + input.index === undefined || + (!Buffer.isBuffer(input.hash) && typeof input.hash !== 'string') || + typeof input.index !== 'number' + ) { + throw new Error('Error adding input.'); + } + const hash = + typeof input.hash === 'string' + ? bufferutils_1.reverseBuffer(Buffer.from(input.hash, 'hex')) + : input.hash; + this.tx.addInput(hash, input.index, input.sequence); + } + addOutput(output) { + if ( + output.script === undefined || + (!Buffer.isBuffer(output.script) && typeof output.script !== 'string') || + output.value === undefined || + (!Buffer.isBuffer(output.value) && typeof output.value !== 'number') || + output.asset === undefined || + (!Buffer.isBuffer(output.asset) && typeof output.asset !== 'string') + ) { + throw new Error('Error adding output.'); + } + const nonce = Buffer.alloc(1, 0); + const script = Buffer.isBuffer(output.script) + ? output.script + : Buffer.from(output.script, 'hex'); + const value = Buffer.isBuffer(output.value) + ? output.value + : confidential.satoshiToConfidentialValue(output.value); + const asset = Buffer.isBuffer(output.asset) + ? output.asset + : Buffer.concat([ + Buffer.alloc(1, 1), + bufferutils_1.reverseBuffer(Buffer.from(output.asset, 'hex')), + ]); + this.tx.addOutput(script, value, asset, nonce); + } + toBuffer() { + return this.tx.toBuffer(); + } } function canFinalize(input, script, scriptType) { - switch (scriptType) { - case 'pubkey': - case 'pubkeyhash': - case 'witnesspubkeyhash': - return hasSigs(1, input.partialSig); - case 'multisig': - const p2ms = payments.p2ms({ output: script }); - return hasSigs(p2ms.m, input.partialSig, p2ms.pubkeys); - default: - return false; - } + switch (scriptType) { + case 'pubkey': + case 'pubkeyhash': + case 'witnesspubkeyhash': + return hasSigs(1, input.partialSig); + case 'multisig': + const p2ms = payments.p2ms({ output: script }); + return hasSigs(p2ms.m, input.partialSig, p2ms.pubkeys); + default: + return false; + } } function hasSigs(neededSigs, partialSig, pubkeys) { - if (!partialSig) - return false; - let sigs; - if (pubkeys) { - sigs = pubkeys - .map(pkey => { - const pubkey = ecpair_1.fromPublicKey(pkey, { compressed: true }) - .publicKey; - return partialSig.find(pSig => pSig.pubkey.equals(pubkey)); - }) - .filter(v => !!v); - } - else { - sigs = partialSig; - } - if (sigs.length > neededSigs) - throw new Error('Too many signatures'); - return sigs.length === neededSigs; + if (!partialSig) return false; + let sigs; + if (pubkeys) { + sigs = pubkeys + .map(pkey => { + const pubkey = ecpair_1.fromPublicKey(pkey, { compressed: true }) + .publicKey; + return partialSig.find(pSig => pSig.pubkey.equals(pubkey)); + }) + .filter(v => !!v); + } else { + sigs = partialSig; + } + if (sigs.length > neededSigs) throw new Error('Too many signatures'); + return sigs.length === neededSigs; } function isFinalized(input) { - return !!input.finalScriptSig || !!input.finalScriptWitness; + return !!input.finalScriptSig || !!input.finalScriptWitness; } function isPaymentFactory(payment) { - return (script) => { - try { - payment({ output: script }); - return true; - } - catch (err) { - return false; - } - }; + return script => { + try { + payment({ output: script }); + return true; + } catch (err) { + return false; + } + }; } const isP2MS = isPaymentFactory(payments.p2ms); const isP2PK = isPaymentFactory(payments.p2pk); @@ -868,514 +1119,561 @@ const isP2PKH = isPaymentFactory(payments.p2pkh); const isP2WPKH = isPaymentFactory(payments.p2wpkh); const isP2WSHScript = isPaymentFactory(payments.p2wsh); function check32Bit(num) { - if (typeof num !== 'number' || - num !== Math.floor(num) || - num > 0xffffffff || - num < 0) { - throw new Error('Invalid 32 bit integer'); - } + if ( + typeof num !== 'number' || + num !== Math.floor(num) || + num > 0xffffffff || + num < 0 + ) { + throw new Error('Invalid 32 bit integer'); + } } function checkFees(psbt, cache, opts) { - const feeRate = cache.__FEE_RATE || psbt.getFeeRate(); - const vsize = cache.__EXTRACTED_TX.virtualSize(); - const satoshis = feeRate * vsize; - if (feeRate >= opts.maximumFeeRate) { - throw new Error(`Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` + - `fees, which is ${feeRate} satoshi per byte for a transaction ` + - `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` + - `byte). Use setMaximumFeeRate method to raise your threshold, or ` + - `pass true to the first arg of extractTransaction.`); - } + const feeRate = cache.__FEE_RATE || psbt.getFeeRate(); + const vsize = cache.__EXTRACTED_TX.virtualSize(); + const satoshis = feeRate * vsize; + if (feeRate >= opts.maximumFeeRate) { + throw new Error( + `Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` + + `fees, which is ${feeRate} satoshi per byte for a transaction ` + + `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` + + `byte). Use setMaximumFeeRate method to raise your threshold, or ` + + `pass true to the first arg of extractTransaction.`, + ); + } } function checkInputsForPartialSig(inputs, action) { - inputs.forEach(input => { - let throws = false; - let pSigs = []; - if ((input.partialSig || []).length === 0) { - if (!input.finalScriptSig && !input.finalScriptWitness) - return; - pSigs = getPsigsFromInputFinalScripts(input); - } - else { - pSigs = input.partialSig; - } - pSigs.forEach(pSig => { - const { hashType } = bscript.signature.decode(pSig.signature); - const whitelist = []; - const isAnyoneCanPay = hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY; - if (isAnyoneCanPay) - whitelist.push('addInput'); - const hashMod = hashType & 0x1f; - switch (hashMod) { - case transaction_1.Transaction.SIGHASH_ALL: - break; - case transaction_1.Transaction.SIGHASH_SINGLE: - case transaction_1.Transaction.SIGHASH_NONE: - whitelist.push('addOutput'); - whitelist.push('setInputSequence'); - break; - } - if (whitelist.indexOf(action) === -1) { - throws = true; - } - }); - if (throws) { - throw new Error('Can not modify transaction, signatures exist.'); - } + inputs.forEach(input => { + let throws = false; + let pSigs = []; + if ((input.partialSig || []).length === 0) { + if (!input.finalScriptSig && !input.finalScriptWitness) return; + pSigs = getPsigsFromInputFinalScripts(input); + } else { + pSigs = input.partialSig; + } + pSigs.forEach(pSig => { + const { hashType } = bscript.signature.decode(pSig.signature); + const whitelist = []; + const isAnyoneCanPay = + hashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY; + if (isAnyoneCanPay) whitelist.push('addInput'); + const hashMod = hashType & 0x1f; + switch (hashMod) { + case transaction_1.Transaction.SIGHASH_ALL: + break; + case transaction_1.Transaction.SIGHASH_SINGLE: + case transaction_1.Transaction.SIGHASH_NONE: + whitelist.push('addOutput'); + whitelist.push('setInputSequence'); + break; + } + if (whitelist.indexOf(action) === -1) { + throws = true; + } }); + if (throws) { + throw new Error('Can not modify transaction, signatures exist.'); + } + }); } function checkPartialSigSighashes(input) { - if (!input.sighashType || !input.partialSig) - return; - const { partialSig, sighashType } = input; - partialSig.forEach(pSig => { - const { hashType } = bscript.signature.decode(pSig.signature); - if (sighashType !== hashType) { - throw new Error('Signature sighash does not match input sighash type'); - } - }); + if (!input.sighashType || !input.partialSig) return; + const { partialSig, sighashType } = input; + partialSig.forEach(pSig => { + const { hashType } = bscript.signature.decode(pSig.signature); + if (sighashType !== hashType) { + throw new Error('Signature sighash does not match input sighash type'); + } + }); } function checkScriptForPubkey(pubkey, script, action) { - const pubkeyHash = crypto_1.hash160(pubkey); - const decompiled = bscript.decompile(script); - if (decompiled === null) - throw new Error('Unknown script error'); - const hasKey = decompiled.some(element => { - if (typeof element === 'number') - return false; - return element.equals(pubkey) || element.equals(pubkeyHash); - }); - if (!hasKey) { - throw new Error(`Can not ${action} for this input with the key ${pubkey.toString('hex')}`); - } + const pubkeyHash = crypto_1.hash160(pubkey); + const decompiled = bscript.decompile(script); + if (decompiled === null) throw new Error('Unknown script error'); + const hasKey = decompiled.some(element => { + if (typeof element === 'number') return false; + return element.equals(pubkey) || element.equals(pubkeyHash); + }); + if (!hasKey) { + throw new Error( + `Can not ${action} for this input with the key ${pubkey.toString('hex')}`, + ); + } } function checkTxEmpty(tx) { - const isEmpty = tx.ins.every(input => input.script && input.script.length === 0); - if (!isEmpty) { - throw new Error('Format Error: Transaction ScriptSigs are not empty'); - } - // if (tx.flag === 1 && tx.witnessIn.length > 0) { - // throw new Error('Format Error: Transaction WitnessScriptSigs are not empty'); - // } + const isEmpty = tx.ins.every( + input => input.script && input.script.length === 0, + ); + if (!isEmpty) { + throw new Error('Format Error: Transaction ScriptSigs are not empty'); + } + // if (tx.flag === 1 && tx.witnessIn.length > 0) { + // throw new Error('Format Error: Transaction WitnessScriptSigs are not empty'); + // } } function checkTxForDupeIns(tx, cache) { - tx.ins.forEach(input => { - checkTxInputCache(cache, input); - }); + tx.ins.forEach(input => { + checkTxInputCache(cache, input); + }); } function checkTxInputCache(cache, input) { - const key = bufferutils_1.reverseBuffer(Buffer.from(input.hash)).toString('hex') + ':' + input.index; - if (cache.__TX_IN_CACHE[key]) - throw new Error('Duplicate input detected.'); - cache.__TX_IN_CACHE[key] = 1; + const key = + bufferutils_1.reverseBuffer(Buffer.from(input.hash)).toString('hex') + + ':' + + input.index; + if (cache.__TX_IN_CACHE[key]) throw new Error('Duplicate input detected.'); + cache.__TX_IN_CACHE[key] = 1; } function scriptCheckerFactory(payment, paymentScriptName) { - return (inputIndex, scriptPubKey, redeemScript) => { - const redeemScriptOutput = payment({ - redeem: { output: redeemScript }, - }).output; - if (!scriptPubKey.equals(redeemScriptOutput)) { - throw new Error(`${paymentScriptName} for input #${inputIndex} doesn't match the scriptPubKey in the prevout`); - } - }; + return (inputIndex, scriptPubKey, redeemScript) => { + const redeemScriptOutput = payment({ + redeem: { output: redeemScript }, + }).output; + if (!scriptPubKey.equals(redeemScriptOutput)) { + throw new Error( + `${paymentScriptName} for input #${inputIndex} doesn't match the scriptPubKey in the prevout`, + ); + } + }; } const checkRedeemScript = scriptCheckerFactory(payments.p2sh, 'Redeem script'); -const checkWitnessScript = scriptCheckerFactory(payments.p2wsh, 'Witness script'); +const checkWitnessScript = scriptCheckerFactory( + payments.p2wsh, + 'Witness script', +); function getTxCacheValue(key, name, inputs, c) { - if (!inputs.every(isFinalized)) - throw new Error(`PSBT must be finalized to calculate ${name}`); - if (key === '__FEE_RATE' && c.__FEE_RATE) - return c.__FEE_RATE; - if (key === '__FEE' && c.__FEE) - return c.__FEE; - let tx; - let mustFinalize = true; - if (c.__EXTRACTED_TX) { - tx = c.__EXTRACTED_TX; - mustFinalize = false; - } - else { - tx = c.__TX.clone(); - } - inputFinalizeGetAmts(inputs, tx, c, mustFinalize); - if (key === '__FEE_RATE') - return c.__FEE_RATE; - else if (key === '__FEE') - return c.__FEE; + if (!inputs.every(isFinalized)) + throw new Error(`PSBT must be finalized to calculate ${name}`); + if (key === '__FEE_RATE' && c.__FEE_RATE) return c.__FEE_RATE; + if (key === '__FEE' && c.__FEE) return c.__FEE; + let tx; + let mustFinalize = true; + if (c.__EXTRACTED_TX) { + tx = c.__EXTRACTED_TX; + mustFinalize = false; + } else { + tx = c.__TX.clone(); + } + inputFinalizeGetAmts(inputs, tx, c, mustFinalize); + if (key === '__FEE_RATE') return c.__FEE_RATE; + else if (key === '__FEE') return c.__FEE; } -function getFinalScripts(script, scriptType, partialSig, isSegwit, isP2SH, isP2WSH) { - let finalScriptSig; - let finalScriptWitness; - // Wow, the payments API is very handy - const payment = getPayment(script, scriptType, partialSig); - const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment }); - const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment }); - if (isSegwit) { - if (p2wsh) { - finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness); - } - else { - finalScriptWitness = witnessStackToScriptWitness(payment.witness); - } - if (p2sh) { - finalScriptSig = p2sh.input; - } - } - else { - if (p2sh) { - finalScriptSig = p2sh.input; - } - else { - finalScriptSig = payment.input; - } - } - return { - finalScriptSig, - finalScriptWitness, - }; +function getFinalScripts( + script, + scriptType, + partialSig, + isSegwit, + isP2SH, + isP2WSH, +) { + let finalScriptSig; + let finalScriptWitness; + // Wow, the payments API is very handy + const payment = getPayment(script, scriptType, partialSig); + const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment }); + const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment }); + if (isSegwit) { + if (p2wsh) { + finalScriptWitness = witnessStackToScriptWitness(p2wsh.witness); + } else { + finalScriptWitness = witnessStackToScriptWitness(payment.witness); + } + if (p2sh) { + finalScriptSig = p2sh.input; + } + } else { + if (p2sh) { + finalScriptSig = p2sh.input; + } else { + finalScriptSig = payment.input; + } + } + return { + finalScriptSig, + finalScriptWitness, + }; } -function getHashAndSighashType(inputs, inputIndex, pubkey, cache, sighashTypes) { - const input = utils_1.checkForInput(inputs, inputIndex); - const { hash, sighashType, script } = getHashForSig(inputIndex, input, cache, sighashTypes); - checkScriptForPubkey(pubkey, script, 'sign'); - return { - hash, - sighashType, - }; +function getHashAndSighashType( + inputs, + inputIndex, + pubkey, + cache, + sighashTypes, +) { + const input = utils_1.checkForInput(inputs, inputIndex); + const { hash, sighashType, script } = getHashForSig( + inputIndex, + input, + cache, + sighashTypes, + ); + checkScriptForPubkey(pubkey, script, 'sign'); + return { + hash, + sighashType, + }; } function getHashForSig(inputIndex, input, cache, sighashTypes) { - const unsignedTx = cache.__TX; - const sighashType = input.sighashType || transaction_1.Transaction.SIGHASH_ALL; - if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { - const str = sighashTypeToString(sighashType); - throw new Error(`Sighash type is not allowed. Retry the sign method passing the ` + - `sighashTypes array of whitelisted types. Sighash type: ${str}`); - } - let hash; - let script; - if (input.nonWitnessUtxo) { - const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex); - const prevoutHash = unsignedTx.ins[inputIndex].hash; - const utxoHash = nonWitnessUtxoTx.getHash(); - // If a non-witness UTXO is provided, its hash must match the hash specified in the prevout - if (!prevoutHash.equals(utxoHash)) { - throw new Error(`Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`); - } - const prevoutIndex = unsignedTx.ins[inputIndex].index; - const prevout = nonWitnessUtxoTx.outs[prevoutIndex]; - if (input.redeemScript) { - // If a redeemScript is provided, the scriptPubKey must be for that redeemScript - checkRedeemScript(inputIndex, prevout.script, input.redeemScript); - script = input.redeemScript; - } - else { - script = prevout.script; - } - if (isP2WSHScript(script)) { - if (!input.witnessScript) - throw new Error('Segwit input needs witnessScript if not P2WPKH'); - checkWitnessScript(inputIndex, script, input.witnessScript); - hash = unsignedTx.hashForWitnessV0(inputIndex, input.witnessScript, prevout.value, sighashType); - script = input.witnessScript; - } - else if (isP2WPKH(script)) { - // P2WPKH uses the P2PKH template for prevoutScript when signing - const signingScript = payments.p2pkh({ hash: script.slice(2) }).output; - hash = unsignedTx.hashForWitnessV0(inputIndex, signingScript, prevout.value, sighashType); - } - else { - hash = unsignedTx.hashForSignature(inputIndex, script, sighashType); - } - } - else if (input.witnessUtxo) { - let _script; // so we don't shadow the `let script` above - if (input.redeemScript) { - // If a redeemScript is provided, the scriptPubKey must be for that redeemScript - checkRedeemScript(inputIndex, input.witnessUtxo.script, input.redeemScript); - _script = input.redeemScript; - } - else { - _script = input.witnessUtxo.script; - } - if (isP2WPKH(_script)) { - // P2WPKH uses the P2PKH template for prevoutScript when signing - const signingScript = payments.p2pkh({ hash: _script.slice(2) }).output; - hash = unsignedTx.hashForWitnessV0(inputIndex, signingScript, input.witnessUtxo.value, sighashType); - script = _script; - } - else if (isP2WSHScript(_script)) { - if (!input.witnessScript) - throw new Error('Segwit input needs witnessScript if not P2WPKH'); - checkWitnessScript(inputIndex, _script, input.witnessScript); - hash = unsignedTx.hashForWitnessV0(inputIndex, input.witnessScript, input.witnessUtxo.value, sighashType); - // want to make sure the script we return is the actual meaningful script - script = input.witnessScript; - } - else { - throw new Error(`Input #${inputIndex} has witnessUtxo but non-segwit script: ` + - `${_script.toString('hex')}`); - } - } - else { - throw new Error('Need a Utxo input item for signing'); - } - return { - script, + const unsignedTx = cache.__TX; + const sighashType = + input.sighashType || transaction_1.Transaction.SIGHASH_ALL; + if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) { + const str = sighashTypeToString(sighashType); + throw new Error( + `Sighash type is not allowed. Retry the sign method passing the ` + + `sighashTypes array of whitelisted types. Sighash type: ${str}`, + ); + } + let hash; + let script; + if (input.nonWitnessUtxo) { + const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( + cache, + input, + inputIndex, + ); + const prevoutHash = unsignedTx.ins[inputIndex].hash; + const utxoHash = nonWitnessUtxoTx.getHash(); + // If a non-witness UTXO is provided, its hash must match the hash specified in the prevout + if (!prevoutHash.equals(utxoHash)) { + throw new Error( + `Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`, + ); + } + const prevoutIndex = unsignedTx.ins[inputIndex].index; + const prevout = nonWitnessUtxoTx.outs[prevoutIndex]; + if (input.redeemScript) { + // If a redeemScript is provided, the scriptPubKey must be for that redeemScript + checkRedeemScript(inputIndex, prevout.script, input.redeemScript); + script = input.redeemScript; + } else { + script = prevout.script; + } + if (isP2WSHScript(script)) { + if (!input.witnessScript) + throw new Error('Segwit input needs witnessScript if not P2WPKH'); + checkWitnessScript(inputIndex, script, input.witnessScript); + hash = unsignedTx.hashForWitnessV0( + inputIndex, + input.witnessScript, + prevout.value, sighashType, - hash, - }; + ); + script = input.witnessScript; + } else if (isP2WPKH(script)) { + // P2WPKH uses the P2PKH template for prevoutScript when signing + const signingScript = payments.p2pkh({ hash: script.slice(2) }).output; + hash = unsignedTx.hashForWitnessV0( + inputIndex, + signingScript, + prevout.value, + sighashType, + ); + } else { + hash = unsignedTx.hashForSignature(inputIndex, script, sighashType); + } + } else if (input.witnessUtxo) { + let _script; // so we don't shadow the `let script` above + if (input.redeemScript) { + // If a redeemScript is provided, the scriptPubKey must be for that redeemScript + checkRedeemScript( + inputIndex, + input.witnessUtxo.script, + input.redeemScript, + ); + _script = input.redeemScript; + } else { + _script = input.witnessUtxo.script; + } + if (isP2WPKH(_script)) { + // P2WPKH uses the P2PKH template for prevoutScript when signing + const signingScript = payments.p2pkh({ hash: _script.slice(2) }).output; + hash = unsignedTx.hashForWitnessV0( + inputIndex, + signingScript, + input.witnessUtxo.value, + sighashType, + ); + script = _script; + } else if (isP2WSHScript(_script)) { + if (!input.witnessScript) + throw new Error('Segwit input needs witnessScript if not P2WPKH'); + checkWitnessScript(inputIndex, _script, input.witnessScript); + hash = unsignedTx.hashForWitnessV0( + inputIndex, + input.witnessScript, + input.witnessUtxo.value, + sighashType, + ); + // want to make sure the script we return is the actual meaningful script + script = input.witnessScript; + } else { + throw new Error( + `Input #${inputIndex} has witnessUtxo but non-segwit script: ` + + `${_script.toString('hex')}`, + ); + } + } else { + throw new Error('Need a Utxo input item for signing'); + } + return { + script, + sighashType, + hash, + }; } function getPayment(script, scriptType, partialSig) { - let payment; - switch (scriptType) { - case 'multisig': - const sigs = getSortedSigs(script, partialSig); - payment = payments.p2ms({ - output: script, - signatures: sigs, - }); - break; - case 'pubkey': - payment = payments.p2pk({ - output: script, - signature: partialSig[0].signature, - }); - break; - case 'pubkeyhash': - payment = payments.p2pkh({ - output: script, - pubkey: partialSig[0].pubkey, - signature: partialSig[0].signature, - }); - break; - case 'witnesspubkeyhash': - payment = payments.p2wpkh({ - output: script, - pubkey: partialSig[0].pubkey, - signature: partialSig[0].signature, - }); - break; - } - return payment; + let payment; + switch (scriptType) { + case 'multisig': + const sigs = getSortedSigs(script, partialSig); + payment = payments.p2ms({ + output: script, + signatures: sigs, + }); + break; + case 'pubkey': + payment = payments.p2pk({ + output: script, + signature: partialSig[0].signature, + }); + break; + case 'pubkeyhash': + payment = payments.p2pkh({ + output: script, + pubkey: partialSig[0].pubkey, + signature: partialSig[0].signature, + }); + break; + case 'witnesspubkeyhash': + payment = payments.p2wpkh({ + output: script, + pubkey: partialSig[0].pubkey, + signature: partialSig[0].signature, + }); + break; + } + return payment; } function getPsigsFromInputFinalScripts(input) { - const scriptItems = !input.finalScriptSig - ? [] - : bscript.decompile(input.finalScriptSig) || []; - const witnessItems = !input.finalScriptWitness - ? [] - : bscript.decompile(input.finalScriptWitness) || []; - return scriptItems - .concat(witnessItems) - .filter(item => { - return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); + const scriptItems = !input.finalScriptSig + ? [] + : bscript.decompile(input.finalScriptSig) || []; + const witnessItems = !input.finalScriptWitness + ? [] + : bscript.decompile(input.finalScriptWitness) || []; + return scriptItems + .concat(witnessItems) + .filter(item => { + return Buffer.isBuffer(item) && bscript.isCanonicalScriptSignature(item); }) - .map(sig => ({ signature: sig })); + .map(sig => ({ signature: sig })); } function getScriptFromInput(inputIndex, input, cache) { - const unsignedTx = cache.__TX; - const res = { - script: null, - isSegwit: false, - isP2SH: false, - isP2WSH: false, - }; - res.isP2SH = !!input.redeemScript; - res.isP2WSH = !!input.witnessScript; - if (input.witnessScript) { - res.script = input.witnessScript; - } - else if (input.redeemScript) { - res.script = input.redeemScript; - } - else { - if (input.nonWitnessUtxo) { - const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(cache, input, inputIndex); - const prevoutIndex = unsignedTx.ins[inputIndex].index; - res.script = nonWitnessUtxoTx.outs[prevoutIndex].script; - } - else if (input.witnessUtxo) { - res.script = input.witnessUtxo.script; - } - } - if (input.witnessScript || isP2WPKH(res.script)) { - res.isSegwit = true; - } - return res; + const unsignedTx = cache.__TX; + const res = { + script: null, + isSegwit: false, + isP2SH: false, + isP2WSH: false, + }; + res.isP2SH = !!input.redeemScript; + res.isP2WSH = !!input.witnessScript; + if (input.witnessScript) { + res.script = input.witnessScript; + } else if (input.redeemScript) { + res.script = input.redeemScript; + } else { + if (input.nonWitnessUtxo) { + const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache( + cache, + input, + inputIndex, + ); + const prevoutIndex = unsignedTx.ins[inputIndex].index; + res.script = nonWitnessUtxoTx.outs[prevoutIndex].script; + } else if (input.witnessUtxo) { + res.script = input.witnessUtxo.script; + } + } + if (input.witnessScript || isP2WPKH(res.script)) { + res.isSegwit = true; + } + return res; } function getSignersFromHD(inputIndex, inputs, hdKeyPair) { - const input = utils_1.checkForInput(inputs, inputIndex); - if (!input.bip32Derivation || input.bip32Derivation.length === 0) { - throw new Error('Need bip32Derivation to sign with HD'); - } - const myDerivations = input.bip32Derivation - .map(bipDv => { - if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) { - return bipDv; - } - else { - return; - } + const input = utils_1.checkForInput(inputs, inputIndex); + if (!input.bip32Derivation || input.bip32Derivation.length === 0) { + throw new Error('Need bip32Derivation to sign with HD'); + } + const myDerivations = input.bip32Derivation + .map(bipDv => { + if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) { + return bipDv; + } else { + return; + } }) - .filter(v => !!v); - if (myDerivations.length === 0) { - throw new Error('Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint'); - } - const signers = myDerivations.map(bipDv => { - const node = hdKeyPair.derivePath(bipDv.path); - if (!bipDv.pubkey.equals(node.publicKey)) { - throw new Error('pubkey did not match bip32Derivation'); - } - return node; - }); - return signers; + .filter(v => !!v); + if (myDerivations.length === 0) { + throw new Error( + 'Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint', + ); + } + const signers = myDerivations.map(bipDv => { + const node = hdKeyPair.derivePath(bipDv.path); + if (!bipDv.pubkey.equals(node.publicKey)) { + throw new Error('pubkey did not match bip32Derivation'); + } + return node; + }); + return signers; } function getSortedSigs(script, partialSig) { - const p2ms = payments.p2ms({ output: script }); - // for each pubkey in order of p2ms script - return p2ms - .pubkeys.map(pk => { - // filter partialSig array by pubkey being equal - return (partialSig.filter(ps => { - return ps.pubkey.equals(pk); - })[0] || {}).signature; - // Any pubkey without a match will return undefined - // this last filter removes all the undefined items in the array. + const p2ms = payments.p2ms({ output: script }); + // for each pubkey in order of p2ms script + return p2ms.pubkeys + .map(pk => { + // filter partialSig array by pubkey being equal + return ( + partialSig.filter(ps => { + return ps.pubkey.equals(pk); + })[0] || {} + ).signature; + // Any pubkey without a match will return undefined + // this last filter removes all the undefined items in the array. }) - .filter(v => !!v); + .filter(v => !!v); } function scriptWitnessToWitnessStack(buffer) { - let offset = 0; - function readSlice(n) { - offset += n; - return buffer.slice(offset - n, offset); - } - function readVarInt() { - const vi = varuint.decode(buffer, offset); - offset += varuint.decode.bytes; - return vi; - } - function readVarSlice() { - return readSlice(readVarInt()); - } - function readVector() { - const count = readVarInt(); - const vector = []; - for (let i = 0; i < count; i++) - vector.push(readVarSlice()); - return vector; - } - return readVector(); + let offset = 0; + function readSlice(n) { + offset += n; + return buffer.slice(offset - n, offset); + } + function readVarInt() { + const vi = varuint.decode(buffer, offset); + offset += varuint.decode.bytes; + return vi; + } + function readVarSlice() { + return readSlice(readVarInt()); + } + function readVector() { + const count = readVarInt(); + const vector = []; + for (let i = 0; i < count; i++) vector.push(readVarSlice()); + return vector; + } + return readVector(); } function sighashTypeToString(sighashType) { - let text = sighashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY - ? 'SIGHASH_ANYONECANPAY | ' - : ''; - const sigMod = sighashType & 0x1f; - switch (sigMod) { - case transaction_1.Transaction.SIGHASH_ALL: - text += 'SIGHASH_ALL'; - break; - case transaction_1.Transaction.SIGHASH_SINGLE: - text += 'SIGHASH_SINGLE'; - break; - case transaction_1.Transaction.SIGHASH_NONE: - text += 'SIGHASH_NONE'; - break; - } - return text; + let text = + sighashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY + ? 'SIGHASH_ANYONECANPAY | ' + : ''; + const sigMod = sighashType & 0x1f; + switch (sigMod) { + case transaction_1.Transaction.SIGHASH_ALL: + text += 'SIGHASH_ALL'; + break; + case transaction_1.Transaction.SIGHASH_SINGLE: + text += 'SIGHASH_SINGLE'; + break; + case transaction_1.Transaction.SIGHASH_NONE: + text += 'SIGHASH_NONE'; + break; + } + return text; } function witnessStackToScriptWitness(witness) { - let buffer = Buffer.allocUnsafe(0); - function writeSlice(slice) { - buffer = Buffer.concat([buffer, Buffer.from(slice)]); - } - function writeVarInt(i) { - const currentLen = buffer.length; - const varintLen = varuint.encodingLength(i); - buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); - varuint.encode(i, buffer, currentLen); - } - function writeVarSlice(slice) { - writeVarInt(slice.length); - writeSlice(slice); - } - function writeVector(vector) { - writeVarInt(vector.length); - vector.forEach(writeVarSlice); - } - writeVector(witness); - return buffer; + let buffer = Buffer.allocUnsafe(0); + function writeSlice(slice) { + buffer = Buffer.concat([buffer, Buffer.from(slice)]); + } + function writeVarInt(i) { + const currentLen = buffer.length; + const varintLen = varuint.encodingLength(i); + buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); + varuint.encode(i, buffer, currentLen); + } + function writeVarSlice(slice) { + writeVarInt(slice.length); + writeSlice(slice); + } + function writeVector(vector) { + writeVarInt(vector.length); + vector.forEach(writeVarSlice); + } + writeVector(witness); + return buffer; } function addNonWitnessTxCache(cache, input, inputIndex) { - cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo; - const tx = transaction_1.Transaction.fromBuffer(input.nonWitnessUtxo); - cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = tx; - const self = cache; - const selfIndex = inputIndex; - delete input.nonWitnessUtxo; - Object.defineProperty(input, 'nonWitnessUtxo', { - enumerable: true, - get() { - const buf = self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex]; - const txCache = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex]; - if (buf !== undefined) { - return buf; - } - else { - const newBuf = txCache.toBuffer(); - self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = newBuf; - return newBuf; - } - }, - set(data) { - self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data; - }, - }); + cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo; + const tx = transaction_1.Transaction.fromBuffer(input.nonWitnessUtxo); + cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = tx; + const self = cache; + const selfIndex = inputIndex; + delete input.nonWitnessUtxo; + Object.defineProperty(input, 'nonWitnessUtxo', { + enumerable: true, + get() { + const buf = self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex]; + const txCache = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex]; + if (buf !== undefined) { + return buf; + } else { + const newBuf = txCache.toBuffer(); + self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = newBuf; + return newBuf; + } + }, + set(data) { + self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data; + }, + }); } function inputFinalizeGetAmts(inputs, tx, cache, mustFinalize) { - inputs.forEach((input, idx) => { - if (mustFinalize && input.finalScriptSig) - tx.ins[idx].script = input.finalScriptSig; - if (mustFinalize && input.finalScriptWitness) { - tx.ins[idx].witness = scriptWitnessToWitnessStack(input.finalScriptWitness); - } - }); - if (tx.ins.some(x => x.witness.length !== 0)) { - tx.flag = 1; - } - const bytes = tx.virtualSize(); - const fee = 2 * bytes; - cache.__FEE = fee; - cache.__EXTRACTED_TX = tx; - cache.__FEE_RATE = Math.floor(fee / bytes); + inputs.forEach((input, idx) => { + if (mustFinalize && input.finalScriptSig) + tx.ins[idx].script = input.finalScriptSig; + if (mustFinalize && input.finalScriptWitness) { + tx.ins[idx].witness = scriptWitnessToWitnessStack( + input.finalScriptWitness, + ); + } + }); + if (tx.ins.some(x => x.witness.length !== 0)) { + tx.flag = 1; + } + const bytes = tx.virtualSize(); + const fee = 2 * bytes; + cache.__FEE = fee; + cache.__EXTRACTED_TX = tx; + cache.__FEE_RATE = Math.floor(fee / bytes); } function nonWitnessUtxoTxFromCache(cache, input, inputIndex) { - const c = cache.__NON_WITNESS_UTXO_TX_CACHE; - if (!c[inputIndex]) { - addNonWitnessTxCache(cache, input, inputIndex); - } - return c[inputIndex]; + const c = cache.__NON_WITNESS_UTXO_TX_CACHE; + if (!c[inputIndex]) { + addNonWitnessTxCache(cache, input, inputIndex); + } + return c[inputIndex]; } function classifyScript(script) { - if (isP2WPKH(script)) - return 'witnesspubkeyhash'; - if (isP2PKH(script)) - return 'pubkeyhash'; - if (isP2MS(script)) - return 'multisig'; - if (isP2PK(script)) - return 'pubkey'; - return 'nonstandard'; + if (isP2WPKH(script)) return 'witnesspubkeyhash'; + if (isP2PKH(script)) return 'pubkeyhash'; + if (isP2MS(script)) return 'multisig'; + if (isP2PK(script)) return 'pubkey'; + return 'nonstandard'; } function range(n) { - return [...Array(n).keys()]; + return [...Array(n).keys()]; } function randomBytes(options) { - if (options === undefined) - options = {}; - const rng = options.rng || _randomBytes; - return rng(32); + if (options === undefined) options = {}; + const rng = options.rng || _randomBytes; + return rng(32); } /** * Compute outputs blinders @@ -1384,43 +1682,60 @@ function randomBytes(options) { * @returns an array of BlindingData[] corresponding of blinders to blind outputs specified in outputsData */ function computeOutputsBlindingData(inputsBlindingData, outputsData) { - return __awaiter(this, void 0, void 0, function* () { - const outputsBlindingData = []; - outputsData.slice(0, outputsData.length - 1).forEach(([satoshis, asset]) => { - const blindingData = { - value: satoshis, - asset, - valueBlindingFactor: randomBytes(), - assetBlindingFactor: randomBytes(), - }; - outputsBlindingData.push(blindingData); - }); - const [lastOutputValue, lastOutputAsset] = outputsData[outputsData.length - 1]; - const finalBlindingData = { - value: lastOutputValue, - asset: lastOutputAsset, - assetBlindingFactor: randomBytes(), - valueBlindingFactor: Buffer.from([]), + return __awaiter(this, void 0, void 0, function*() { + const outputsBlindingData = []; + outputsData + .slice(0, outputsData.length - 1) + .forEach(([satoshis, asset]) => { + const blindingData = { + value: satoshis, + asset, + valueBlindingFactor: randomBytes(), + assetBlindingFactor: randomBytes(), }; - // values - const inputsValues = inputsBlindingData.map(({ value }) => value); - const outputsValues = outputsData - .map(([amount]) => amount) - .concat(lastOutputValue); - // asset blinders - const inputsAssetBlinders = inputsBlindingData.map(({ assetBlindingFactor }) => assetBlindingFactor); - const outputsAssetBlinders = outputsBlindingData - .map(({ assetBlindingFactor }) => assetBlindingFactor) - .concat(finalBlindingData.assetBlindingFactor); - // value blinders - const inputsAmountBlinders = inputsBlindingData.map(({ valueBlindingFactor }) => valueBlindingFactor); - const outputsAmountBlinders = outputsBlindingData.map(({ valueBlindingFactor }) => valueBlindingFactor); - // compute output final amount blinder - const finalAmountBlinder = yield confidential.valueBlindingFactor(inputsValues, outputsValues, inputsAssetBlinders, outputsAssetBlinders, inputsAmountBlinders, outputsAmountBlinders); - finalBlindingData.valueBlindingFactor = finalAmountBlinder; - outputsBlindingData.push(finalBlindingData); - return outputsBlindingData; - }); + outputsBlindingData.push(blindingData); + }); + const [lastOutputValue, lastOutputAsset] = outputsData[ + outputsData.length - 1 + ]; + const finalBlindingData = { + value: lastOutputValue, + asset: lastOutputAsset, + assetBlindingFactor: randomBytes(), + valueBlindingFactor: Buffer.from([]), + }; + // values + const inputsValues = inputsBlindingData.map(({ value }) => value); + const outputsValues = outputsData + .map(([amount]) => amount) + .concat(lastOutputValue); + // asset blinders + const inputsAssetBlinders = inputsBlindingData.map( + ({ assetBlindingFactor }) => assetBlindingFactor, + ); + const outputsAssetBlinders = outputsBlindingData + .map(({ assetBlindingFactor }) => assetBlindingFactor) + .concat(finalBlindingData.assetBlindingFactor); + // value blinders + const inputsAmountBlinders = inputsBlindingData.map( + ({ valueBlindingFactor }) => valueBlindingFactor, + ); + const outputsAmountBlinders = outputsBlindingData.map( + ({ valueBlindingFactor }) => valueBlindingFactor, + ); + // compute output final amount blinder + const finalAmountBlinder = yield confidential.valueBlindingFactor( + inputsValues, + outputsValues, + inputsAssetBlinders, + outputsAssetBlinders, + inputsAmountBlinders, + outputsAmountBlinders, + ); + finalBlindingData.valueBlindingFactor = finalAmountBlinder; + outputsBlindingData.push(finalBlindingData); + return outputsBlindingData; + }); } exports.computeOutputsBlindingData = computeOutputsBlindingData; /** @@ -1429,27 +1744,25 @@ exports.computeOutputsBlindingData = computeOutputsBlindingData; * @param witnessUtxo the prevout of the input I */ function toBlindingData(blindDataLike, witnessUtxo) { - return __awaiter(this, void 0, void 0, function* () { - if (!blindDataLike) { - if (!witnessUtxo) - throw new Error('need witnessUtxo'); - return getUnconfidentialWitnessUtxoBlindingData(witnessUtxo); - } - if (Buffer.isBuffer(blindDataLike)) { - if (!witnessUtxo) - throw new Error('need witnessUtxo'); - return confidential.unblindOutputWithKey(witnessUtxo, blindDataLike); - } - return blindDataLike; - }); + return __awaiter(this, void 0, void 0, function*() { + if (!blindDataLike) { + if (!witnessUtxo) throw new Error('need witnessUtxo'); + return getUnconfidentialWitnessUtxoBlindingData(witnessUtxo); + } + if (Buffer.isBuffer(blindDataLike)) { + if (!witnessUtxo) throw new Error('need witnessUtxo'); + return confidential.unblindOutputWithKey(witnessUtxo, blindDataLike); + } + return blindDataLike; + }); } exports.toBlindingData = toBlindingData; function getUnconfidentialWitnessUtxoBlindingData(prevout) { - const unblindedInputBlindingData = { - value: confidential.confidentialValueToSatoshi(prevout.value).toString(10), - valueBlindingFactor: transaction_1.ZERO, - asset: prevout.asset.slice(1), - assetBlindingFactor: transaction_1.ZERO, - }; - return unblindedInputBlindingData; + const unblindedInputBlindingData = { + value: confidential.confidentialValueToSatoshi(prevout.value).toString(10), + valueBlindingFactor: transaction_1.ZERO, + asset: prevout.asset.slice(1), + assetBlindingFactor: transaction_1.ZERO, + }; + return unblindedInputBlindingData; } diff --git a/src/script.js b/src/script.js index 957489d9b..d727fe082 100644 --- a/src/script.js +++ b/src/script.js @@ -1,15 +1,19 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const scriptNumber = __importStar(require("./script_number")); -const scriptSignature = __importStar(require("./script_signature")); -const types = __importStar(require("./types")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const scriptNumber = __importStar(require('./script_number')); +const scriptSignature = __importStar(require('./script_signature')); +const types = __importStar(require('./types')); const bip66 = require('bip66'); const ecc = require('tiny-secp256k1'); const pushdata = require('pushdata-bitcoin'); @@ -18,179 +22,165 @@ exports.OPS = require('bitcoin-ops'); const REVERSE_OPS = require('bitcoin-ops/map'); const OP_INT_BASE = exports.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)); + return ( + types.Number(value) && + (value === exports.OPS.OP_0 || + (value >= exports.OPS.OP_1 && value <= exports.OPS.OP_16) || + value === exports.OPS.OP_1NEGATE) + ); } function isPushOnlyChunk(value) { - return types.Buffer(value) || isOPInt(value); + return types.Buffer(value) || isOPInt(value); } function isPushOnly(value) { - return types.Array(value) && value.every(isPushOnlyChunk); + return types.Array(value) && value.every(isPushOnlyChunk); } exports.isPushOnly = isPushOnly; function asMinimalOP(buffer) { - if (buffer.length === 0) - return exports.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.length === 0) return exports.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; } function chunksIsBuffer(buf) { - return Buffer.isBuffer(buf); + return Buffer.isBuffer(buf); } function chunksIsArray(buf) { - return types.Array(buf); + return types.Array(buf); } function singleChunkIsBuffer(buf) { - return Buffer.isBuffer(buf); + return Buffer.isBuffer(buf); } function compile(chunks) { - // TODO: remove me - if (chunksIsBuffer(chunks)) - return chunks; - typeforce(types.Array, chunks); - const bufferSize = chunks.reduce((accum, chunk) => { - // data chunk - if (singleChunkIsBuffer(chunk)) { - // adhere to BIP62.3, minimal push policy - if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) { - return accum + 1; - } - return accum + pushdata.encodingLength(chunk.length) + chunk.length; - } - // opcode + // TODO: remove me + if (chunksIsBuffer(chunks)) return chunks; + typeforce(types.Array, chunks); + const bufferSize = chunks.reduce((accum, chunk) => { + // data chunk + if (singleChunkIsBuffer(chunk)) { + // adhere to BIP62.3, minimal push policy + if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) { return accum + 1; - }, 0.0); - const buffer = Buffer.allocUnsafe(bufferSize); - let offset = 0; - chunks.forEach(chunk => { - // data chunk - if (singleChunkIsBuffer(chunk)) { - // adhere to BIP62.3, minimal push policy - const opcode = asMinimalOP(chunk); - if (opcode !== undefined) { - buffer.writeUInt8(opcode, offset); - offset += 1; - return; - } - offset += pushdata.encode(buffer, chunk.length, offset); - chunk.copy(buffer, offset); - offset += chunk.length; - // opcode - } - else { - buffer.writeUInt8(chunk, offset); - offset += 1; - } - }); - if (offset !== buffer.length) - throw new Error('Could not decode chunks'); - return buffer; + } + return accum + pushdata.encodingLength(chunk.length) + chunk.length; + } + // opcode + return accum + 1; + }, 0.0); + const buffer = Buffer.allocUnsafe(bufferSize); + let offset = 0; + chunks.forEach(chunk => { + // data chunk + if (singleChunkIsBuffer(chunk)) { + // adhere to BIP62.3, minimal push policy + const opcode = asMinimalOP(chunk); + if (opcode !== undefined) { + buffer.writeUInt8(opcode, offset); + offset += 1; + return; + } + offset += pushdata.encode(buffer, chunk.length, offset); + chunk.copy(buffer, offset); + offset += chunk.length; + // opcode + } else { + buffer.writeUInt8(chunk, offset); + offset += 1; + } + }); + if (offset !== buffer.length) throw new Error('Could not decode chunks'); + return buffer; } exports.compile = compile; function decompile(buffer) { - // TODO: remove me - if (chunksIsArray(buffer)) - return buffer; - typeforce(types.Buffer, buffer); - const chunks = []; - let i = 0; - while (i < buffer.length) { - const opcode = buffer[i]; - // data chunk - if (opcode > exports.OPS.OP_0 && opcode <= exports.OPS.OP_PUSHDATA4) { - const d = pushdata.decode(buffer, i); - // did reading a pushDataInt fail? - if (d === null) - return null; - i += d.size; - // attempt to read too much data? - if (i + d.number > buffer.length) - return null; - const data = buffer.slice(i, i + d.number); - i += d.number; - // decompile minimally - const op = asMinimalOP(data); - if (op !== undefined) { - chunks.push(op); - } - else { - chunks.push(data); - } - // opcode - } - else { - chunks.push(opcode); - i += 1; - } + // TODO: remove me + if (chunksIsArray(buffer)) return buffer; + typeforce(types.Buffer, buffer); + const chunks = []; + let i = 0; + while (i < buffer.length) { + const opcode = buffer[i]; + // data chunk + if (opcode > exports.OPS.OP_0 && opcode <= exports.OPS.OP_PUSHDATA4) { + const d = pushdata.decode(buffer, i); + // did reading a pushDataInt fail? + if (d === null) return null; + i += d.size; + // attempt to read too much data? + if (i + d.number > buffer.length) return null; + const data = buffer.slice(i, i + d.number); + i += d.number; + // decompile minimally + const op = asMinimalOP(data); + if (op !== undefined) { + chunks.push(op); + } else { + chunks.push(data); + } + // opcode + } else { + chunks.push(opcode); + i += 1; } - return chunks; + } + return chunks; } exports.decompile = decompile; function toASM(chunks) { - if (chunksIsBuffer(chunks)) { - chunks = decompile(chunks); - } - return chunks - .map(chunk => { - // data? - if (singleChunkIsBuffer(chunk)) { - const op = asMinimalOP(chunk); - if (op === undefined) - return chunk.toString('hex'); - chunk = op; - } - // opcode! - return REVERSE_OPS[chunk]; + if (chunksIsBuffer(chunks)) { + chunks = decompile(chunks); + } + return chunks + .map(chunk => { + // data? + if (singleChunkIsBuffer(chunk)) { + const op = asMinimalOP(chunk); + if (op === undefined) return chunk.toString('hex'); + chunk = op; + } + // opcode! + return REVERSE_OPS[chunk]; }) - .join(' '); + .join(' '); } exports.toASM = toASM; function fromASM(asm) { - typeforce(types.String, asm); - return compile(asm.split(' ').map(chunkStr => { - // opcode? - if (exports.OPS[chunkStr] !== undefined) - return exports.OPS[chunkStr]; - typeforce(types.Hex, chunkStr); - // data! - return Buffer.from(chunkStr, 'hex'); - })); + typeforce(types.String, asm); + return compile( + asm.split(' ').map(chunkStr => { + // opcode? + if (exports.OPS[chunkStr] !== undefined) return exports.OPS[chunkStr]; + typeforce(types.Hex, chunkStr); + // data! + return Buffer.from(chunkStr, 'hex'); + }), + ); } exports.fromASM = fromASM; function toStack(chunks) { - chunks = decompile(chunks); - typeforce(isPushOnly, chunks); - return chunks.map(op => { - if (singleChunkIsBuffer(op)) - return op; - if (op === exports.OPS.OP_0) - return Buffer.allocUnsafe(0); - return scriptNumber.encode(op - OP_INT_BASE); - }); + chunks = decompile(chunks); + typeforce(isPushOnly, chunks); + return chunks.map(op => { + if (singleChunkIsBuffer(op)) return op; + if (op === exports.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 ecc.isPoint(buffer); } exports.isCanonicalPubKey = isCanonicalPubKey; function isDefinedHashType(hashType) { - const hashTypeMod = hashType & ~0x80; - // return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE - return hashTypeMod > 0x00 && hashTypeMod < 0x04; + const hashTypeMod = hashType & ~0x80; + // return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE + return hashTypeMod > 0x00 && hashTypeMod < 0x04; } exports.isDefinedHashType = isDefinedHashType; function isCanonicalScriptSignature(buffer) { - if (!Buffer.isBuffer(buffer)) - return false; - if (!isDefinedHashType(buffer[buffer.length - 1])) - return false; - return bip66.check(buffer.slice(0, -1)); + if (!Buffer.isBuffer(buffer)) return false; + if (!isDefinedHashType(buffer[buffer.length - 1])) return false; + return bip66.check(buffer.slice(0, -1)); } exports.isCanonicalScriptSignature = isCanonicalScriptSignature; // tslint:disable-next-line variable-name diff --git a/src/script_number.js b/src/script_number.js index 3a30a43de..3f313af56 100644 --- a/src/script_number.js +++ b/src/script_number.js @@ -1,65 +1,61 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); function decode(buffer, maxLength, minimal) { - maxLength = maxLength || 4; - minimal = minimal === undefined ? true : minimal; - const length = buffer.length; - if (length === 0) - return 0; - if (length > maxLength) - throw new TypeError('Script number overflow'); - if (minimal) { - if ((buffer[length - 1] & 0x7f) === 0) { - if (length <= 1 || (buffer[length - 2] & 0x80) === 0) - throw new Error('Non-minimally encoded script number'); - } + maxLength = maxLength || 4; + minimal = minimal === undefined ? true : minimal; + const length = buffer.length; + if (length === 0) return 0; + if (length > maxLength) throw new TypeError('Script number overflow'); + if (minimal) { + if ((buffer[length - 1] & 0x7f) === 0) { + if (length <= 1 || (buffer[length - 2] & 0x80) === 0) + throw new Error('Non-minimally encoded script number'); } - // 40-bit - if (length === 5) { - const a = buffer.readUInt32LE(0); - const b = buffer.readUInt8(4); - if (b & 0x80) - return -((b & ~0x80) * 0x100000000 + a); - return b * 0x100000000 + a; - } - // 32-bit / 24-bit / 16-bit / 8-bit - let result = 0; - for (let i = 0; i < length; ++i) { - result |= buffer[i] << (8 * i); - } - if (buffer[length - 1] & 0x80) - return -(result & ~(0x80 << (8 * (length - 1)))); - return result; + } + // 40-bit + if (length === 5) { + const a = buffer.readUInt32LE(0); + const b = buffer.readUInt8(4); + if (b & 0x80) return -((b & ~0x80) * 0x100000000 + a); + return b * 0x100000000 + a; + } + // 32-bit / 24-bit / 16-bit / 8-bit + let result = 0; + for (let i = 0; i < length; ++i) { + result |= buffer[i] << (8 * i); + } + if (buffer[length - 1] & 0x80) + return -(result & ~(0x80 << (8 * (length - 1)))); + return result; } exports.decode = decode; function scriptNumSize(i) { - return i > 0x7fffffff - ? 5 - : i > 0x7fffff - ? 4 - : i > 0x7fff - ? 3 - : i > 0x7f - ? 2 - : i > 0x00 - ? 1 - : 0; + return i > 0x7fffffff + ? 5 + : i > 0x7fffff + ? 4 + : i > 0x7fff + ? 3 + : i > 0x7f + ? 2 + : i > 0x00 + ? 1 + : 0; } function encode(_number) { - let value = Math.abs(_number); - const size = scriptNumSize(value); - const buffer = Buffer.allocUnsafe(size); - const negative = _number < 0; - for (let i = 0; i < size; ++i) { - buffer.writeUInt8(value & 0xff, i); - value >>= 8; - } - if (buffer[size - 1] & 0x80) { - buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1); - } - else if (negative) { - buffer[size - 1] |= 0x80; - } - return buffer; + let value = Math.abs(_number); + const size = scriptNumSize(value); + const buffer = Buffer.allocUnsafe(size); + const negative = _number < 0; + for (let i = 0; i < size; ++i) { + buffer.writeUInt8(value & 0xff, i); + value >>= 8; + } + if (buffer[size - 1] & 0x80) { + buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1); + } else if (negative) { + buffer[size - 1] |= 0x80; + } + return buffer; } exports.encode = encode; diff --git a/src/script_signature.js b/src/script_signature.js index 6f36f1127..116196fc8 100644 --- a/src/script_signature.js +++ b/src/script_signature.js @@ -1,60 +1,63 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const types = __importStar(require("./types")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const types = __importStar(require('./types')); const bip66 = require('bip66'); const typeforce = require('typeforce'); const ZERO = Buffer.alloc(1, 0); function toDER(x) { - let i = 0; - while (x[i] === 0) - ++i; - if (i === x.length) - return ZERO; - x = x.slice(i); - if (x[0] & 0x80) - return Buffer.concat([ZERO, x], 1 + x.length); - return x; + let i = 0; + while (x[i] === 0) ++i; + if (i === x.length) return ZERO; + x = x.slice(i); + if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length); + return x; } function fromDER(x) { - if (x[0] === 0x00) - x = x.slice(1); - const buffer = Buffer.alloc(32, 0); - const bstart = Math.max(0, 32 - x.length); - x.copy(buffer, bstart); - return buffer; + if (x[0] === 0x00) x = x.slice(1); + const buffer = Buffer.alloc(32, 0); + const bstart = Math.max(0, 32 - x.length); + x.copy(buffer, bstart); + return buffer; } // BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed) function decode(buffer) { - const hashType = buffer.readUInt8(buffer.length - 1); - const hashTypeMod = hashType & ~0x80; - if (hashTypeMod <= 0 || hashTypeMod >= 4) - throw new Error('Invalid hashType ' + hashType); - const decoded = bip66.decode(buffer.slice(0, -1)); - const r = fromDER(decoded.r); - const s = fromDER(decoded.s); - const signature = Buffer.concat([r, s], 64); - return { signature, hashType }; + const hashType = buffer.readUInt8(buffer.length - 1); + const hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); + const decoded = bip66.decode(buffer.slice(0, -1)); + const r = fromDER(decoded.r); + const s = fromDER(decoded.s); + const signature = Buffer.concat([r, s], 64); + return { signature, hashType }; } exports.decode = decode; function encode(signature, hashType) { - typeforce({ - signature: types.BufferN(64), - hashType: types.UInt8, - }, { signature, hashType }); - const hashTypeMod = hashType & ~0x80; - if (hashTypeMod <= 0 || hashTypeMod >= 4) - throw new Error('Invalid hashType ' + hashType); - const hashTypeBuffer = Buffer.allocUnsafe(1); - hashTypeBuffer.writeUInt8(hashType, 0); - const r = toDER(signature.slice(0, 32)); - const s = toDER(signature.slice(32, 64)); - return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]); + typeforce( + { + signature: types.BufferN(64), + hashType: types.UInt8, + }, + { signature, hashType }, + ); + const hashTypeMod = hashType & ~0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new Error('Invalid hashType ' + hashType); + const hashTypeBuffer = Buffer.allocUnsafe(1); + hashTypeBuffer.writeUInt8(hashType, 0); + const r = toDER(signature.slice(0, 32)); + const s = toDER(signature.slice(32, 64)); + return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]); } exports.encode = encode; diff --git a/src/sha256d.js b/src/sha256d.js index 05feaa9ce..e0f1b9031 100644 --- a/src/sha256d.js +++ b/src/sha256d.js @@ -1,5 +1,5 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); // SHA-256 (+ HMAC and PBKDF2) for JavaScript. // // Written in 2014-2016 by Dmitry Chestnykh. @@ -20,304 +20,304 @@ exports.digestLength = 32; exports.blockSize = 64; // SHA-256 constants const K = new Uint32Array([ - 0x428a2f98, - 0x71374491, - 0xb5c0fbcf, - 0xe9b5dba5, - 0x3956c25b, - 0x59f111f1, - 0x923f82a4, - 0xab1c5ed5, - 0xd807aa98, - 0x12835b01, - 0x243185be, - 0x550c7dc3, - 0x72be5d74, - 0x80deb1fe, - 0x9bdc06a7, - 0xc19bf174, - 0xe49b69c1, - 0xefbe4786, - 0x0fc19dc6, - 0x240ca1cc, - 0x2de92c6f, - 0x4a7484aa, - 0x5cb0a9dc, - 0x76f988da, - 0x983e5152, - 0xa831c66d, - 0xb00327c8, - 0xbf597fc7, - 0xc6e00bf3, - 0xd5a79147, - 0x06ca6351, - 0x14292967, - 0x27b70a85, - 0x2e1b2138, - 0x4d2c6dfc, - 0x53380d13, - 0x650a7354, - 0x766a0abb, - 0x81c2c92e, - 0x92722c85, - 0xa2bfe8a1, - 0xa81a664b, - 0xc24b8b70, - 0xc76c51a3, - 0xd192e819, - 0xd6990624, - 0xf40e3585, - 0x106aa070, - 0x19a4c116, - 0x1e376c08, - 0x2748774c, - 0x34b0bcb5, - 0x391c0cb3, - 0x4ed8aa4a, - 0x5b9cca4f, - 0x682e6ff3, - 0x748f82ee, - 0x78a5636f, - 0x84c87814, - 0x8cc70208, - 0x90befffa, - 0xa4506ceb, - 0xbef9a3f7, - 0xc67178f2, + 0x428a2f98, + 0x71374491, + 0xb5c0fbcf, + 0xe9b5dba5, + 0x3956c25b, + 0x59f111f1, + 0x923f82a4, + 0xab1c5ed5, + 0xd807aa98, + 0x12835b01, + 0x243185be, + 0x550c7dc3, + 0x72be5d74, + 0x80deb1fe, + 0x9bdc06a7, + 0xc19bf174, + 0xe49b69c1, + 0xefbe4786, + 0x0fc19dc6, + 0x240ca1cc, + 0x2de92c6f, + 0x4a7484aa, + 0x5cb0a9dc, + 0x76f988da, + 0x983e5152, + 0xa831c66d, + 0xb00327c8, + 0xbf597fc7, + 0xc6e00bf3, + 0xd5a79147, + 0x06ca6351, + 0x14292967, + 0x27b70a85, + 0x2e1b2138, + 0x4d2c6dfc, + 0x53380d13, + 0x650a7354, + 0x766a0abb, + 0x81c2c92e, + 0x92722c85, + 0xa2bfe8a1, + 0xa81a664b, + 0xc24b8b70, + 0xc76c51a3, + 0xd192e819, + 0xd6990624, + 0xf40e3585, + 0x106aa070, + 0x19a4c116, + 0x1e376c08, + 0x2748774c, + 0x34b0bcb5, + 0x391c0cb3, + 0x4ed8aa4a, + 0x5b9cca4f, + 0x682e6ff3, + 0x748f82ee, + 0x78a5636f, + 0x84c87814, + 0x8cc70208, + 0x90befffa, + 0xa4506ceb, + 0xbef9a3f7, + 0xc67178f2, ]); function hashBlocks(w, v, p, pos, len) { - let a; - let b; - let c; - let d; - let e; - let f; - let g; - let h; - let u; - let i; - let j; - let t1; - let t2; - while (len >= 64) { - a = v[0]; - b = v[1]; - c = v[2]; - d = v[3]; - e = v[4]; - f = v[5]; - g = v[6]; - h = v[7]; - for (i = 0; i < 16; i++) { - j = pos + i * 4; - w[i] = - ((p[j] & 0xff) << 24) | - ((p[j + 1] & 0xff) << 16) | - ((p[j + 2] & 0xff) << 8) | - (p[j + 3] & 0xff); - } - for (i = 16; i < 64; i++) { - u = w[i - 2]; - t1 = - ((u >>> 17) | (u << (32 - 17))) ^ - ((u >>> 19) | (u << (32 - 19))) ^ - (u >>> 10); - u = w[i - 15]; - t2 = - ((u >>> 7) | (u << (32 - 7))) ^ - ((u >>> 18) | (u << (32 - 18))) ^ - (u >>> 3); - w[i] = ((t1 + w[i - 7]) | 0) + ((t2 + w[i - 16]) | 0); - } - for (i = 0; i < 64; i++) { - t1 = - ((((((e >>> 6) | (e << (32 - 6))) ^ - ((e >>> 11) | (e << (32 - 11))) ^ - ((e >>> 25) | (e << (32 - 25)))) + - ((e & f) ^ (~e & g))) | - 0) + - ((h + ((K[i] + w[i]) | 0)) | 0)) | - 0; - t2 = - ((((a >>> 2) | (a << (32 - 2))) ^ - ((a >>> 13) | (a << (32 - 13))) ^ - ((a >>> 22) | (a << (32 - 22)))) + - ((a & b) ^ (a & c) ^ (b & c))) | - 0; - h = g; - g = f; - f = e; - e = (d + t1) | 0; - d = c; - c = b; - b = a; - a = (t1 + t2) | 0; - } - v[0] += a; - v[1] += b; - v[2] += c; - v[3] += d; - v[4] += e; - v[5] += f; - v[6] += g; - v[7] += h; - pos += 64; - len -= 64; + let a; + let b; + let c; + let d; + let e; + let f; + let g; + let h; + let u; + let i; + let j; + let t1; + let t2; + while (len >= 64) { + a = v[0]; + b = v[1]; + c = v[2]; + d = v[3]; + e = v[4]; + f = v[5]; + g = v[6]; + h = v[7]; + for (i = 0; i < 16; i++) { + j = pos + i * 4; + w[i] = + ((p[j] & 0xff) << 24) | + ((p[j + 1] & 0xff) << 16) | + ((p[j + 2] & 0xff) << 8) | + (p[j + 3] & 0xff); } - return pos; + for (i = 16; i < 64; i++) { + u = w[i - 2]; + t1 = + ((u >>> 17) | (u << (32 - 17))) ^ + ((u >>> 19) | (u << (32 - 19))) ^ + (u >>> 10); + u = w[i - 15]; + t2 = + ((u >>> 7) | (u << (32 - 7))) ^ + ((u >>> 18) | (u << (32 - 18))) ^ + (u >>> 3); + w[i] = ((t1 + w[i - 7]) | 0) + ((t2 + w[i - 16]) | 0); + } + for (i = 0; i < 64; i++) { + t1 = + ((((((e >>> 6) | (e << (32 - 6))) ^ + ((e >>> 11) | (e << (32 - 11))) ^ + ((e >>> 25) | (e << (32 - 25)))) + + ((e & f) ^ (~e & g))) | + 0) + + ((h + ((K[i] + w[i]) | 0)) | 0)) | + 0; + t2 = + ((((a >>> 2) | (a << (32 - 2))) ^ + ((a >>> 13) | (a << (32 - 13))) ^ + ((a >>> 22) | (a << (32 - 22)))) + + ((a & b) ^ (a & c) ^ (b & c))) | + 0; + h = g; + g = f; + f = e; + e = (d + t1) | 0; + d = c; + c = b; + b = a; + a = (t1 + t2) | 0; + } + v[0] += a; + v[1] += b; + v[2] += c; + v[3] += d; + v[4] += e; + v[5] += f; + v[6] += g; + v[7] += h; + pos += 64; + len -= 64; + } + return pos; } // Hash implements SHA256 hash algorithm. class Hash { - constructor() { - this.digestLength = exports.digestLength; - this.blockSize = exports.blockSize; - this.finished = false; // indicates whether the hash was finalized - // Note: Int32Array is used instead of Uint32Array for performance reasons. - this.state = new Int32Array(8); // hash state - this.temp = new Int32Array(64); // temporary state - this.buffer = new Uint8Array(128); // buffer for data to hash - this.bufferLength = 0; // number of bytes in buffer - this.bytesHashed = 0; // number of total bytes hashed - this.reset(); + constructor() { + this.digestLength = exports.digestLength; + this.blockSize = exports.blockSize; + this.finished = false; // indicates whether the hash was finalized + // Note: Int32Array is used instead of Uint32Array for performance reasons. + this.state = new Int32Array(8); // hash state + this.temp = new Int32Array(64); // temporary state + this.buffer = new Uint8Array(128); // buffer for data to hash + this.bufferLength = 0; // number of bytes in buffer + this.bytesHashed = 0; // number of total bytes hashed + this.reset(); + } + // Resets hash state making it possible + // to re-use this instance to hash other data. + reset() { + this.state[0] = 0x6a09e667; + this.state[1] = 0xbb67ae85; + this.state[2] = 0x3c6ef372; + this.state[3] = 0xa54ff53a; + this.state[4] = 0x510e527f; + this.state[5] = 0x9b05688c; + this.state[6] = 0x1f83d9ab; + this.state[7] = 0x5be0cd19; + this.bufferLength = 0; + this.bytesHashed = 0; + this.finished = false; + return this; + } + // Cleans internal buffers and re-initializes hash state. + clean() { + for (let i = 0; i < this.buffer.length; i++) { + this.buffer[i] = 0; + } + for (let i = 0; i < this.temp.length; i++) { + this.temp[i] = 0; } - // Resets hash state making it possible - // to re-use this instance to hash other data. - reset() { - this.state[0] = 0x6a09e667; - this.state[1] = 0xbb67ae85; - this.state[2] = 0x3c6ef372; - this.state[3] = 0xa54ff53a; - this.state[4] = 0x510e527f; - this.state[5] = 0x9b05688c; - this.state[6] = 0x1f83d9ab; - this.state[7] = 0x5be0cd19; + this.reset(); + } + // Updates hash state with the given data. + // + // Optionally, length of the data can be specified to hash + // fewer bytes than data.length. + // + // Throws error when trying to update already finalized hash: + // instance must be reset to use it again. + update(data, dataLength = data.length) { + if (this.finished) { + throw new Error("SHA256: can't update because hash was finished."); + } + let dataPos = 0; + this.bytesHashed += dataLength; + if (this.bufferLength > 0) { + while (this.bufferLength < 64 && dataLength > 0) { + this.buffer[this.bufferLength++] = data[dataPos++]; + dataLength--; + } + if (this.bufferLength === 64) { + hashBlocks(this.temp, this.state, this.buffer, 0, 64); this.bufferLength = 0; - this.bytesHashed = 0; - this.finished = false; - return this; + } } - // Cleans internal buffers and re-initializes hash state. - clean() { - for (let i = 0; i < this.buffer.length; i++) { - this.buffer[i] = 0; - } - for (let i = 0; i < this.temp.length; i++) { - this.temp[i] = 0; - } - this.reset(); + if (dataLength >= 64) { + dataPos = hashBlocks(this.temp, this.state, data, dataPos, dataLength); + dataLength %= 64; } - // Updates hash state with the given data. - // - // Optionally, length of the data can be specified to hash - // fewer bytes than data.length. - // - // Throws error when trying to update already finalized hash: - // instance must be reset to use it again. - update(data, dataLength = data.length) { - if (this.finished) { - throw new Error("SHA256: can't update because hash was finished."); - } - let dataPos = 0; - this.bytesHashed += dataLength; - if (this.bufferLength > 0) { - while (this.bufferLength < 64 && dataLength > 0) { - this.buffer[this.bufferLength++] = data[dataPos++]; - dataLength--; - } - if (this.bufferLength === 64) { - hashBlocks(this.temp, this.state, this.buffer, 0, 64); - this.bufferLength = 0; - } - } - if (dataLength >= 64) { - dataPos = hashBlocks(this.temp, this.state, data, dataPos, dataLength); - dataLength %= 64; - } - while (dataLength > 0) { - this.buffer[this.bufferLength++] = data[dataPos++]; - dataLength--; - } - return this; + while (dataLength > 0) { + this.buffer[this.bufferLength++] = data[dataPos++]; + dataLength--; } - // Finalizes hash state and puts hash into out. - // - // If hash was already finalized, puts the same value. - finish(out) { - if (!this.finished) { - const bytesHashed = this.bytesHashed; - const left = this.bufferLength; - const bitLenHi = (bytesHashed / 0x20000000) | 0; - const bitLenLo = bytesHashed << 3; - const padLength = bytesHashed % 64 < 56 ? 64 : 128; - this.buffer[left] = 0x80; - for (let i = left + 1; i < padLength - 8; i++) { - this.buffer[i] = 0; - } - this.buffer[padLength - 8] = (bitLenHi >>> 24) & 0xff; - this.buffer[padLength - 7] = (bitLenHi >>> 16) & 0xff; - this.buffer[padLength - 6] = (bitLenHi >>> 8) & 0xff; - this.buffer[padLength - 5] = (bitLenHi >>> 0) & 0xff; - this.buffer[padLength - 4] = (bitLenLo >>> 24) & 0xff; - this.buffer[padLength - 3] = (bitLenLo >>> 16) & 0xff; - this.buffer[padLength - 2] = (bitLenLo >>> 8) & 0xff; - this.buffer[padLength - 1] = (bitLenLo >>> 0) & 0xff; - hashBlocks(this.temp, this.state, this.buffer, 0, padLength); - this.finished = true; - } - for (let i = 0; i < 8; i++) { - out[i * 4 + 0] = (this.state[i] >>> 24) & 0xff; - out[i * 4 + 1] = (this.state[i] >>> 16) & 0xff; - out[i * 4 + 2] = (this.state[i] >>> 8) & 0xff; - out[i * 4 + 3] = (this.state[i] >>> 0) & 0xff; - } - return this; + return this; + } + // Finalizes hash state and puts hash into out. + // + // If hash was already finalized, puts the same value. + finish(out) { + if (!this.finished) { + const bytesHashed = this.bytesHashed; + const left = this.bufferLength; + const bitLenHi = (bytesHashed / 0x20000000) | 0; + const bitLenLo = bytesHashed << 3; + const padLength = bytesHashed % 64 < 56 ? 64 : 128; + this.buffer[left] = 0x80; + for (let i = left + 1; i < padLength - 8; i++) { + this.buffer[i] = 0; + } + this.buffer[padLength - 8] = (bitLenHi >>> 24) & 0xff; + this.buffer[padLength - 7] = (bitLenHi >>> 16) & 0xff; + this.buffer[padLength - 6] = (bitLenHi >>> 8) & 0xff; + this.buffer[padLength - 5] = (bitLenHi >>> 0) & 0xff; + this.buffer[padLength - 4] = (bitLenLo >>> 24) & 0xff; + this.buffer[padLength - 3] = (bitLenLo >>> 16) & 0xff; + this.buffer[padLength - 2] = (bitLenLo >>> 8) & 0xff; + this.buffer[padLength - 1] = (bitLenLo >>> 0) & 0xff; + hashBlocks(this.temp, this.state, this.buffer, 0, padLength); + this.finished = true; } - // Returns the final hash digest. - digest() { - const out = new Uint8Array(this.digestLength); - this.finish(out); - return out; + for (let i = 0; i < 8; i++) { + out[i * 4 + 0] = (this.state[i] >>> 24) & 0xff; + out[i * 4 + 1] = (this.state[i] >>> 16) & 0xff; + out[i * 4 + 2] = (this.state[i] >>> 8) & 0xff; + out[i * 4 + 3] = (this.state[i] >>> 0) & 0xff; } - // Internal function for use in HMAC for optimization. - _saveState(out) { - for (let i = 0; i < this.state.length; i++) { - out[i] = this.state[i]; - } + return this; + } + // Returns the final hash digest. + digest() { + const out = new Uint8Array(this.digestLength); + this.finish(out); + return out; + } + // Internal function for use in HMAC for optimization. + _saveState(out) { + for (let i = 0; i < this.state.length; i++) { + out[i] = this.state[i]; } - // Internal function for use in HMAC for optimization. - _restoreState(from, bytesHashed) { - for (let i = 0; i < this.state.length; i++) { - this.state[i] = from[i]; - } - this.bytesHashed = bytesHashed; - this.finished = false; - this.bufferLength = 0; + } + // Internal function for use in HMAC for optimization. + _restoreState(from, bytesHashed) { + for (let i = 0; i < this.state.length; i++) { + this.state[i] = from[i]; } + this.bytesHashed = bytesHashed; + this.finished = false; + this.bufferLength = 0; + } } exports.Hash = Hash; // Returns SHA256 hash of data. function hash(data) { - const h = new Hash().update(data); - const digest = h.digest(); - h.clean(); - return digest; + const h = new Hash().update(data); + const digest = h.digest(); + h.clean(); + return digest; } exports.hash = hash; function sha256Midstate(data) { - let d = data; - if (data.length > exports.blockSize) { - d = data.slice(0, exports.blockSize); - } - const h = new Hash(); - h.reset(); - h.update(Uint8Array.from(d)); - const midstate = Buffer.alloc(exports.digestLength); - for (let i = 0; i < 8; i++) { - midstate[i * 4 + 0] = (h.state[i] >>> 24) & 0xff; - midstate[i * 4 + 1] = (h.state[i] >>> 16) & 0xff; - midstate[i * 4 + 2] = (h.state[i] >>> 8) & 0xff; - midstate[i * 4 + 3] = (h.state[i] >>> 0) & 0xff; - } - return midstate; + let d = data; + if (data.length > exports.blockSize) { + d = data.slice(0, exports.blockSize); + } + const h = new Hash(); + h.reset(); + h.update(Uint8Array.from(d)); + const midstate = Buffer.alloc(exports.digestLength); + for (let i = 0; i < 8; i++) { + midstate[i * 4 + 0] = (h.state[i] >>> 24) & 0xff; + midstate[i * 4 + 1] = (h.state[i] >>> 16) & 0xff; + midstate[i * 4 + 2] = (h.state[i] >>> 8) & 0xff; + midstate[i * 4 + 3] = (h.state[i] >>> 0) & 0xff; + } + return midstate; } exports.sha256Midstate = sha256Midstate; diff --git a/src/templates/multisig/index.js b/src/templates/multisig/index.js index 218d0d404..a010c5408 100644 --- a/src/templates/multisig/index.js +++ b/src/templates/multisig/index.js @@ -1,13 +1,17 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const input = __importStar(require("./input")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = __importStar(require('./input')); exports.input = input; -const output = __importStar(require("./output")); +const output = __importStar(require('./output')); exports.output = output; diff --git a/src/templates/multisig/input.js b/src/templates/multisig/input.js index faf94dded..0e959b751 100644 --- a/src/templates/multisig/input.js +++ b/src/templates/multisig/input.js @@ -1,30 +1,34 @@ -"use strict"; +'use strict'; // OP_0 [signatures ...] -var __importStar = (this && this.__importStar) || function (mod) { +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = __importStar(require("../../script")); -const script_1 = require("../../script"); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = __importStar(require('../../script')); +const script_1 = require('../../script'); function partialSignature(value) { - return (value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value)); + return ( + value === script_1.OPS.OP_0 || bscript.isCanonicalScriptSignature(value) + ); } function check(script, allowIncomplete) { - const chunks = bscript.decompile(script); - if (chunks.length < 2) - return false; - if (chunks[0] !== script_1.OPS.OP_0) - return false; - if (allowIncomplete) { - return chunks.slice(1).every(partialSignature); - } - return chunks.slice(1).every(bscript.isCanonicalScriptSignature); + const chunks = bscript.decompile(script); + if (chunks.length < 2) return false; + if (chunks[0] !== script_1.OPS.OP_0) return false; + if (allowIncomplete) { + return chunks.slice(1).every(partialSignature); + } + return chunks.slice(1).every(bscript.isCanonicalScriptSignature); } exports.check = check; check.toJSON = () => { - return 'multisig input'; + return 'multisig input'; }; diff --git a/src/templates/multisig/output.js b/src/templates/multisig/output.js index 072cd333e..642f8bdb2 100644 --- a/src/templates/multisig/output.js +++ b/src/templates/multisig/output.js @@ -1,43 +1,38 @@ -"use strict"; +'use strict'; // m [pubKeys ...] n OP_CHECKMULTISIG -var __importStar = (this && this.__importStar) || function (mod) { +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = __importStar(require("../../script")); -const script_1 = require("../../script"); -const types = __importStar(require("../../types")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = __importStar(require('../../script')); +const script_1 = require('../../script'); +const types = __importStar(require('../../types')); const OP_INT_BASE = script_1.OPS.OP_RESERVED; // OP_1 - 1 function check(script, allowIncomplete) { - const chunks = bscript.decompile(script); - if (chunks.length < 4) - return false; - if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG) - return false; - if (!types.Number(chunks[0])) - return false; - if (!types.Number(chunks[chunks.length - 2])) - return false; - const m = chunks[0] - OP_INT_BASE; - const n = chunks[chunks.length - 2] - OP_INT_BASE; - if (m <= 0) - return false; - if (n > 16) - return false; - if (m > n) - return false; - if (n !== chunks.length - 3) - return false; - if (allowIncomplete) - return true; - const keys = chunks.slice(1, -2); - return keys.every(bscript.isCanonicalPubKey); + const chunks = bscript.decompile(script); + if (chunks.length < 4) return false; + if (chunks[chunks.length - 1] !== script_1.OPS.OP_CHECKMULTISIG) return false; + if (!types.Number(chunks[0])) return false; + if (!types.Number(chunks[chunks.length - 2])) return false; + const m = chunks[0] - OP_INT_BASE; + const n = chunks[chunks.length - 2] - OP_INT_BASE; + if (m <= 0) return false; + if (n > 16) return false; + if (m > n) return false; + if (n !== chunks.length - 3) return false; + if (allowIncomplete) return true; + const keys = chunks.slice(1, -2); + return keys.every(bscript.isCanonicalPubKey); } exports.check = check; check.toJSON = () => { - return 'multi-sig output'; + return 'multi-sig output'; }; diff --git a/src/templates/nulldata.js b/src/templates/nulldata.js index 2bbca7aa9..bb54106ee 100644 --- a/src/templates/nulldata.js +++ b/src/templates/nulldata.js @@ -1,22 +1,26 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); + }; +Object.defineProperty(exports, '__esModule', { value: true }); // OP_RETURN {data} -const bscript = __importStar(require("../script")); +const bscript = __importStar(require('../script')); const OPS = bscript.OPS; function check(script) { - const buffer = bscript.compile(script); - return buffer.length > 1 && buffer[0] === OPS.OP_RETURN; + const buffer = bscript.compile(script); + return buffer.length > 1 && buffer[0] === OPS.OP_RETURN; } exports.check = check; check.toJSON = () => { - return 'null data output'; + return 'null data output'; }; const output = { check }; exports.output = output; diff --git a/src/templates/pubkey/index.js b/src/templates/pubkey/index.js index 218d0d404..a010c5408 100644 --- a/src/templates/pubkey/index.js +++ b/src/templates/pubkey/index.js @@ -1,13 +1,17 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const input = __importStar(require("./input")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = __importStar(require('./input')); exports.input = input; -const output = __importStar(require("./output")); +const output = __importStar(require('./output')); exports.output = output; diff --git a/src/templates/pubkey/input.js b/src/templates/pubkey/input.js index 89694240d..eef3fc500 100644 --- a/src/templates/pubkey/input.js +++ b/src/templates/pubkey/input.js @@ -1,20 +1,23 @@ -"use strict"; +'use strict'; // {signature} -var __importStar = (this && this.__importStar) || function (mod) { +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = __importStar(require("../../script")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = __importStar(require('../../script')); function check(script) { - const chunks = bscript.decompile(script); - return (chunks.length === 1 && - bscript.isCanonicalScriptSignature(chunks[0])); + const chunks = bscript.decompile(script); + return chunks.length === 1 && bscript.isCanonicalScriptSignature(chunks[0]); } exports.check = check; check.toJSON = () => { - return 'pubKey input'; + return 'pubKey input'; }; diff --git a/src/templates/pubkey/output.js b/src/templates/pubkey/output.js index 6bf5ba42b..cdff455fd 100644 --- a/src/templates/pubkey/output.js +++ b/src/templates/pubkey/output.js @@ -1,22 +1,28 @@ -"use strict"; +'use strict'; // {pubKey} OP_CHECKSIG -var __importStar = (this && this.__importStar) || function (mod) { +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = __importStar(require("../../script")); -const script_1 = require("../../script"); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = __importStar(require('../../script')); +const script_1 = require('../../script'); function check(script) { - const chunks = bscript.decompile(script); - return (chunks.length === 2 && - bscript.isCanonicalPubKey(chunks[0]) && - chunks[1] === script_1.OPS.OP_CHECKSIG); + const chunks = bscript.decompile(script); + return ( + chunks.length === 2 && + bscript.isCanonicalPubKey(chunks[0]) && + chunks[1] === script_1.OPS.OP_CHECKSIG + ); } exports.check = check; check.toJSON = () => { - return 'pubKey output'; + return 'pubKey output'; }; diff --git a/src/templates/pubkeyhash/index.js b/src/templates/pubkeyhash/index.js index 218d0d404..a010c5408 100644 --- a/src/templates/pubkeyhash/index.js +++ b/src/templates/pubkeyhash/index.js @@ -1,13 +1,17 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const input = __importStar(require("./input")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = __importStar(require('./input')); exports.input = input; -const output = __importStar(require("./output")); +const output = __importStar(require('./output')); exports.output = output; diff --git a/src/templates/pubkeyhash/input.js b/src/templates/pubkeyhash/input.js index 0c13041a3..ec869cace 100644 --- a/src/templates/pubkeyhash/input.js +++ b/src/templates/pubkeyhash/input.js @@ -1,21 +1,27 @@ -"use strict"; +'use strict'; // {signature} {pubKey} -var __importStar = (this && this.__importStar) || function (mod) { +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = __importStar(require("../../script")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = __importStar(require('../../script')); function check(script) { - const chunks = bscript.decompile(script); - return (chunks.length === 2 && - bscript.isCanonicalScriptSignature(chunks[0]) && - bscript.isCanonicalPubKey(chunks[1])); + const chunks = bscript.decompile(script); + return ( + chunks.length === 2 && + bscript.isCanonicalScriptSignature(chunks[0]) && + bscript.isCanonicalPubKey(chunks[1]) + ); } exports.check = check; check.toJSON = () => { - return 'pubKeyHash input'; + return 'pubKeyHash input'; }; diff --git a/src/templates/pubkeyhash/output.js b/src/templates/pubkeyhash/output.js index 32b7be5f5..98a708ff4 100644 --- a/src/templates/pubkeyhash/output.js +++ b/src/templates/pubkeyhash/output.js @@ -1,25 +1,31 @@ -"use strict"; +'use strict'; // OP_DUP OP_HASH160 {pubKeyHash} OP_EQUALVERIFY OP_CHECKSIG -var __importStar = (this && this.__importStar) || function (mod) { +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = __importStar(require("../../script")); -const script_1 = require("../../script"); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = __importStar(require('../../script')); +const script_1 = require('../../script'); function check(script) { - const buffer = bscript.compile(script); - return (buffer.length === 25 && - buffer[0] === script_1.OPS.OP_DUP && - buffer[1] === script_1.OPS.OP_HASH160 && - buffer[2] === 0x14 && - buffer[23] === script_1.OPS.OP_EQUALVERIFY && - buffer[24] === script_1.OPS.OP_CHECKSIG); + const buffer = bscript.compile(script); + return ( + buffer.length === 25 && + buffer[0] === script_1.OPS.OP_DUP && + buffer[1] === script_1.OPS.OP_HASH160 && + buffer[2] === 0x14 && + buffer[23] === script_1.OPS.OP_EQUALVERIFY && + buffer[24] === script_1.OPS.OP_CHECKSIG + ); } exports.check = check; check.toJSON = () => { - return 'pubKeyHash output'; + return 'pubKeyHash output'; }; diff --git a/src/templates/scripthash/index.js b/src/templates/scripthash/index.js index 218d0d404..a010c5408 100644 --- a/src/templates/scripthash/index.js +++ b/src/templates/scripthash/index.js @@ -1,13 +1,17 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const input = __importStar(require("./input")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = __importStar(require('./input')); exports.input = input; -const output = __importStar(require("./output")); +const output = __importStar(require('./output')); exports.output = output; diff --git a/src/templates/scripthash/input.js b/src/templates/scripthash/input.js index 3009335ae..81ec82a5f 100644 --- a/src/templates/scripthash/input.js +++ b/src/templates/scripthash/input.js @@ -1,51 +1,61 @@ -"use strict"; +'use strict'; // {serialized scriptPubKey script} -var __importStar = (this && this.__importStar) || function (mod) { +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = __importStar(require("../../script")); -const p2ms = __importStar(require("../multisig")); -const p2pk = __importStar(require("../pubkey")); -const p2pkh = __importStar(require("../pubkeyhash")); -const p2wpkho = __importStar(require("../witnesspubkeyhash/output")); -const p2wsho = __importStar(require("../witnessscripthash/output")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = __importStar(require('../../script')); +const p2ms = __importStar(require('../multisig')); +const p2pk = __importStar(require('../pubkey')); +const p2pkh = __importStar(require('../pubkeyhash')); +const p2wpkho = __importStar(require('../witnesspubkeyhash/output')); +const p2wsho = __importStar(require('../witnessscripthash/output')); function check(script, allowIncomplete) { - const chunks = bscript.decompile(script); - if (chunks.length < 1) - return false; - const lastChunk = chunks[chunks.length - 1]; - if (!Buffer.isBuffer(lastChunk)) - return false; - const scriptSigChunks = bscript.decompile(bscript.compile(chunks.slice(0, -1))); - const redeemScriptChunks = bscript.decompile(lastChunk); - // is redeemScript a valid script? - if (!redeemScriptChunks) - return false; - // is redeemScriptSig push only? - if (!bscript.isPushOnly(scriptSigChunks)) - return false; - // is witness? - if (chunks.length === 1) { - return (p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks)); - } - // match types - if (p2pkh.input.check(scriptSigChunks) && - p2pkh.output.check(redeemScriptChunks)) - return true; - if (p2ms.input.check(scriptSigChunks, allowIncomplete) && - p2ms.output.check(redeemScriptChunks)) - return true; - if (p2pk.input.check(scriptSigChunks) && - p2pk.output.check(redeemScriptChunks)) - return true; - return false; + const chunks = bscript.decompile(script); + if (chunks.length < 1) return false; + const lastChunk = chunks[chunks.length - 1]; + if (!Buffer.isBuffer(lastChunk)) return false; + const scriptSigChunks = bscript.decompile( + bscript.compile(chunks.slice(0, -1)), + ); + const redeemScriptChunks = bscript.decompile(lastChunk); + // is redeemScript a valid script? + if (!redeemScriptChunks) return false; + // is redeemScriptSig push only? + if (!bscript.isPushOnly(scriptSigChunks)) return false; + // is witness? + if (chunks.length === 1) { + return ( + p2wsho.check(redeemScriptChunks) || p2wpkho.check(redeemScriptChunks) + ); + } + // match types + if ( + p2pkh.input.check(scriptSigChunks) && + p2pkh.output.check(redeemScriptChunks) + ) + return true; + if ( + p2ms.input.check(scriptSigChunks, allowIncomplete) && + p2ms.output.check(redeemScriptChunks) + ) + return true; + if ( + p2pk.input.check(scriptSigChunks) && + p2pk.output.check(redeemScriptChunks) + ) + return true; + return false; } exports.check = check; check.toJSON = () => { - return 'scriptHash input'; + return 'scriptHash input'; }; diff --git a/src/templates/scripthash/output.js b/src/templates/scripthash/output.js index addd738c0..39af5731e 100644 --- a/src/templates/scripthash/output.js +++ b/src/templates/scripthash/output.js @@ -1,23 +1,29 @@ -"use strict"; +'use strict'; // OP_HASH160 {scriptHash} OP_EQUAL -var __importStar = (this && this.__importStar) || function (mod) { +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = __importStar(require("../../script")); -const script_1 = require("../../script"); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = __importStar(require('../../script')); +const script_1 = require('../../script'); function check(script) { - const buffer = bscript.compile(script); - return (buffer.length === 23 && - buffer[0] === script_1.OPS.OP_HASH160 && - buffer[1] === 0x14 && - buffer[22] === script_1.OPS.OP_EQUAL); + const buffer = bscript.compile(script); + return ( + buffer.length === 23 && + buffer[0] === script_1.OPS.OP_HASH160 && + buffer[1] === 0x14 && + buffer[22] === script_1.OPS.OP_EQUAL + ); } exports.check = check; check.toJSON = () => { - return 'scriptHash output'; + return 'scriptHash output'; }; diff --git a/src/templates/witnesscommitment/index.js b/src/templates/witnesscommitment/index.js index 7db518569..16e612b37 100644 --- a/src/templates/witnesscommitment/index.js +++ b/src/templates/witnesscommitment/index.js @@ -1,11 +1,15 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const output = __importStar(require("./output")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const output = __importStar(require('./output')); exports.output = output; diff --git a/src/templates/witnesscommitment/output.js b/src/templates/witnesscommitment/output.js index 938b579f3..b843c5f3c 100644 --- a/src/templates/witnesscommitment/output.js +++ b/src/templates/witnesscommitment/output.js @@ -1,39 +1,45 @@ -"use strict"; +'use strict'; // OP_RETURN {aa21a9ed} {commitment} -var __importStar = (this && this.__importStar) || function (mod) { +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = __importStar(require("../../script")); -const script_1 = require("../../script"); -const types = __importStar(require("../../types")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = __importStar(require('../../script')); +const script_1 = require('../../script'); +const types = __importStar(require('../../types')); const typeforce = require('typeforce'); const HEADER = Buffer.from('aa21a9ed', 'hex'); function check(script) { - const buffer = bscript.compile(script); - return (buffer.length > 37 && - buffer[0] === script_1.OPS.OP_RETURN && - buffer[1] === 0x24 && - buffer.slice(2, 6).equals(HEADER)); + const buffer = bscript.compile(script); + return ( + buffer.length > 37 && + buffer[0] === script_1.OPS.OP_RETURN && + buffer[1] === 0x24 && + buffer.slice(2, 6).equals(HEADER) + ); } exports.check = check; check.toJSON = () => { - return 'Witness commitment output'; + return 'Witness commitment output'; }; function encode(commitment) { - typeforce(types.Hash256bit, commitment); - const buffer = Buffer.allocUnsafe(36); - HEADER.copy(buffer, 0); - commitment.copy(buffer, 4); - return bscript.compile([script_1.OPS.OP_RETURN, buffer]); + typeforce(types.Hash256bit, commitment); + const buffer = Buffer.allocUnsafe(36); + HEADER.copy(buffer, 0); + commitment.copy(buffer, 4); + return bscript.compile([script_1.OPS.OP_RETURN, buffer]); } exports.encode = encode; function decode(buffer) { - typeforce(check, buffer); - return bscript.decompile(buffer)[1].slice(4, 36); + typeforce(check, buffer); + return bscript.decompile(buffer)[1].slice(4, 36); } exports.decode = decode; diff --git a/src/templates/witnesspubkeyhash/index.js b/src/templates/witnesspubkeyhash/index.js index 218d0d404..a010c5408 100644 --- a/src/templates/witnesspubkeyhash/index.js +++ b/src/templates/witnesspubkeyhash/index.js @@ -1,13 +1,17 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const input = __importStar(require("./input")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = __importStar(require('./input')); exports.input = input; -const output = __importStar(require("./output")); +const output = __importStar(require('./output')); exports.output = output; diff --git a/src/templates/witnesspubkeyhash/input.js b/src/templates/witnesspubkeyhash/input.js index d98f08072..820935f15 100644 --- a/src/templates/witnesspubkeyhash/input.js +++ b/src/templates/witnesspubkeyhash/input.js @@ -1,24 +1,30 @@ -"use strict"; +'use strict'; // {signature} {pubKey} -var __importStar = (this && this.__importStar) || function (mod) { +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = __importStar(require("../../script")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = __importStar(require('../../script')); function isCompressedCanonicalPubKey(pubKey) { - return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33; + return bscript.isCanonicalPubKey(pubKey) && pubKey.length === 33; } function check(script) { - const chunks = bscript.decompile(script); - return (chunks.length === 2 && - bscript.isCanonicalScriptSignature(chunks[0]) && - isCompressedCanonicalPubKey(chunks[1])); + const chunks = bscript.decompile(script); + return ( + chunks.length === 2 && + bscript.isCanonicalScriptSignature(chunks[0]) && + isCompressedCanonicalPubKey(chunks[1]) + ); } exports.check = check; check.toJSON = () => { - return 'witnessPubKeyHash input'; + return 'witnessPubKeyHash input'; }; diff --git a/src/templates/witnesspubkeyhash/output.js b/src/templates/witnesspubkeyhash/output.js index 5ee0f07c6..545251353 100644 --- a/src/templates/witnesspubkeyhash/output.js +++ b/src/templates/witnesspubkeyhash/output.js @@ -1,20 +1,28 @@ -"use strict"; +'use strict'; // OP_0 {pubKeyHash} -var __importStar = (this && this.__importStar) || function (mod) { +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = __importStar(require("../../script")); -const script_1 = require("../../script"); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = __importStar(require('../../script')); +const script_1 = require('../../script'); function check(script) { - const buffer = bscript.compile(script); - return buffer.length === 22 && buffer[0] === script_1.OPS.OP_0 && buffer[1] === 0x14; + const buffer = bscript.compile(script); + return ( + buffer.length === 22 && + buffer[0] === script_1.OPS.OP_0 && + buffer[1] === 0x14 + ); } exports.check = check; check.toJSON = () => { - return 'Witness pubKeyHash output'; + return 'Witness pubKeyHash output'; }; diff --git a/src/templates/witnessscripthash/index.js b/src/templates/witnessscripthash/index.js index 218d0d404..a010c5408 100644 --- a/src/templates/witnessscripthash/index.js +++ b/src/templates/witnessscripthash/index.js @@ -1,13 +1,17 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const input = __importStar(require("./input")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const input = __importStar(require('./input')); exports.input = input; -const output = __importStar(require("./output")); +const output = __importStar(require('./output')); exports.output = output; diff --git a/src/templates/witnessscripthash/input.js b/src/templates/witnessscripthash/input.js index a1ac77663..be2a82eeb 100644 --- a/src/templates/witnessscripthash/input.js +++ b/src/templates/witnessscripthash/input.js @@ -1,43 +1,50 @@ -"use strict"; +'use strict'; // {serialized scriptPubKey script} -var __importStar = (this && this.__importStar) || function (mod) { +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = __importStar(require("../../script")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = __importStar(require('../../script')); const typeforce = require('typeforce'); -const p2ms = __importStar(require("../multisig")); -const p2pk = __importStar(require("../pubkey")); -const p2pkh = __importStar(require("../pubkeyhash")); +const p2ms = __importStar(require('../multisig')); +const p2pk = __importStar(require('../pubkey')); +const p2pkh = __importStar(require('../pubkeyhash')); function check(chunks, allowIncomplete) { - typeforce(typeforce.Array, chunks); - if (chunks.length < 1) - return false; - const witnessScript = chunks[chunks.length - 1]; - if (!Buffer.isBuffer(witnessScript)) - return false; - const witnessScriptChunks = bscript.decompile(witnessScript); - // is witnessScript a valid script? - if (!witnessScriptChunks || witnessScriptChunks.length === 0) - return false; - const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1)); - // match types - if (p2pkh.input.check(witnessRawScriptSig) && - p2pkh.output.check(witnessScriptChunks)) - return true; - if (p2ms.input.check(witnessRawScriptSig, allowIncomplete) && - p2ms.output.check(witnessScriptChunks)) - return true; - if (p2pk.input.check(witnessRawScriptSig) && - p2pk.output.check(witnessScriptChunks)) - return true; - return false; + typeforce(typeforce.Array, chunks); + if (chunks.length < 1) return false; + const witnessScript = chunks[chunks.length - 1]; + if (!Buffer.isBuffer(witnessScript)) return false; + const witnessScriptChunks = bscript.decompile(witnessScript); + // is witnessScript a valid script? + if (!witnessScriptChunks || witnessScriptChunks.length === 0) return false; + const witnessRawScriptSig = bscript.compile(chunks.slice(0, -1)); + // match types + if ( + p2pkh.input.check(witnessRawScriptSig) && + p2pkh.output.check(witnessScriptChunks) + ) + return true; + if ( + p2ms.input.check(witnessRawScriptSig, allowIncomplete) && + p2ms.output.check(witnessScriptChunks) + ) + return true; + if ( + p2pk.input.check(witnessRawScriptSig) && + p2pk.output.check(witnessScriptChunks) + ) + return true; + return false; } exports.check = check; check.toJSON = () => { - return 'witnessScriptHash input'; + return 'witnessScriptHash input'; }; diff --git a/src/templates/witnessscripthash/output.js b/src/templates/witnessscripthash/output.js index 7b95a1b6e..9159204ef 100644 --- a/src/templates/witnessscripthash/output.js +++ b/src/templates/witnessscripthash/output.js @@ -1,20 +1,28 @@ -"use strict"; +'use strict'; // OP_0 {scriptHash} -var __importStar = (this && this.__importStar) || function (mod) { +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bscript = __importStar(require("../../script")); -const script_1 = require("../../script"); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bscript = __importStar(require('../../script')); +const script_1 = require('../../script'); function check(script) { - const buffer = bscript.compile(script); - return buffer.length === 34 && buffer[0] === script_1.OPS.OP_0 && buffer[1] === 0x20; + const buffer = bscript.compile(script); + return ( + buffer.length === 34 && + buffer[0] === script_1.OPS.OP_0 && + buffer[1] === 0x20 + ); } exports.check = check; check.toJSON = () => { - return 'Witness scriptHash output'; + return 'Witness scriptHash output'; }; diff --git a/src/transaction.js b/src/transaction.js index 792853b6b..5475e01a4 100644 --- a/src/transaction.js +++ b/src/transaction.js @@ -1,27 +1,37 @@ -"use strict"; -var __importStar = (this && this.__importStar) || function (mod) { +'use strict'; +var __importStar = + (this && this.__importStar) || + function(mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result["default"] = mod; + if (mod != null) + for (var k in mod) + if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result['default'] = mod; return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const bufferutils_1 = require("./bufferutils"); -const bcrypto = __importStar(require("./crypto")); -const bscript = __importStar(require("./script")); -const script_1 = require("./script"); -const types = __importStar(require("./types")); + }; +Object.defineProperty(exports, '__esModule', { value: true }); +const bufferutils_1 = require('./bufferutils'); +const bcrypto = __importStar(require('./crypto')); +const bscript = __importStar(require('./script')); +const script_1 = require('./script'); +const types = __importStar(require('./types')); const typeforce = require('typeforce'); const varuint = require('varuint-bitcoin'); function varSliceSize(someScript) { - const length = someScript.length; - return varuint.encodingLength(length) + length; + const length = someScript.length; + return varuint.encodingLength(length) + length; } const EMPTY_SCRIPT = Buffer.allocUnsafe(0); const EMPTY_WITNESS = []; -exports.ZERO = Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex'); -const ONE = Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex'); +exports.ZERO = Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000000', + 'hex', +); +const ONE = Buffer.from( + '0000000000000000000000000000000000000000000000000000000000000001', + 'hex', +); const WITNESS_SCALE_FACTOR = 4; const OUTPOINT_ISSUANCE_FLAG = (1 << 31) >>> 0; const OUTPOINT_PEGIN_FLAG = (1 << 30) >>> 0; @@ -29,553 +39,637 @@ const OUTPOINT_INDEX_MASK = 0x3fffffff; const MINUS_1 = 4294967295; const VALUE_UINT64_MAX = Buffer.from('ffffffffffffffff', 'hex'); const BLANK_OUTPUT = { - script: EMPTY_SCRIPT, - asset: exports.ZERO, - nonce: exports.ZERO, - value: VALUE_UINT64_MAX, + script: EMPTY_SCRIPT, + asset: exports.ZERO, + nonce: exports.ZERO, + value: VALUE_UINT64_MAX, }; class Transaction { - constructor() { - this.version = 1; - this.locktime = 0; - this.flag = 0; - this.ins = []; - this.outs = []; - } - static fromBuffer(buffer, _NO_STRICT) { - const bufferReader = new bufferutils_1.BufferReader(buffer); - const tx = new Transaction(); - tx.version = bufferReader.readInt32(); - tx.flag = bufferReader.readUInt8(); - const vinLen = bufferReader.readVarInt(); - for (let i = 0; i < vinLen; ++i) { - const inHash = bufferReader.readSlice(32); - let inIndex = bufferReader.readUInt32(); - const inScript = bufferReader.readVarSlice(); - const inSequence = bufferReader.readUInt32(); - let inIsPegin = false; - let inIssuance; - if (inIndex !== MINUS_1) { - if (inIndex & OUTPOINT_ISSUANCE_FLAG) { - inIssuance = bufferReader.readIssuance(); - } - if (inIndex & OUTPOINT_PEGIN_FLAG) { - inIsPegin = true; - } - inIndex &= OUTPOINT_INDEX_MASK; - } - tx.ins.push({ - hash: inHash, - index: inIndex, - script: inScript, - sequence: inSequence, - witness: EMPTY_WITNESS, - isPegin: inIsPegin, - issuance: inIssuance, - peginWitness: EMPTY_WITNESS, - issuanceRangeProof: EMPTY_SCRIPT, - inflationRangeProof: EMPTY_SCRIPT, - }); - } - const voutLen = bufferReader.readVarInt(); - for (let i = 0; i < voutLen; ++i) { - const asset = bufferReader.readConfidentialAsset(); - const value = bufferReader.readConfidentialValue(); - const nonce = bufferReader.readConfidentialNonce(); - const script = bufferReader.readVarSlice(); - tx.outs.push({ - asset, - value, - nonce, - script, - rangeProof: EMPTY_SCRIPT, - surjectionProof: EMPTY_SCRIPT, - }); - } - tx.locktime = bufferReader.readUInt32(); - if (tx.flag === 1) { - for (let i = 0; i < vinLen; ++i) { - const { witness, peginWitness, issuanceRangeProof, inflationRangeProof, } = bufferReader.readConfidentialInFields(); - tx.ins[i].witness = witness; - tx.ins[i].peginWitness = peginWitness; - tx.ins[i].issuanceRangeProof = issuanceRangeProof; - tx.ins[i].inflationRangeProof = inflationRangeProof; - } - for (let i = 0; i < voutLen; ++i) { - const { rangeProof, surjectionProof, } = bufferReader.readConfidentialOutFields(); - tx.outs[i].rangeProof = rangeProof; - tx.outs[i].surjectionProof = surjectionProof; - } + constructor() { + this.version = 1; + this.locktime = 0; + this.flag = 0; + this.ins = []; + this.outs = []; + } + static fromBuffer(buffer, _NO_STRICT) { + const bufferReader = new bufferutils_1.BufferReader(buffer); + const tx = new Transaction(); + tx.version = bufferReader.readInt32(); + tx.flag = bufferReader.readUInt8(); + const vinLen = bufferReader.readVarInt(); + for (let i = 0; i < vinLen; ++i) { + const inHash = bufferReader.readSlice(32); + let inIndex = bufferReader.readUInt32(); + const inScript = bufferReader.readVarSlice(); + const inSequence = bufferReader.readUInt32(); + let inIsPegin = false; + let inIssuance; + if (inIndex !== MINUS_1) { + if (inIndex & OUTPOINT_ISSUANCE_FLAG) { + inIssuance = bufferReader.readIssuance(); } - if (_NO_STRICT) - return tx; - if (bufferReader.offset !== buffer.length) - throw new Error('Transaction has unexpected data'); - return tx; - } - static fromHex(hex) { - return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false); - } - static isCoinbaseHash(buffer) { - typeforce(types.Hash256bit, buffer); - for (let i = 0; i < 32; ++i) { - if (buffer[i] !== 0) - return false; + if (inIndex & OUTPOINT_PEGIN_FLAG) { + inIsPegin = true; } - return true; + inIndex &= OUTPOINT_INDEX_MASK; + } + tx.ins.push({ + hash: inHash, + index: inIndex, + script: inScript, + sequence: inSequence, + witness: EMPTY_WITNESS, + isPegin: inIsPegin, + issuance: inIssuance, + peginWitness: EMPTY_WITNESS, + issuanceRangeProof: EMPTY_SCRIPT, + inflationRangeProof: EMPTY_SCRIPT, + }); } - isCoinbase() { - return (this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash)); + const voutLen = bufferReader.readVarInt(); + for (let i = 0; i < voutLen; ++i) { + const asset = bufferReader.readConfidentialAsset(); + const value = bufferReader.readConfidentialValue(); + const nonce = bufferReader.readConfidentialNonce(); + const script = bufferReader.readVarSlice(); + tx.outs.push({ + asset, + value, + nonce, + script, + rangeProof: EMPTY_SCRIPT, + surjectionProof: EMPTY_SCRIPT, + }); } - // A quick and reliable way to validate that all the buffers are of correct type and length - validateIssuance(assetBlindingNonce, assetEntropy, assetAmount, tokenAmount) { - typeforce(types.Hash256bit, assetBlindingNonce); - typeforce(types.Hash256bit, assetEntropy); - typeforce(types.oneOf(types.ConfidentialValue, types.ConfidentialCommitment, types.BufferOne), assetAmount); - typeforce(types.oneOf(types.ConfidentialValue, types.ConfidentialCommitment, types.BufferOne), tokenAmount); - return true; + tx.locktime = bufferReader.readUInt32(); + if (tx.flag === 1) { + for (let i = 0; i < vinLen; ++i) { + const { + witness, + peginWitness, + issuanceRangeProof, + inflationRangeProof, + } = bufferReader.readConfidentialInFields(); + tx.ins[i].witness = witness; + tx.ins[i].peginWitness = peginWitness; + tx.ins[i].issuanceRangeProof = issuanceRangeProof; + tx.ins[i].inflationRangeProof = inflationRangeProof; + } + for (let i = 0; i < voutLen; ++i) { + const { + rangeProof, + surjectionProof, + } = bufferReader.readConfidentialOutFields(); + tx.outs[i].rangeProof = rangeProof; + tx.outs[i].surjectionProof = surjectionProof; + } } - addInput(hash, index, sequence, scriptSig, issuance) { - typeforce(types.tuple(types.Hash256bit, types.UInt32, types.maybe(types.UInt32), types.maybe(types.Buffer), types.maybe(types.Object)), arguments); - let isPegin = false; - if (index !== MINUS_1) { - if (index & OUTPOINT_ISSUANCE_FLAG) { - if (!issuance) { - throw new Error('Issuance flag has been set but the Issuance object is not defined or invalid'); - } - else - this.validateIssuance(issuance.assetBlindingNonce, issuance.assetEntropy, issuance.assetAmount, issuance.tokenAmount); - } - if (index & OUTPOINT_PEGIN_FLAG) { - isPegin = true; - } - index &= OUTPOINT_INDEX_MASK; - } - // Add the input and return the input's index - return (this.ins.push({ - hash, - index, - isPegin, - issuance, - witness: EMPTY_WITNESS, - peginWitness: EMPTY_WITNESS, - issuanceRangeProof: EMPTY_SCRIPT, - inflationRangeProof: EMPTY_SCRIPT, - script: scriptSig || EMPTY_SCRIPT, - sequence: sequence || Transaction.DEFAULT_SEQUENCE, - }) - 1); + if (_NO_STRICT) return tx; + if (bufferReader.offset !== buffer.length) + throw new Error('Transaction has unexpected data'); + return tx; + } + static fromHex(hex) { + return Transaction.fromBuffer(Buffer.from(hex, 'hex'), false); + } + static isCoinbaseHash(buffer) { + typeforce(types.Hash256bit, buffer); + for (let i = 0; i < 32; ++i) { + if (buffer[i] !== 0) return false; } - addOutput(scriptPubKey, value, asset, nonce, rangeProof, surjectionProof) { - typeforce(types.tuple(types.Buffer, types.oneOf(types.ConfidentialValue, types.ConfidentialCommitment, types.BufferOne), types.oneOf(types.ConfidentialCommitment, types.BufferOne), types.oneOf(types.ConfidentialCommitment, types.BufferOne), types.maybe(types.Buffer), types.maybe(types.Buffer)), arguments); - // Add the output and return the output's index - return (this.outs.push({ - script: scriptPubKey, - value, - asset, - nonce, - rangeProof: rangeProof || EMPTY_SCRIPT, - surjectionProof: surjectionProof || EMPTY_SCRIPT, - }) - 1); + return true; + } + isCoinbase() { + return ( + this.ins.length === 1 && Transaction.isCoinbaseHash(this.ins[0].hash) + ); + } + // A quick and reliable way to validate that all the buffers are of correct type and length + validateIssuance(assetBlindingNonce, assetEntropy, assetAmount, tokenAmount) { + typeforce(types.Hash256bit, assetBlindingNonce); + typeforce(types.Hash256bit, assetEntropy); + typeforce( + types.oneOf( + types.ConfidentialValue, + types.ConfidentialCommitment, + types.BufferOne, + ), + assetAmount, + ); + typeforce( + types.oneOf( + types.ConfidentialValue, + types.ConfidentialCommitment, + types.BufferOne, + ), + tokenAmount, + ); + return true; + } + addInput(hash, index, sequence, scriptSig, issuance) { + typeforce( + types.tuple( + types.Hash256bit, + types.UInt32, + types.maybe(types.UInt32), + types.maybe(types.Buffer), + types.maybe(types.Object), + ), + arguments, + ); + let isPegin = false; + if (index !== MINUS_1) { + if (index & OUTPOINT_ISSUANCE_FLAG) { + if (!issuance) { + throw new Error( + 'Issuance flag has been set but the Issuance object is not defined or invalid', + ); + } else + this.validateIssuance( + issuance.assetBlindingNonce, + issuance.assetEntropy, + issuance.assetAmount, + issuance.tokenAmount, + ); + } + if (index & OUTPOINT_PEGIN_FLAG) { + isPegin = true; + } + index &= OUTPOINT_INDEX_MASK; } - hasWitnesses() { - return (this.flag === 1 || - this.ins.some(x => { - return x.witness.length !== 0; - }) || - this.outs.some(x => { - return x.rangeProof.length !== 0 && x.surjectionProof.length !== 0; - })); + // Add the input and return the input's index + return ( + this.ins.push({ + hash, + index, + isPegin, + issuance, + witness: EMPTY_WITNESS, + peginWitness: EMPTY_WITNESS, + issuanceRangeProof: EMPTY_SCRIPT, + inflationRangeProof: EMPTY_SCRIPT, + script: scriptSig || EMPTY_SCRIPT, + sequence: sequence || Transaction.DEFAULT_SEQUENCE, + }) - 1 + ); + } + addOutput(scriptPubKey, value, asset, nonce, rangeProof, surjectionProof) { + typeforce( + types.tuple( + types.Buffer, + types.oneOf( + types.ConfidentialValue, + types.ConfidentialCommitment, + types.BufferOne, + ), + types.oneOf(types.ConfidentialCommitment, types.BufferOne), + types.oneOf(types.ConfidentialCommitment, types.BufferOne), + types.maybe(types.Buffer), + types.maybe(types.Buffer), + ), + arguments, + ); + // Add the output and return the output's index + return ( + this.outs.push({ + script: scriptPubKey, + value, + asset, + nonce, + rangeProof: rangeProof || EMPTY_SCRIPT, + surjectionProof: surjectionProof || EMPTY_SCRIPT, + }) - 1 + ); + } + hasWitnesses() { + return ( + this.flag === 1 || + this.ins.some(x => { + return x.witness.length !== 0; + }) || + this.outs.some(x => { + return x.rangeProof.length !== 0 && x.surjectionProof.length !== 0; + }) + ); + } + weight() { + const base = this.__byteLength(false); + const total = this.__byteLength(true); + return base * (WITNESS_SCALE_FACTOR - 1) + total; + } + virtualSize() { + const vsize = + (this.weight() + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR; + return Math.floor(vsize); + } + byteLength(_ALLOW_WITNESS) { + return this.__byteLength(_ALLOW_WITNESS || true); + } + clone() { + const newTx = new Transaction(); + newTx.version = this.version; + newTx.locktime = this.locktime; + newTx.flag = this.flag; + newTx.ins = this.ins.map(txIn => { + return { + hash: txIn.hash, + index: txIn.index, + script: txIn.script, + sequence: txIn.sequence, + witness: txIn.witness, + isPegin: txIn.isPegin, + issuance: txIn.issuance, + peginWitness: txIn.peginWitness, + issuanceRangeProof: txIn.issuanceRangeProof, + inflationRangeProof: txIn.inflationRangeProof, + }; + }); + newTx.outs = this.outs.map(txOut => { + return { + script: txOut.script, + value: txOut.value, + asset: txOut.asset, + nonce: txOut.nonce, + rangeProof: txOut.rangeProof, + surjectionProof: txOut.surjectionProof, + }; + }); + return newTx; + } + /** + * Hash transaction for signing a specific input. + * + * Bitcoin uses a different hash for each signed transaction input. + * This method copies the transaction, makes the necessary changes based on the + * hashType, and then hashes the result. + * This hash can then be used to sign the provided transaction input. + */ + hashForSignature(inIndex, prevOutScript, hashType) { + typeforce( + types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), + arguments, + ); + // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29 + if (inIndex >= this.ins.length) return ONE; + // ignore OP_CODESEPARATOR + const ourScript = bscript.compile( + bscript.decompile(prevOutScript).filter(x => { + return x !== script_1.OPS.OP_CODESEPARATOR; + }), + ); + const txTmp = this.clone(); + // SIGHASH_NONE: ignore all outputs? (wildcard payee) + if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) { + txTmp.outs = []; + // ignore sequence numbers (except at inIndex) + txTmp.ins.forEach((input, i) => { + if (i === inIndex) return; + input.sequence = 0; + }); + // SIGHASH_SINGLE: ignore all outputs, except at the same index? + } else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) { + // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60 + if (inIndex >= this.outs.length) return ONE; + // truncate outputs after + txTmp.outs.length = inIndex + 1; + // "blank" outputs before + for (let i = 0; i < inIndex; i++) { + txTmp.outs[i] = BLANK_OUTPUT; + } + // ignore sequence numbers (except at inIndex) + txTmp.ins.forEach((input, y) => { + if (y === inIndex) return; + input.sequence = 0; + }); } - weight() { - const base = this.__byteLength(false); - const total = this.__byteLength(true); - return base * (WITNESS_SCALE_FACTOR - 1) + total; + // SIGHASH_ANYONECANPAY: ignore inputs entirely? + if (hashType & Transaction.SIGHASH_ANYONECANPAY) { + txTmp.ins = [txTmp.ins[inIndex]]; + txTmp.ins[0].script = ourScript; + // SIGHASH_ALL: only ignore input scripts + } else { + // "blank" others input scripts + txTmp.ins.forEach(input => { + input.script = EMPTY_SCRIPT; + }); + txTmp.ins[inIndex].script = ourScript; } - virtualSize() { - const vsize = (this.weight() + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR; - return Math.floor(vsize); + // serialize and hash + const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false, true) + 4); + buffer.writeInt32LE(hashType, buffer.length - 4); + txTmp.__toBuffer(buffer, 0, false, true, true); + return bcrypto.hash256(buffer); + } + hashForWitnessV0(inIndex, prevOutScript, value, hashType) { + typeforce( + types.tuple(types.UInt32, types.Buffer, types.Buffer, types.UInt32), + arguments, + ); + function writeInputs(ins) { + const tBuffer = Buffer.allocUnsafe(36 * ins.length); + const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); + ins.forEach(txIn => { + tBufferWriter.writeSlice(txIn.hash); + tBufferWriter.writeUInt32(txIn.index); + }); + return bcrypto.hash256(tBuffer); } - byteLength(_ALLOW_WITNESS) { - return this.__byteLength(_ALLOW_WITNESS || true); + function writeSequences(ins) { + const tBuffer = Buffer.allocUnsafe(4 * ins.length); + const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); + ins.forEach(txIn => { + tBufferWriter.writeUInt32(txIn.sequence); + }); + return bcrypto.hash256(tBuffer); } - clone() { - const newTx = new Transaction(); - newTx.version = this.version; - newTx.locktime = this.locktime; - newTx.flag = this.flag; - newTx.ins = this.ins.map(txIn => { - return { - hash: txIn.hash, - index: txIn.index, - script: txIn.script, - sequence: txIn.sequence, - witness: txIn.witness, - isPegin: txIn.isPegin, - issuance: txIn.issuance, - peginWitness: txIn.peginWitness, - issuanceRangeProof: txIn.issuanceRangeProof, - inflationRangeProof: txIn.inflationRangeProof, - }; - }); - newTx.outs = this.outs.map(txOut => { - return { - script: txOut.script, - value: txOut.value, - asset: txOut.asset, - nonce: txOut.nonce, - rangeProof: txOut.rangeProof, - surjectionProof: txOut.surjectionProof, - }; - }); - return newTx; + function issuanceSize(ins) { + return ins.reduce( + (sum, txIn) => + !types.Null(txIn.issuance) + ? sum + + txIn.issuance.assetBlindingNonce.length + + txIn.issuance.assetEntropy.length + + txIn.issuance.assetAmount.length + + txIn.issuance.tokenAmount.length + : sum, // we'll use the empty 00 Buffer if issuance is not set + 0, + ); } - /** - * Hash transaction for signing a specific input. - * - * Bitcoin uses a different hash for each signed transaction input. - * This method copies the transaction, makes the necessary changes based on the - * hashType, and then hashes the result. - * This hash can then be used to sign the provided transaction input. - */ - hashForSignature(inIndex, prevOutScript, hashType) { - typeforce(types.tuple(types.UInt32, types.Buffer, /* types.UInt8 */ types.Number), arguments); - // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L29 - if (inIndex >= this.ins.length) - return ONE; - // ignore OP_CODESEPARATOR - const ourScript = bscript.compile(bscript.decompile(prevOutScript).filter(x => { - return x !== script_1.OPS.OP_CODESEPARATOR; - })); - const txTmp = this.clone(); - // SIGHASH_NONE: ignore all outputs? (wildcard payee) - if ((hashType & 0x1f) === Transaction.SIGHASH_NONE) { - txTmp.outs = []; - // ignore sequence numbers (except at inIndex) - txTmp.ins.forEach((input, i) => { - if (i === inIndex) - return; - input.sequence = 0; - }); - // SIGHASH_SINGLE: ignore all outputs, except at the same index? - } - else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE) { - // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60 - if (inIndex >= this.outs.length) - return ONE; - // truncate outputs after - txTmp.outs.length = inIndex + 1; - // "blank" outputs before - for (let i = 0; i < inIndex; i++) { - txTmp.outs[i] = BLANK_OUTPUT; - } - // ignore sequence numbers (except at inIndex) - txTmp.ins.forEach((input, y) => { - if (y === inIndex) - return; - input.sequence = 0; - }); - } - // SIGHASH_ANYONECANPAY: ignore inputs entirely? - if (hashType & Transaction.SIGHASH_ANYONECANPAY) { - txTmp.ins = [txTmp.ins[inIndex]]; - txTmp.ins[0].script = ourScript; - // SIGHASH_ALL: only ignore input scripts - } - else { - // "blank" others input scripts - txTmp.ins.forEach(input => { - input.script = EMPTY_SCRIPT; - }); - txTmp.ins[inIndex].script = ourScript; - } - // serialize and hash - const buffer = Buffer.allocUnsafe(txTmp.__byteLength(false, true) + 4); - buffer.writeInt32LE(hashType, buffer.length - 4); - txTmp.__toBuffer(buffer, 0, false, true, true); - return bcrypto.hash256(buffer); - } - hashForWitnessV0(inIndex, prevOutScript, value, hashType) { - typeforce(types.tuple(types.UInt32, types.Buffer, types.Buffer, types.UInt32), arguments); - function writeInputs(ins) { - const tBuffer = Buffer.allocUnsafe(36 * ins.length); - const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); - ins.forEach((txIn) => { - tBufferWriter.writeSlice(txIn.hash); - tBufferWriter.writeUInt32(txIn.index); - }); - return bcrypto.hash256(tBuffer); - } - function writeSequences(ins) { - const tBuffer = Buffer.allocUnsafe(4 * ins.length); - const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); - ins.forEach((txIn) => { - tBufferWriter.writeUInt32(txIn.sequence); - }); - return bcrypto.hash256(tBuffer); - } - function issuanceSize(ins) { - return ins.reduce((sum, txIn) => !types.Null(txIn.issuance) - ? sum + - txIn.issuance.assetBlindingNonce.length + - txIn.issuance.assetEntropy.length + - txIn.issuance.assetAmount.length + - txIn.issuance.tokenAmount.length - : sum, // we'll use the empty 00 Buffer if issuance is not set - 0); - } - function writeIssuances(ins, sizeIssuances) { - const size = sizeIssuances === 0 ? ins.length : sizeIssuances; - const tBuffer = Buffer.allocUnsafe(size); - const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); - ins.forEach((txIn) => { - if (!types.Null(txIn.issuance)) { - tBufferWriter.writeSlice(txIn.issuance.assetBlindingNonce); - tBufferWriter.writeSlice(txIn.issuance.assetEntropy); - tBufferWriter.writeSlice(txIn.issuance.assetAmount); - tBufferWriter.writeSlice(txIn.issuance.tokenAmount); - } - else { - tBufferWriter.writeSlice(Buffer.from('00', 'hex')); - } - }); - return bcrypto.hash256(tBuffer); + function writeIssuances(ins, sizeIssuances) { + const size = sizeIssuances === 0 ? ins.length : sizeIssuances; + const tBuffer = Buffer.allocUnsafe(size); + const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); + ins.forEach(txIn => { + if (!types.Null(txIn.issuance)) { + tBufferWriter.writeSlice(txIn.issuance.assetBlindingNonce); + tBufferWriter.writeSlice(txIn.issuance.assetEntropy); + tBufferWriter.writeSlice(txIn.issuance.assetAmount); + tBufferWriter.writeSlice(txIn.issuance.tokenAmount); + } else { + tBufferWriter.writeSlice(Buffer.from('00', 'hex')); } - function writeOutputs(outs) { - const outsSize = outs.reduce((sum, txOut) => sum + - txOut.asset.length + - txOut.value.length + - txOut.nonce.length + - varSliceSize(txOut.script), 0); - const tBuffer = Buffer.allocUnsafe(outsSize); - const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); - outs.forEach((txOut) => { - tBufferWriter.writeSlice(txOut.asset); - tBufferWriter.writeSlice(txOut.value); - tBufferWriter.writeSlice(txOut.nonce); - tBufferWriter.writeVarSlice(txOut.script); - }); - return bcrypto.hash256(tBuffer); - } - let hashOutputs = exports.ZERO; - let hashPrevouts = exports.ZERO; - let hashSequences = exports.ZERO; - let hashIssuances = exports.ZERO; - let sizeOfIssuances = 0; - // Inputs - if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { - hashPrevouts = writeInputs(this.ins); - } - // Sequences - if (!(hashType & Transaction.SIGHASH_ANYONECANPAY) && - (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && - (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { - hashSequences = writeSequences(this.ins); - } - // Issuances - if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { - sizeOfIssuances = issuanceSize(this.ins); - hashIssuances = writeIssuances(this.ins, sizeOfIssuances); - } - // Outputs - if ((hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && - (hashType & 0x1f) !== Transaction.SIGHASH_NONE) { - hashOutputs = writeOutputs(this.outs); - } - else if ((hashType & 0x1f) === Transaction.SIGHASH_SINGLE && - inIndex < this.outs.length) { - hashOutputs = writeOutputs([this.outs[inIndex]]); - } - const input = this.ins[inIndex]; - const hasIssuance = !types.Null(input.issuance); - const bufferSize = 4 + // version - hashPrevouts.length + - hashSequences.length + - hashIssuances.length + - input.hash.length + - 4 + // input.index - varSliceSize(prevOutScript) + - value.length + - 4 + // input.sequence - hashOutputs.length + - sizeOfIssuances + - 4 + // locktime - 4; // hashType - const buffer = Buffer.allocUnsafe(bufferSize); - const bufferWriter = new bufferutils_1.BufferWriter(buffer, 0); - bufferWriter.writeUInt32(this.version); - bufferWriter.writeSlice(hashPrevouts); - bufferWriter.writeSlice(hashSequences); - bufferWriter.writeSlice(hashIssuances); - bufferWriter.writeSlice(input.hash); - bufferWriter.writeUInt32(input.index); - bufferWriter.writeVarSlice(prevOutScript); - bufferWriter.writeSlice(value); - bufferWriter.writeUInt32(input.sequence); - if (hasIssuance) { - bufferWriter.writeSlice(input.issuance.assetBlindingNonce); - bufferWriter.writeSlice(input.issuance.assetEntropy); - bufferWriter.writeSlice(input.issuance.assetAmount); - bufferWriter.writeSlice(input.issuance.tokenAmount); - } - bufferWriter.writeSlice(hashOutputs); - bufferWriter.writeUInt32(this.locktime); - bufferWriter.writeUInt32(hashType); - return bcrypto.hash256(buffer); - } - getHash(forWitness) { - // wtxid for coinbase is always 32 bytes of 0x00 - if (forWitness && this.isCoinbase()) - return Buffer.alloc(32, 0); - return bcrypto.hash256(this.__toBuffer(undefined, undefined, forWitness, true)); + }); + return bcrypto.hash256(tBuffer); } - getId() { - // transaction hash's are displayed in reverse order - return bufferutils_1.reverseBuffer(this.getHash(false)).toString('hex'); + function writeOutputs(outs) { + const outsSize = outs.reduce( + (sum, txOut) => + sum + + txOut.asset.length + + txOut.value.length + + txOut.nonce.length + + varSliceSize(txOut.script), + 0, + ); + const tBuffer = Buffer.allocUnsafe(outsSize); + const tBufferWriter = new bufferutils_1.BufferWriter(tBuffer, 0); + outs.forEach(txOut => { + tBufferWriter.writeSlice(txOut.asset); + tBufferWriter.writeSlice(txOut.value); + tBufferWriter.writeSlice(txOut.nonce); + tBufferWriter.writeVarSlice(txOut.script); + }); + return bcrypto.hash256(tBuffer); } - toBuffer(buffer, initialOffset) { - return this.__toBuffer(buffer, initialOffset, true, false); + let hashOutputs = exports.ZERO; + let hashPrevouts = exports.ZERO; + let hashSequences = exports.ZERO; + let hashIssuances = exports.ZERO; + let sizeOfIssuances = 0; + // Inputs + if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { + hashPrevouts = writeInputs(this.ins); } - toHex() { - return this.toBuffer(undefined, undefined).toString('hex'); + // Sequences + if ( + !(hashType & Transaction.SIGHASH_ANYONECANPAY) && + (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && + (hashType & 0x1f) !== Transaction.SIGHASH_NONE + ) { + hashSequences = writeSequences(this.ins); } - setInputScript(index, scriptSig) { - typeforce(types.tuple(types.Number, types.Buffer), arguments); - this.ins[index].script = scriptSig; + // Issuances + if (!(hashType & Transaction.SIGHASH_ANYONECANPAY)) { + sizeOfIssuances = issuanceSize(this.ins); + hashIssuances = writeIssuances(this.ins, sizeOfIssuances); } - setWitness(index, witness) { - typeforce(types.tuple(types.Number, [types.Buffer]), arguments); - this.ins[index].witness = witness; + // Outputs + if ( + (hashType & 0x1f) !== Transaction.SIGHASH_SINGLE && + (hashType & 0x1f) !== Transaction.SIGHASH_NONE + ) { + hashOutputs = writeOutputs(this.outs); + } else if ( + (hashType & 0x1f) === Transaction.SIGHASH_SINGLE && + inIndex < this.outs.length + ) { + hashOutputs = writeOutputs([this.outs[inIndex]]); } - setPeginWitness(index, peginWitness) { - typeforce(types.tuple(types.Number, [types.Buffer]), arguments); - this.ins[index].peginWitness = peginWitness; + const input = this.ins[inIndex]; + const hasIssuance = !types.Null(input.issuance); + const bufferSize = + 4 + // version + hashPrevouts.length + + hashSequences.length + + hashIssuances.length + + input.hash.length + + 4 + // input.index + varSliceSize(prevOutScript) + + value.length + + 4 + // input.sequence + hashOutputs.length + + sizeOfIssuances + + 4 + // locktime + 4; // hashType + const buffer = Buffer.allocUnsafe(bufferSize); + const bufferWriter = new bufferutils_1.BufferWriter(buffer, 0); + bufferWriter.writeUInt32(this.version); + bufferWriter.writeSlice(hashPrevouts); + bufferWriter.writeSlice(hashSequences); + bufferWriter.writeSlice(hashIssuances); + bufferWriter.writeSlice(input.hash); + bufferWriter.writeUInt32(input.index); + bufferWriter.writeVarSlice(prevOutScript); + bufferWriter.writeSlice(value); + bufferWriter.writeUInt32(input.sequence); + if (hasIssuance) { + bufferWriter.writeSlice(input.issuance.assetBlindingNonce); + bufferWriter.writeSlice(input.issuance.assetEntropy); + bufferWriter.writeSlice(input.issuance.assetAmount); + bufferWriter.writeSlice(input.issuance.tokenAmount); } - setInputIssuanceRangeProof(index, issuanceRangeProof) { - typeforce(types.tuple(types.Buffer), arguments); - if (this.ins[index].issuance === undefined) - throw new Error('Issuance not set for input #' + index); - this.ins[index].issuanceRangeProof = issuanceRangeProof; + bufferWriter.writeSlice(hashOutputs); + bufferWriter.writeUInt32(this.locktime); + bufferWriter.writeUInt32(hashType); + return bcrypto.hash256(buffer); + } + getHash(forWitness) { + // wtxid for coinbase is always 32 bytes of 0x00 + if (forWitness && this.isCoinbase()) return Buffer.alloc(32, 0); + return bcrypto.hash256( + this.__toBuffer(undefined, undefined, forWitness, true), + ); + } + getId() { + // transaction hash's are displayed in reverse order + return bufferutils_1.reverseBuffer(this.getHash(false)).toString('hex'); + } + toBuffer(buffer, initialOffset) { + return this.__toBuffer(buffer, initialOffset, true, false); + } + toHex() { + return this.toBuffer(undefined, undefined).toString('hex'); + } + setInputScript(index, scriptSig) { + typeforce(types.tuple(types.Number, types.Buffer), arguments); + this.ins[index].script = scriptSig; + } + setWitness(index, witness) { + typeforce(types.tuple(types.Number, [types.Buffer]), arguments); + this.ins[index].witness = witness; + } + setPeginWitness(index, peginWitness) { + typeforce(types.tuple(types.Number, [types.Buffer]), arguments); + this.ins[index].peginWitness = peginWitness; + } + setInputIssuanceRangeProof(index, issuanceRangeProof) { + typeforce(types.tuple(types.Buffer), arguments); + if (this.ins[index].issuance === undefined) + throw new Error('Issuance not set for input #' + index); + this.ins[index].issuanceRangeProof = issuanceRangeProof; + } + setInputInflationRangeProof(index, inflationRangeProof) { + typeforce(types.tuple(types.Buffer), arguments); + if (this.ins[index].issuance === undefined) + throw new Error('Issuance not set for input #' + index); + this.ins[index].inflationRangeProof = inflationRangeProof; + } + setOutputNonce(index, nonce) { + typeforce(types.tuple(types.Number, types.Buffer), arguments); + this.outs[index].nonce = nonce; + } + setOutputRangeProof(index, proof) { + typeforce(types.tuple(types.Number, types.Buffer), arguments); + this.outs[index].rangeProof = proof; + } + setOutputSurjectionProof(index, proof) { + typeforce(types.tuple(types.Number, types.Buffer), arguments); + this.outs[index].surjectionProof = proof; + } + __byteLength(_ALLOW_WITNESS, forSignature) { + const extraByte = forSignature ? 0 : 1; + let size = + 8 + + extraByte + + varuint.encodingLength(this.ins.length) + + varuint.encodingLength(this.outs.length); + for (const txIn of this.ins) { + size += 40 + varSliceSize(txIn.script); + if (txIn.issuance) { + size += + 64 + + txIn.issuance.assetAmount.length + + txIn.issuance.tokenAmount.length; + } } - setInputInflationRangeProof(index, inflationRangeProof) { - typeforce(types.tuple(types.Buffer), arguments); - if (this.ins[index].issuance === undefined) - throw new Error('Issuance not set for input #' + index); - this.ins[index].inflationRangeProof = inflationRangeProof; + for (const txOut of this.outs) { + size += + txOut.asset.length + + txOut.value.length + + txOut.nonce.length + + varSliceSize(txOut.script); } - setOutputNonce(index, nonce) { - typeforce(types.tuple(types.Number, types.Buffer), arguments); - this.outs[index].nonce = nonce; - } - setOutputRangeProof(index, proof) { - typeforce(types.tuple(types.Number, types.Buffer), arguments); - this.outs[index].rangeProof = proof; - } - setOutputSurjectionProof(index, proof) { - typeforce(types.tuple(types.Number, types.Buffer), arguments); - this.outs[index].surjectionProof = proof; - } - __byteLength(_ALLOW_WITNESS, forSignature) { - const extraByte = forSignature ? 0 : 1; - let size = 8 + - extraByte + - varuint.encodingLength(this.ins.length) + - varuint.encodingLength(this.outs.length); - for (const txIn of this.ins) { - size += 40 + varSliceSize(txIn.script); - if (txIn.issuance) { - size += - 64 + - txIn.issuance.assetAmount.length + - txIn.issuance.tokenAmount.length; - } - } - for (const txOut of this.outs) { - size += - txOut.asset.length + - txOut.value.length + - txOut.nonce.length + - varSliceSize(txOut.script); + if (_ALLOW_WITNESS && this.hasWitnesses()) { + for (const txIn of this.ins) { + size += varSliceSize(txIn.issuanceRangeProof); + size += varSliceSize(txIn.inflationRangeProof); + size += varuint.encodingLength(txIn.witness.length); + for (const wit of txIn.witness) { + size += varSliceSize(wit); } - if (_ALLOW_WITNESS && this.hasWitnesses()) { - for (const txIn of this.ins) { - size += varSliceSize(txIn.issuanceRangeProof); - size += varSliceSize(txIn.inflationRangeProof); - size += varuint.encodingLength(txIn.witness.length); - for (const wit of txIn.witness) { - size += varSliceSize(wit); - } - size += varuint.encodingLength((txIn.peginWitness || []).length); - for (const wit of txIn.peginWitness || []) { - size += varSliceSize(wit); - } - } - for (const txOut of this.outs) { - size += varSliceSize(txOut.surjectionProof); - size += varSliceSize(txOut.rangeProof); - } + size += varuint.encodingLength((txIn.peginWitness || []).length); + for (const wit of txIn.peginWitness || []) { + size += varSliceSize(wit); } - return size; + } + for (const txOut of this.outs) { + size += varSliceSize(txOut.surjectionProof); + size += varSliceSize(txOut.rangeProof); + } } - __toBuffer(buffer, initialOffset, _ALLOW_WITNESS, forceZeroFlag, forSignature) { - if (!buffer) - buffer = Buffer.allocUnsafe(this.__byteLength(_ALLOW_WITNESS, forSignature)); - const bufferWriter = new bufferutils_1.BufferWriter(buffer, initialOffset || 0); - bufferWriter.writeInt32(this.version); - const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); - if (!forSignature) { - let value = Transaction.ADVANCED_TRANSACTION_MARKER; - if (hasWitnesses && !forceZeroFlag) { - value = Transaction.ADVANCED_TRANSACTION_FLAG; - } - bufferWriter.writeUInt8(value); - } - bufferWriter.writeVarInt(this.ins.length); - this.ins.forEach(txIn => { - bufferWriter.writeSlice(txIn.hash); - let prevIndex = txIn.index; - if (txIn.issuance) { - prevIndex = (prevIndex | OUTPOINT_ISSUANCE_FLAG) >>> 0; - } - if (txIn.isPegin) { - prevIndex = (prevIndex | OUTPOINT_PEGIN_FLAG) >>> 0; - } - bufferWriter.writeUInt32(prevIndex); - bufferWriter.writeVarSlice(txIn.script); - bufferWriter.writeUInt32(txIn.sequence); - if (txIn.issuance) { - bufferWriter.writeSlice(txIn.issuance.assetBlindingNonce); - bufferWriter.writeSlice(txIn.issuance.assetEntropy); - bufferWriter.writeSlice(txIn.issuance.assetAmount); - bufferWriter.writeSlice(txIn.issuance.tokenAmount); - } - }); - bufferWriter.writeVarInt(this.outs.length); - this.outs.forEach(txOut => { - // if we are serializing a confidential output for producing a signature, - // we must exclude the confidential value from the serialization and - // use the satoshi 0 value instead, as done for typical bitcoin witness signatures. - const val = forSignature && hasWitnesses ? Buffer.alloc(0) : txOut.value; - bufferWriter.writeSlice(txOut.asset); - bufferWriter.writeSlice(val); - bufferWriter.writeSlice(txOut.nonce); - if (forSignature && hasWitnesses) - bufferWriter.writeUInt64(0); - bufferWriter.writeVarSlice(txOut.script); - }); - bufferWriter.writeUInt32(this.locktime); - if (!forSignature && hasWitnesses) { - this.ins.forEach((input) => { - bufferWriter.writeConfidentialInFields(input); - }); - this.outs.forEach((output) => { - bufferWriter.writeConfidentialOutFields(output); - }); - } - // avoid slicing unless necessary - if (initialOffset !== undefined) - return buffer.slice(initialOffset, bufferWriter.offset); - return buffer; + return size; + } + __toBuffer( + buffer, + initialOffset, + _ALLOW_WITNESS, + forceZeroFlag, + forSignature, + ) { + if (!buffer) + buffer = Buffer.allocUnsafe( + this.__byteLength(_ALLOW_WITNESS, forSignature), + ); + const bufferWriter = new bufferutils_1.BufferWriter( + buffer, + initialOffset || 0, + ); + bufferWriter.writeInt32(this.version); + const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses(); + if (!forSignature) { + let value = Transaction.ADVANCED_TRANSACTION_MARKER; + if (hasWitnesses && !forceZeroFlag) { + value = Transaction.ADVANCED_TRANSACTION_FLAG; + } + bufferWriter.writeUInt8(value); + } + bufferWriter.writeVarInt(this.ins.length); + this.ins.forEach(txIn => { + bufferWriter.writeSlice(txIn.hash); + let prevIndex = txIn.index; + if (txIn.issuance) { + prevIndex = (prevIndex | OUTPOINT_ISSUANCE_FLAG) >>> 0; + } + if (txIn.isPegin) { + prevIndex = (prevIndex | OUTPOINT_PEGIN_FLAG) >>> 0; + } + bufferWriter.writeUInt32(prevIndex); + bufferWriter.writeVarSlice(txIn.script); + bufferWriter.writeUInt32(txIn.sequence); + if (txIn.issuance) { + bufferWriter.writeSlice(txIn.issuance.assetBlindingNonce); + bufferWriter.writeSlice(txIn.issuance.assetEntropy); + bufferWriter.writeSlice(txIn.issuance.assetAmount); + bufferWriter.writeSlice(txIn.issuance.tokenAmount); + } + }); + bufferWriter.writeVarInt(this.outs.length); + this.outs.forEach(txOut => { + // if we are serializing a confidential output for producing a signature, + // we must exclude the confidential value from the serialization and + // use the satoshi 0 value instead, as done for typical bitcoin witness signatures. + const val = forSignature && hasWitnesses ? Buffer.alloc(0) : txOut.value; + bufferWriter.writeSlice(txOut.asset); + bufferWriter.writeSlice(val); + bufferWriter.writeSlice(txOut.nonce); + if (forSignature && hasWitnesses) bufferWriter.writeUInt64(0); + bufferWriter.writeVarSlice(txOut.script); + }); + bufferWriter.writeUInt32(this.locktime); + if (!forSignature && hasWitnesses) { + this.ins.forEach(input => { + bufferWriter.writeConfidentialInFields(input); + }); + this.outs.forEach(output => { + bufferWriter.writeConfidentialOutFields(output); + }); } + // avoid slicing unless necessary + if (initialOffset !== undefined) + return buffer.slice(initialOffset, bufferWriter.offset); + return buffer; + } } Transaction.DEFAULT_SEQUENCE = 0xffffffff; Transaction.SIGHASH_ALL = 0x01; diff --git a/src/types.js b/src/types.js index 2b70dd270..ec87eeabf 100644 --- a/src/types.js +++ b/src/types.js @@ -1,43 +1,45 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +'use strict'; +Object.defineProperty(exports, '__esModule', { value: true }); const typeforce = require('typeforce'); const UINT31_MAX = Math.pow(2, 31) - 1; function UInt31(value) { - return typeforce.UInt32(value) && value <= UINT31_MAX; + return typeforce.UInt32(value) && value <= UINT31_MAX; } exports.UInt31 = UInt31; function BIP32Path(value) { - return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); + return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); } exports.BIP32Path = BIP32Path; BIP32Path.toJSON = () => { - return 'BIP32 derivation path'; + return 'BIP32 derivation path'; }; function Signer(obj) { - return ((typeforce.Buffer(obj.publicKey) || - typeof obj.getPublicKey === 'function') && - typeof obj.sign === 'function'); + return ( + (typeforce.Buffer(obj.publicKey) || + typeof obj.getPublicKey === 'function') && + typeof obj.sign === 'function' + ); } exports.Signer = Signer; const SATOSHI_MAX = 21 * 1e14; function Satoshi(value) { - return typeforce.UInt53(value) && value <= SATOSHI_MAX; + return typeforce.UInt53(value) && value <= SATOSHI_MAX; } exports.Satoshi = Satoshi; // external dependent types exports.ECPoint = typeforce.quacksLike('Point'); // exposed, external API exports.Network = typeforce.compile({ - messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), - bip32: { - public: typeforce.UInt32, - private: typeforce.UInt32, - }, - pubKeyHash: typeforce.UInt8, - scriptHash: typeforce.UInt8, - wif: typeforce.UInt8, - assetHash: typeforce.String, - confidentialPrefix: typeforce.UInt8, + messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), + bip32: { + public: typeforce.UInt32, + private: typeforce.UInt32, + }, + pubKeyHash: typeforce.UInt8, + scriptHash: typeforce.UInt8, + wif: typeforce.UInt8, + assetHash: typeforce.String, + confidentialPrefix: typeforce.UInt8, }); exports.Buffer256bit = typeforce.BufferN(32); exports.Hash160bit = typeforce.BufferN(20); From 8b8330646c7ab9ec6f2bf8d05572a7320b40be6c Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 23 Sep 2021 22:55:34 -0700 Subject: [PATCH 06/14] include dynafed block tests from rust-elements --- test/block.spec.ts | 19 +++++++++-- test/fixtures/block_deserialize.json | 51 ++++++++++++++++------------ 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/test/block.spec.ts b/test/block.spec.ts index 99ab4cee6..e88769417 100644 --- a/test/block.spec.ts +++ b/test/block.spec.ts @@ -1,9 +1,24 @@ +import * as assert from 'assert'; import { describe } from 'mocha'; import * as fixtures from './fixtures/block_deserialize.json'; -import * as block from '../ts_src/block'; +import { Block } from '../ts_src/block'; describe('block deserialization ', () => { fixtures.test.forEach(f => { - block.Block.fromBuffer(Buffer.from(f.hex, 'hex')); + it(f.name, () => { + let block = Block.fromBuffer(Buffer.from(f.hex, 'hex')); + + if (f.name.includes('compact current')) { + assert.strictEqual(block.getHash().toString('hex'), f.hash); + assert.strictEqual(block.version, parseInt(f.version || "", 16)); + assert.strictEqual(block.currentSignBlockWitnessLimit, f.signBlockWitnessLimit); + } + + if (f.name.includes('full current')) { + assert.strictEqual(block.getHash().toString('hex'), f.hash); + } + + assert.strictEqual(block.transactions?.length, f.numOfTx); + }); }); }); diff --git a/test/fixtures/block_deserialize.json b/test/fixtures/block_deserialize.json index ebb805e80..35b072ad9 100644 --- a/test/fixtures/block_deserialize.json +++ b/test/fixtures/block_deserialize.json @@ -1,24 +1,33 @@ { "test":[ - { - "name": "proof - no dynamic federations", - "numOfTx": "1", - "hex": "0000002069de100c1bae40e1cf8819bd18282e4ca370f62123c8ea2c60836984ba052270ee0cb6e5458591ac157ad414a111db4d34cedffc22e096291f7b4b3c8de3f69f8d53815b03000000695221031c25c60ef342990d9bf75425c1dc2392b5e206268d9d35044b731735db230c38210319c5a32a8ae698aaf1246784f54231d8d20f81b91c31353214538b827d718c8d210399d55e0a7fb30281da074dfbbb2654cacc2d03289ba79feae702ad6dbb542aab53ae9000463044022029bbe179c2f0d8e6d1576869cea19ef439d0e52373f7efab77cd6ccb551b29f6022042baa3c17fccfb265ee878059b6cb85d40b976a30495c6ca14b7ffe6d1d87572473045022100da88bb6fa1ecf3060ad7c8347eaa1a7ef8c9ae27a8b0136cff909994ca409f9e022068ddf3090bde1e04deda04f762eb35858d7dfc17e156bfc1c8131ca07a349dda010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03530101ffffffff02018dc25a055e773e7e91d4678053ebc702cce47f07b29f3ebd7c4b34cd30fb240201000000000000000000016a018dc25a055e773e7e91d4678053ebc702cce47f07b29f3ebd7c4b34cd30fb240201000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000" - }, - { - "name": "dynamic federation - full current and proposed", - "numOfTx": "1", - "hex": "000000a01ecf88cda4d9e6339109c685417c526e8316fe0d3ea058765634dcbb205d3081bd83073b1f1793154ab820c70a1fda32a0d45bb0e1f40c0c61ae03507f49c293debcc45d1400000002220020a6794de47a1612cc94c1b978d5bd1b25873f4cab0b1a76260b0b8af9ad954dc74b0100002200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260015100022200204cb40d59d6e1bbe963f3a63021b0d7d5474b87206978a1129fbffc4d1c1cf7e44b0100002200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc332600151000500483045022100ab2203a8a68d7eca8a3a0fac91e7c7802656d937535800da82f7102e1c06f7b80220576f004cb14b95178c71e36bf947bfea496b00b66c18d2eb8febf46362d50e2b0147304402202d4630887d661a50b76b7b32555fd76906ad298ce24483df42310ffbf62d451802200e0d64069e58047c271c1b4051c1ff3d1cba7d32e56abb0d8b8bc30e1bed075b01483045022100c6b196967c661c4543802a895ae731af44862e75d9e3c65b8efdd668727a34af022041ff4d67029052eb6305d25d0fc4813d21a939ff5316a12562d0c9038976f8e1016953210296db75c11ea3a292a372f6c94f5013eaeb379f701857a702f3b83f88da21be6f21021f0d8638c413ef7769cd711ce84c8f192f5a85f0fd6d8e63ddb4d2cf6740b23b2103cadff18e928133df2e670a3715c4e7a81d357de36ddaa5016628e70a3e6a452f53ae010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401140101ffffffff020137c495f58d698979ff9124e8c7455fe79b13ddb96afa25c45894eb059868a8c001000000000000000000016a0137c495f58d698979ff9124e8c7455fe79b13ddb96afa25c45894eb059868a8c001000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000" - }, - { - "name": "dynamic federation - compact", - "numOfTx": "2", - "hex": "000000a0a8d446ef9d78103b06e6a3af54a4c28c792c6fb3035dc428cb03c373cb7daf7c3ed98edf8f484508ea0f2853fd8b0204cbab1c6fc0cba260d8ea3828a4c3b3a793045e607f000000012200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc332604a000000fbee9cea00d8efdc49cfbec328537e0d7032194de6ebf3cf42e5c05bb89a08b100010151020200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04017f0101ffffffff020125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000000f0d0017a9143a5839b8f943a149f3bb3b03a5ee81a3da9fca52870125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000000000266a24aa21a9ed0d9aa5a0cab48746412b08c038ba2fa11065bb86031be7d018dade387366c289000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000020000000102435d740d05da8077191107c03adfea535c95b3d6f9723813e96ba519aee68c920000000000ffffffff02ec45f4fdf659ff3defd76eb9d47b10d1b55b126614f1dea1afd0c9a2ac62e00100000000ffffffff170bd2f4da4c56e93100816db5e309f043dfb674c2eb9197e93b8440d041809ced6b0991011c1577b61d45cda9a10e3f35cc73bee52a5d4f6791201a1868d7adf06a2002cd0fa7673a7c958611faee00167e10c19b4e6756486dfee4ae7b7bb0d7460186160014e87366855e73431f607fb0867150d44102c3cdb50b583dd77eb44d72ecf9292fc294fa5660314d3f3978ef4ecab47b157814cccca109aa48db7728b7fba6b771ada23b76707ae72f461d4fa9aa81f13382312f620f5c0365433e24f6f88d6a036f762b26f273521d2754af4c18243d2c5049d65b9e064c160014cabb8f8c29d62408ceb5ad6ed7f2051d7a37c5300b51e5d811890efd807ccb15075ca91f7afd53411d27834550715c8158e65baa9e09e7968e0c0cf4739001d38fa469e944b337be5dd198e1723b8d81aafcd45ef09c03c2d3af137ac29c1a149421e94a267c98bdc082ff3327aef4086f1c71f48da0a816001481ed44d4ee30a87f6a9058ad046c73fc9c800bd50bce9c8cba7372ca98f44387280e9ff6e9caffd42853d582f7d6d96b78eaac310f094c43eaea4e9ca53b67391b4233a88c5497c3f1d83f639d9b00ac7f36fe2c8bf603cd31fbf01b8b643ccd9103719fe5db1e83be1d899d24e5d064cf84b572a63c40160014a862422b441bc4183aa3ffa944e0c0be5f76c2500b36656df4793d0dcae42c0b973fd8093a0ae7ec60900b153c8728347f4eb4f44709c7a73335c9a7954d487fb067b4cd3f3ff357cc951b1dacb37eb547e4927de8b703036ca99b670049d5e60f13091781ebbde5eca3b0703edcc206ca8d7b3dcdb0eb160014c6d033db90b4cb4c422559b7373a6190d07317020a8790fc61cf11930ebd67d1ece1d2b07c96c73265a2917fbcaa18a352ae0113d008c98b3655400f77657c312aab8e9b73ae6e1fe5a8436c4f5f8bdf0c30825447ce03da6d763207deecbdfee6ac28bbbdf8b404d44b9d45509ba03dfc5cce0d9ae34516001413161df67b184053336f8cea99f1b1b3e947da6b0bf630ea2f07681eed44cc8a95d314e79c0382a629421bf002978e4b7d29999cae0890eb5d6bb65f41c8568c7b3769cb8544ca90a65fa680b88dd7f2b55adf6e862603d533562c5c239439c1e6c53fe5fe7e134cae8f2eef065fe5715736d8c43f008d16001431530ed6ec2440bc556937c0ac6d34518b47c1fc0a86381e5ef3d6376c71f8eeac777b0624b7413361e7b4b6ae70dfed9de5ea859f0937d1e95b2a2287aec9aecfe6391b1452995f5182383587403712054e4334ff810275c725bf416b8ac06b61f37ef9e49570c5ce55b45ba96e8971275b927f747157160014c12fb400eb2ff9f348675d658204c2f62b53775c0ac7354935940c984cf55cbb7adfde655c07a867c2cb380f5493bde47deac6a8520873c0cb0cfca095c250ef70932b305d3ad4d97816b0066c60780f330bfc6d32f8021da100aa93d570f7b31a15d9fc71a934bb4132cf70896a45f724bed6e607248b160014d87797df972e1d31a7c0787432804784ac717b760a776aedc329eaba2e5d5702f62692a6c3f15d5f77465bd399d00744be2a8cab590885ad2d2d4aa1b3ece05f41359a01ea3ec52829b704c68f121abefc4cb5d7bd8e02447cf7d1fe0991dbe094d39d9babd832d59bd9e21d980cb921a7deee64856b80160014f58475246ce667cc9295d0242cbd5068c7e2c0930aeaf518e3bc9f26f46d41d3e927d666e88b7d7c3e414430468b2d0318279762f90939f4aed1c77022d1c939311c4e17a8842199dba965b0c78e42da75f3a07eda0d02e829d7008faff605ef528d2b58aad449e9434329e137c12a1358038f3da49a5e160014d12f69541624478071ab05893bae4df548de27390a25dfaeb373dc501cfc9778f1cef14c0ec55564abfce6735ae0a41be88e300e4608c89ac781c84117543c8f705a7003ed8a93b25e56498a14250106b0a841827d09020b24a4065762f0407407358168af10fd74fb50d2deb2d741d1cf814e776699a9160014fa244d6e2a6b2e1508dfe241ad876a83f28d1ed90bdaa6ce1d45b50f985e569e88af94c0d0934f6db9a7e7c2c4ae67dcd460087aa40815fe8158138634ee342f40f88271b98ce1917640439c8799737cf1b3ceffe62d0370c4943847626cad849083ba85580cd12c4bb670d3ee7fec3191e3750915624516001483342e1e701df4f55957270572cad84392971a980bb029b057aa9e35c60493fb00b79bf3322255047d893082ed1172a5bd247d9d8d08e588ea93dea310a2ed345237acfc37809c4c21dfe6c3e849d5ff3b9aefaf204c025528d1f6b9682264f72066eb2e418f22da5461f12aa69d40fd8f0f04460835051600145d22dc1b9eec324a4253e018a4f0d61719c168230bee518a52be7469cf55deaf633e55a6cf071ccefa044df93c4f43d42b4c52795809375aede1fdfc85295fe6e39bd5897ecec8d586d8a28afe63238966624590300703fa0450521beea79f61727b3df7d575eb3b961af9bbb774d6afa243d3a324ebe9160014f4d1159c2886f0fa764b15a6a75874606b585aa30a82975a0005958cc493c0dfd89706fb12790111da04ee50450d91f4dc49446717089af8fb9451722119067e166dff4c808d966508d22a54bdf90944de1d727adbfc03d718d3cc4d61de108adb2ba139e2a7aeedfdffe9a5b343e0e585edeced73d3b6160014ab6cd763df0183f98957b361bccfcb15dd40a0930a5c82f223f4918e24201119f13c28bdd9a7e6d943bbfe6e009783ae05b0f8bcfe097acdac028779f2b61aaeab887e19e9550556a9b0455b5251f652e9437d58684903ce5bae43790cc39feb556869f6344ef160ecd1efaa587b6a126995267eb76703160014dfb2f3459b4c269248c2952728b9c343644e0d8f0a1eb16e831692222cc8adb883082858747e15bfd1a1cd8b9219360bf8849d0c2008a09d802d6c59d9cb323bec20fa1238a12311fc48ff256943e83bc0cd505c5f7602b57fb3f6232149a092a366eb191fe9b80ed1e79d0a3fda36e5c0feb1078557ce160014bfa4fbe3b29891ee0a6e94e9fc0c7b37cda266160b5e1006780f13ca2ab1769fc635770d64bcbe176417d2a8cab5b3e679b5067f010924826fdc539e012defe2f4e6e2ee6a667bc4bda7719fd329d1f5a8b2fa4cda4102896f8c6419f2ed3bb0bbf94a58535256805027bd51837ff8feddbfada6e74d85160014f85c21de703e15c876d68ee9a67d9ca419b5e96c0aab711436add19692214c677f090e1b8fdeed25b80d9197f58adf0f2b412c9b8a09d4dfe47cf3ac740e72e4251ff97536ef9e40550d61e038d329a608dc5094aa2f03ee31826e195e25a3c3f1118ba3a10a9ee3d09d236fe68d30d959547c160a31cd16001477ae933131eacec4fe0ba9958662b417922e6bd80b60c9271ad044d1aa9c359d18315e2e033955a4b56fc9ddfee3bf7b4776975e430833a7826f19733433b1c7aa9735cfaf30cd0dbe3224198309e03681f677b3643f025f3a3e991d3e9aa59f079840f7b1ba7909b4f863ad9a1e962aee01496667ca7c160014a36286c52af6bdff02be57dad8715cd5449fd7a00a755e63cd17960e49e02b46934ee3ee45733e4e23ecebf6923bdadd359c16993b084b81f2940c1ce0d93f95b575a8a16f5e718f0b863be09bfd007af521fa14409c039695ff3a30f6ac7dbd0718d2037997c526aa7ae198c67c3ed269c022c908872516001476a5b8d27ca4ee40cd14871281758d8e37d9930e0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000000f0d000000000000000002473044022040c7b3da6d6ec5894ddafd85470f5e85b3dd02d4c393c431fe031f789e25c36c022078ab40ac50e4d7a7aeb994cf5bd7d06b325d8522924564ccc5527d8022df6b50012102af61fac2926ebba4cd36c4ea45d331ef7fa4657535dccdfaeec5cc390519be760000000247304402203cb4746196b5b7c60cec2f0a5ab49c6ed0560fc4a0edb4157fbf8f3dcade58cb02203d50220fa7b2c5ba2e89a0a17c605b33f843db5a320390b542e964439b4effd5012102af61fac2926ebba4cd36c4ea45d331ef7fa4657535dccdfaeec5cc390519be760063020003bba889c0e60d2325d6d0397dc7a9c54b8a0a0f8ff83feced8d751af47f99f52d2c7b345204abb4367568db7c0fdf03278d7a5b82189075d407034cac01c38669e3e4a851a2d70f77c2b405ad2884276b3a000463225343dba3c606a38c9322b3fd4e106033000000000000000193bc460154014f71a82b5580857a427077812dbc4e3b34fce7341e4fac57cfccb52ced248d69d082ad16bec684efbd100e783639648d23cf18def64032ca0b6d328a4c0f88a6881adc8f6c09e57c760b252ab8dafc97fdcd8671b971649c5075db84769fd049fb0f5e64cc3795ec52bb0dc8f5bcb30407fafaedb21908f5a0bc70f443f1af9ffe2f5c342d6a746e72c4edf1e5098d06caf941166e9bf86ca5307bfbbed1202cd4b99725d57dbf154f5feff0ae58d4a3cf89dc64edfb429da48d3a274fc85d78277340653f597ab1a00280dab98a58987d7203efff3fe7e395aeb870998297fcc75b119ef07eb033208637175ce6368f56d58825a60f6eb7a105a6a04bde2fa1251759870257b1e63a3e7911f761fbe5d1a9a9066bacdb59f208cd9e888e84afd1c4533615cd9a41b6337ba89391c4d5e93d0a7917952d879a9812d2bc329a81a826bd38824e66760e75599fb9f3ece2d5ccc47da6784c3f325b4faa31bf57efb3d304dbc6414f5d284a120471894e76a013eb9fd815e436b6bced0ee6faba3303d453794b3766ae672e0ee830e27b0492b61af735474bfa67311327b0a1815470882c11cf0e66f48f9f793e28f3239ee1932eb6c75b6c55a26cc9bba9f09362bd9b2030a1214ca18ac38e0e495912a4d22a3f12a45fffd6040c846d3831ead56090b16fec7662deee6ef6d3f5834234a28576b46947e3031a483b67ffe71c9f3501f6eb781940080d10d68dba086b054cb55d62641f44fa66f9aedd633d69eee14b6d76e35e0111b3fa4840f273ebafe6a5642b62ed76c7e02837771c9e444d5d8b3fe0b7fc86a56ae871600b67010f026cf4ffc64f311b798a4bb2441b1b40bc3234c8b967cabfced42c26ab45dd4b1656dc76e1edcad389457f893d3a10f22069f65a8597256ae0508483dd3ce1e61ad0957be3a1018fdee6d30fa5d89a5fd6f6de7f332d3756f9945cfcefc010ef7db2810324d7f3aba83b6f7d97d773957978a861b3fb4ead6a1d0b1f493c68188840152c8739075185aa37400b7f2a0b0b621ab8717a5b4593ac0ee087525aa8babec8667db69d082c48f64191dd343d259334b29941a399e293c829cdd77bbaeccd9d4d73c9306ddb831a582a933de1d16743de7ca5bc504072fd117f88c37da8f0b7ba606d35027f839ba5b62ac1141d271098a6a58258741e4ff834b82d7e95e7e6c324bbd6d1e2ba3ceba989918d8be280c3f39bbc1553f8bad4b37fb9fd1e29c556703c46e872a747e0705d505c7b0b2a3b57f38a65850529d31fc1df6d2a6f99511fee18b816bdfdde485d06301b85929b0e0d412704827ff3af7e9464bf1ab5a3de312f5c9a69f484710b0da7d9054bf47c556524f0e3f21a5ae6211a0b1166df9390f4a48da52bdbb2f4ace85481663ea5e615eda4c014d32c8dd5bd1cca6447c0d0a76af7a25ff320060be5141c626fc50a556dadb5a3f93067746a89525fd0a07b058887360cfab4a7f055766cc2fae9d6b59506ef854910ab79ed6626ad198b416c13ce16f42ce1ddddca653433d70e80b0af02176b37f2af0ad1c5cb23f284ac99598088af50e6ca22ac45f65ceb06aadf11aab3a41788ef5499626b249041b49bd5ca71b0b17006d7df17a3e1ce14abc863acd1219d6642bedbc375866f94417c0072ec31b7697659a61e6601fe206248aa660b60a02368bf421fb938d2642f86cb927d5a0dfedc4d99cd42e1c439f7cf21d15f93538cd2ae1df7d06cbe2fafccf134a9d3e12afc70b35424d67aae7918b39e6f0807f4046c579f1d5c1cbea6ad7719e8a62094d12a3c5fb4515c8dace908813f042f34fd7da1a7b6bcca9f8222691e784ac84b20441044cd73ca3435ae641a359cc58b3f243b6af452cbaf27be20e27b2e2233a5fe7a29f7636d66942ccb787d40867a8f4d1e6d86e120da2fa23cd0cdd96bb771ee71d572039bd1ea4b8eed49c0e445dbc0b592de2872b49b35dc94e0f158f5368e236f9a7fdf147aa8fd8e1b043079de997fc61e8ef1e6304a3f95fbe620ddbe87bf9e9dbfdba48f74c13c32da8759192651acdc46e7dc36a5dfd4d0b12c4006271bef80fae4a47d737e3dcabe83ee5331353c9a7ce8b2327cb27b6bd46e52ef70c3bf53571ca0947a668d56fb5583bb8c0f2e19906bea1c3902816902f50dd785d5b86d35829c197538bd485905f9c330c4b14ddc9af439d4ffa759a89504ef3cf49223b5c975bcb1ca3762f2349c831b76560406d636e8ac520607fea2c32e6fe47ca8811259ec5245d79851320a9de582bcb4f22c33f30e038568f7b5b6addb0242245f1da5af0889c756b47d3abe7f7ee2082359bcc7cc555449bc7c748bcfc3ceaca67cc74fd314ad91d11440e89d0501b3daf74425efcc43e51f1dbe87efb75b221900836e8cc246f5b46ede2098f35f933ea7455b6550e912cbb97263636c24913782071e60c22c4afaf0773b1f2ba7dc59abaa9a729931e9965bab9e2ec8c42424105e249e30eb890d98342a6a2750badd42111ee891d12770ca25ab900796d476eb6db6d544aaf2affb14b0b095c59558f196d20ac3bd6822efe840fe6a357fa00bdb9050b3c9fa373f3b6c5c50ba09b1054306c94cbcc97a285d0c527b801a87e90cac1bc35585f3a9442a80d467da739207e2a6049626ab91e1226cec6c80485aca844a7a28bbb5cd98e039c09dfe905735827454f0aeabdced00ec7c5cc6018c1a3c0c9744064de0493f00270e5f38b9f56522aeae54ac412345c870f30d3525168b7d1079e72d7fdc3f0ca813d1b6c96fdb56f995b8bf6f1fa525d9f6426f56452933d8902b6ff30852653414099143acd792d1cc45eb51ef8569b9260075014496d08406372cb13c146ebefce392439a8c76976c537cc5b37de42b2246696105fdfc1c9ee50206ab266c306fb9cf4577e6b481e426510fe9f66ee5c59543ffbf05f2c25387df2bc2be4940b007595f43272647e7335c32799908dd21330e45248aa870acc03bf6c4b96162917c24d816a0ed3bdd6b7588ee1ef7df35dc8bc34fb7593b18e8fd1f194dcc3df48824b4a14bba4e1c7b09fcfac005fe69408073ef60a2fd939f364e4bca50bf2a05f17a3dc3c95cc5d50a329e63fcee7918a3c6d09de75133e0aaa3d2e6ca1dcdbd470fe1c6c9c80835c86566ef0bbbdb506d3aaff97f82f98bf4b24e1bb0a6a33cb02b160eb660c048c095ca62f3c58aa15675ef3e890ac78524aacaa6c391218d684d8f013219729788562bf3310c2076b24722dab9e19e1d32c0ac33e7609775754348931db472d033cddbed270200996bb9e19cdd23408041012577113be742d1cbe55a5cea503453511689fe6ba9295dc382d72971aaeba98beb1b92f3fb7c980a72883cbc478ffcd3f36bcabc4568be77bc2bcd206d4b6f3ae3b09418f76b1267a120d67d834a94e4d0640720ea806439959701197f5f55f14c6d016420d117d778588bf46f4faf79d3ebd328c5593e20f453674b19dc476de4c1fcd68e6569bce4cb98da9fac37c9c86781d3abc76be8ec55e0dcd3efdc028027cddedabbeb22ae7a6c5490967a22fc9c2094661bd783cb23bc24903a8eba9d938425f05956a673282766854e8fdf899be5710516b234198be979ce66b6f875113f303a0374a7b2b406cbb946c65ef2fd92a3fd39a971c1b32714378190c0816abf7b9167e6232c7225d9312d5997840923c5fd32a89459617092c6f736cdade30a3fafa0558a45df1fa18131547b3a9a169d7263797a4b676a58ef2476dc30999478fcbfa97f53ead4a68dad788036d3f363de336f286a649e55acff939df54424183ea296bba152644f853320313e042231c9729c393da9d434e56f60421fd84ec962ac18e19e28163481265d98a60ab4b80fdbbe9e902d12d99769c10f45d8caa6c4ff0975e85dca036d1ba9bb39e48fc6044665cd9cc05b033e4974bd39db091ab9206224bc0dcca391780b3e9bb7c156a1ecfe016e857ed224fc6240737c161b08db2e43a819aca0d5a3d2a7fd01a4e8bc65a8e4bfc7027b603ff83f92fee62b2e91e3188b2f6e955e4c97bd305312c1559228ee9f4760071b32d2379abb95370e2158f11a44c68fc100aff2a3ea8d9b754b178c9b57424d54d285c9e85a3764e42e5d343ac3fac1038e1df7eeedaf649a9febd0d2ec04d01346d6cd2cb1d7943da9c9a27d88331665dd5483bdd469e814abc1677ea7841bdf0712fe40121c30581b482f0c939673038f14509955cefca32f6c9372bdd53beac180ee0a13622ca1a58eb18eb7e40fcd34ee9649451874dd928177016c7b0bfc6ec2436dac8e73f76f09814e280ccc7fb6f1da0cfbfecc7edfbef24694b5285c94bd750e806e1181237295f84f0ebc1e5b86bdf1e17e8f528e0253fe85c7547743125a6eeeb93e99174efc133d359b4fcf94ff121e804a08b565ed36a2cc2dbd3ada4744bb239e5d812e9286a4c130ba45c9226a0adf567de058d3a86c0c3fba1affa16ee05a4b772991ab24f80780bae20ff17b827b758e13a08d3324f244eed723b9551140dbc38218d686014399bea0e36471f59c70af1004ea4d7aaa1b49933f8f22ddd583a6d6bfef572c1993f5a0629ddea7a3cce5c7b0f232133eb929f96f48161c259438e22cd94fd3c97aef4a1bb64937cb86263f3db8691a82eea7aecb9995c0cddfe7fa5c756de22ce8f0c92f9f8bf54b398e99c0d9c8d1fe16c1a8d07be6b5b9d50f557d9cf269b14e2621e6719612678f497d2bebb267bd178aa3005a7cd6baa7cc22f65c512f2beb9564cbbdd192da490127598b4856348508592dd4152dd45fa258b323692c0782db079d693ad6bf78109735d83623d0cb5a1b4d16b9bdf69c82f67256da4042a3294a1ea0520dd4ee0135aea6be49a654f92498fc3fd9691a5c51fcf977df372c4b42c3d673afaf541b8260211af8a7303d09fc3f12057f46d098e62dd73ec48a81ea2c91efc7ead0212cf58817d9a1b24ca6b9e2c44c74dae79650018e4ffefd7f40cdb6d8d2e61024ca593159fa7cdccdcc0aa0cca2a3aee744e36d41c9850baa247e7c4f0eed69930a16a5b4e5a9eaa7b5484475a43b156f32a392c01dcce86dd9294bb3e0dff789086854836bc409548ce7c6682316531dad1cbb2dc07ffca94ddaaa928ed23e4bd7d82ed47e0f3941d0d2f48f642bab95bd64d7ced8e32b632e30c149b6539fac6052b287de43ed93517f069a821fe3d45e3968778f8b290ca88dcceb73e780931a05fb8f4d79b3a43dc5aec5c8e49dc1a4a6b9f96147f3d0883878dfe3339f79588c2edd8661c4a5b2e6f3760349fd050ec07a8ed80f5147ebba3b195abcd1032a38af87dea5dd5b56d7c7e16296e8f2194a37546356faa35c63730dbee54dee93bb2c80062b2844e0098cc2331c81ceae6318651b8ed771412f91a911b1c2433f00f828d3e348b98c6d38a587e6315d26630e093d81e7dabcdfbeb067e5c4f59b35d8a414c6e82c46d38c11e6b308be3fbd90762143c0b51f4e70b493e5a1207eb17b321a2540abdac8736ac0156d982c01a272529c3dc7f995d1a65c50f14c47dc96795ffb9953bb4f83c0b129aac0846cc818b83a330f695b085eddbb6126a6e6cc34643ba753647c9038388964cedc7848288f308aecc8a97010c225c8ee0bd01303c3a7688c375a5e5554f091bccf253b9b840a3a1f97bdeaf6c116a29a6a22341cd7c8ec0ea6d97f0f23cc0259214f8314d53d026329403db82706fd93713d8b939fde26e522a438efc4b51460f3c9f0037c768a8b0184203bf898ca4868058d5e7b16d5333d61dc796aa5eba4411a8c37a7426383eed51f5fac1267f681bbeb47c3e090b9527b1693357d9799d62958e99cb195637961b541d163020003ac32943f81b2fc8ac10a3dd14b5fda4888db17b3596469d594bfb1276b3d532ee124e760ab1b15d0e97eecea75489ec44d6a359c88b0cf47fad1bf57ab5f3cad9d1e40431e9e2ea9f0460a398beffe1983d3b0a16a4934b3da454e23b7580eebfd4e1060330000000000000001c66d3d0111274fd376ec11eef8dcc5f563889b47c3681570f0ef0d5922f2c01e6d4c3340906030c00cce6a6ac942ca8b4849bda4c1878ec252e4c1a0b20fbc52f4a6a054ab164d760bc6670d0596f7b5cc17ba4cd8fa3121a8f7b60722ab47bfe6809de08a7c7d2926b85c3c8e23b9340f22961147641bdacb6b84c3234a4c2e2dc7b9f79daa15db3b16b31c509aa4d1cdddf9d91f59e6f56f1bf9c94b0bfe7dd69216875d0869aa1e20f39ee282ec8b35f2dd39d7715eab2898b069d268a4b8142d225322cf5e728670d8a5e72da28b866526a972e9f611a6477e3b2b82b679e432f995e32a2fbf334d0dc84a19f3869f3c9da8ece3add129e54f6dd97f4c1750c855ff5c9f888e3a1eda42b4612a2cdd5fc155f9d8053cdf14571248f958f7abb859d7c7dccb793349a31d715479a178d5420c536f5f0d8f0b8982a2192f3fe1948843595471aa8dbdc42cf0a5b4a590f01e6b7375e993fdd62d1ea8099a22280462a9a948e0a84cc39c6ce267dae30047ad54d14f0652263500b659743692acda715c9c66e8d2e302236a5062f110d88465f2ae1f7a38099f022396636c68511561eccb9c3422a1386b664c89576c0a5c115e28745630d95c2ecaf27b9e144802cec6297234a02a837985a093a583d96fc519916c6d670aa60809b5a486ca069a448051134ba9eaa6c3f56a8313e8fd31ec0e6b6753b1b58f4a374ecb79f228d953b29460ff398efa5eb5c1edb8d466be4ba784c213e1100a47df4d51ee8497a20cb05c7af7c18e881d11339c884675b8f5018487ea430871a55e3c42945282190df7a0b71bc671623a13cb233a5f66b451f945ba6200436486ed47c9a948ceccf47a9ce7f8783188ca57256a001f544dc02ef9873c53735dc5c92ca08189021424df9fffbef41b37c714554846d9898e0595c7ecdca8e9397c0638638cf0200fb38479d8f34355a1877f67ca89dfd0203647ebe195eaff1c05706fab30a83f9836e3c86825b0aeb2886fa95072c4087acddff3b5cbe824047c3761c394856694dbe96a700d709d8c6ae97ef63f1c37002fd5d9e1e1000ab94461705c96039eaa00faefda4d79586822b105022eb04d1a7aa74fbe77f567542711cda977d26181c4a7ca056fb6fd52ff54f3b179c52c58c367407d07b0efc5f30ed73254fca7acd7720ef2b7c4282d11cb585e90f8e83b1da45666162e9dc5cd49e6d7221b99f9ab6de7104b3c5688d16bcd2cc01211bc19176a50e420e60d4c4899fb0f995770a730c1b1e0ac67bd748f0511eb63a8e104f2d90afff4e607f1902496d5fbbae0583719b75c3d40af055bd5048a6751dc8bf931d459e34da1b6bad87b1c461e23357321546e9c6f41fe5acaee56fcf2dc2bdfc6af8164956107c856b9c85927e8d237cfcade765481772291c213a73cf12957a61418566eb6efc77e82f6881a7997c8f44eae0883dc7cde0dd5a9f9086020c65d867e423ecc6e9bbef0f32de4e479c45d298c74c4f4abf5bf67e09cdce539ec4dcf4890100fe699f1cd45a38305e7aa60fd2ce83df8a1d8f66c55fa82a4e7673ccfdbc52c4b913e830116876992c5a5929332978c7fb6ee7dddf0af24f774bda960c039ac3f9b9a703cbec4e96212c48947c2ca54b5b2806f1bbae0451d31250def5c3ec9137c8412c0f1ad09f99412401e0fd2c6f0f03f9d5388bf4345b0dd42159c934ff3b3b7055d7dffd521dcf7049dd49c0051f590cf7433496449ab2a20f8b41265f94f442bb5560fd21f9724dd5cd7d78d6624fd3812971bbd3b7fa788f3c18b1ac9c06263ede3761894e1350b64c838591639bddf627e250de63c471012180800c2015dd81ded85a0ca3db5e0d65f61901e130dc10823376dcf371609ba3a60825b620388c1d394fa82ee68c578cfde16d144ee90a068cbef6508f781c65af1df65639c372afd1539f78b471abf2812015f1dfc4f99b4e098ebd11ad6eb9f168f21c7d93e7ede29f449ab2358ab3f68de72bb2bfde5ffb3a820d1dc41d7ed5f413ffe82d510202732080f4bd19ab862b9dc8dffb418565cbe252ddbc28adcdc266177e15b1d296b2b84ffedd4bdb9e0d8332ea0105b00dd6041b72348c91053509dcf21112bada3fdc94bef2c754e5fd4d0ce3ba358898ee1dfaa71f7597764472b4f2f5f052097f6ee4b5f2545db1cc0a7e1b8c53b2f8cf8006e9e29ffa862987eb3d0ea9c9b78960f5969d76fca62d082a2995fab29a9232aae464441583607fb59adbd3240fb097a5e72275b800bd069542135d7dfce3bd60c6563f9e295492fc63f2bba61897317cada9f24190dc4ccb0e59ec4274737530727d2dcd8abb1929363f0914666ddb1f6e65a99e39499e3c6254a9b409d6ad2b89f8657a36cc74a2ed78f1b73832f979c95cfd56376a54f3d4746e886b04df7e70f41bc2f06f42b75ee19a2956ed2b4564a71dcf5f1a313a2590d3ad695975f5c66f73caa8facb58ad8c8d7685717c40034b262656e561574041950b192d6983a7ff8d1da4e516e7b48887a4e6a1431c52cf76af438413eefa3ce8ffec357c1ef6c1714bcfcb9e923d0bdf4ba960fb44bbd30fa2148cbc42ce8f5b1d37b37b7fa3a7ac4119432412a00f3dfef0a49f8cf3a17fa006355ccd6fcf4e0df1e811119e5979c6865f4442557b1850aadd58ef905aaaea5519b6753d3ce6338442be33f9c6680df7001e20ab3b65e3d460e50c1a3aff0590391ff3ab9b143d4c7a9e1c99995778217b1b2ebb76e040577ef6dbd000ae38084bc8f75475f64a8e558f291238d3504a938d09887c06fad820d82201d2571b34cafff6e57e82cd7b7867ad6fbb006a2afa025b5694e01c565d8e8d22fea15a255031c8ef4ee2a97bcd8877e2aa6ea1edcac5d812a48e203181be1aec3342c39a31b7bbc09441deae93f4e13b1165f8950161b0c57b0fcb76642c45b333bf1a341321b8ad8af6789de71e7b45979b2e4a9053d8c5d5ab086e648b3ddee35a366b0b990d401deb443f4709a216dad79a1925b37323722a6ade4e72d29ba7472545f5dc851ec33ad978b28cb60dae56628022f2b69604f7acf7cbc74e4e07086554ef444c57dc0cbbca184cfccfbfdb0edfcd509052b858a457867732b647e540d565cc08170c3197026186b264c8cd874c6d813b6a1d74528540e5e44e06af65f989a0d64c7891fe0e9c728cce771f1e30979fd69a88ab11648cf90832a70daff1c8209fc823d613eb7ce43c96b68798a2d7e078f6506a6ccf622ac2f14e6a4eee5434b0e31357b231f44006de219d087eae489fcc8cccc95e70d4eacad3448ea47d67ed6170278325cc9849609a91dadda53de2b95695a95b594d41f98858f6f33d6957052852a7b0da4e6fd26efe7b7cc76222d65f95590b43c9678e74f9a8d85678f4a02726f1adcd2ffebb05c75b451f7b05d278a67a9b1b94b3e577d103d2b6175f448ecba8458f4e8c044eab8f28cace16e5eaf873ba39aec52c04888b027167a373fd87fd4b115a4f9454836c3baa5e42fb1dad784e3b122009c051d6963daf16777833f3a3f7093d2a5aa43b0ff3d451e7bbca6c46a142a0155ab9c1eda07536d56c74663811e79e1b3bd91dcdc5f7aa4a9594ffcce9c80d0b76db487c42b6cfa8fb166b712c7ed6e30340e4c641bef2f8e2a30c3faa66c751705938cbf31807bc975e6cf3383f5b60b0df510714b465467f2bf91e524cbb3362d3b226630fc6c00786659f4079fb1012dee75b67dc7133b68d951457fd9057a392fce27024ad9468ed48fa4ed48ad2dca22327915406570f5f43476befd9b16dcc167351034845dd10a0a33ea8d41358fbfce653bf52f48fb21e40a7a58640faecd90660f6cc60ebf238de847475ed5c49260265bab1fa29a7f54f635eb4aee9d062875831a4e6cc327a3c48bc0c24647cec3b1358d7b90e86ff8979726866b08f397554e213d219ca8237c8b628b956e33aedf62abd009e1242b1b10e72d06c6f4e5b85c33eb544d91d3645535b73e072d69443c906928902626d485bdbe536cfceb8e097cd6eae91b03c0d62204d819a9dc63e8418a49a41e2d987241852582adf66b66591b38b71cbe49b065cd828f7fa04ad80cc060df55470ac1d1eccda5f6a71950f76ee80ba76f5a1916547b67e0be8eb73eb2376a9a49acb25c6609b8c9365baba545d5fce0e553ed5b9a51b9b0c0764a51869b2dd5d81b895d047a5b4645a75ff48431bcb80f315cbee6e1ef8029f1ea2ce545e28328d1172cf63d13b88e92e4f5dd4e4d464ca5eee9e02ac32ff4b3b84573a77f815f209fa0406ebac822aa42abd7649e69b58ca01aa7295a09facdd19c97bef6d0b55684a962043f7d8f3ea09d4f0dca08cf2812056645d38d3360b3b69a8b1a96aa10cb3bebc694a76c8c8bf16a2ac38191192c2b53ab0b39ab722857643ef53a049ac17cc5d4128070ef9a47b6b87d47447aa4545b377ac3fe864ec5175d0befd2c1bd1587d7722cc2928cd19bc8c1ba8d23de5e41d8eea5a52e5b268b8e206040a7ef21a412270dede1d2a50ed7ab42992dc577ad0e1c0a1d143fbde2d8bf526ba0adb34f57aeb6a9101541b67b9477716bf78523e538bc241077501fc1ea35e350881ce4fffc7792ba2c2d4ec7fc6238ff9a293344019fcb1f880d2540fbb50624c29920ac905c0915213c07160f6d319e1eb8c9c6665327a2de5c8874f3d0e91aa1642c8aa91c3be9b71fedafc58466b4d939bdac0655590e97462a231f659f86a34bdd169c8a7c46eb48460cee6dc625e660b01637f66e03d7ad3fecb7b72843821fbda1aeeb7489584dce6e8eb0c4963502b6788f286dd46aff3289f008f7df69b3d83239b5da6fc99b733936a58dfe5df73b9193863b54cc3c363431bae1c0b95d5531c8a42426d1d4001c202451dde8adebb4abbf418a6120be46d5e3f73f2070629c27970606b7a223636770b8b194732e71896a0d4ba75ece1d7298dca7aef9394c951f94501e0ef4dc5c7626c87240b68c0eb82e040f45184d186f6fea8cb23ddd73ccff5687a08099fc84d5508f4f50f479a1ace06d72946c5752923e48e1de519c541ac00a353b07735ffec6c1923339f293f1bd2c22f889dbf9ee8dcabe7f7450d5b19fd759744ebc0cd6cd7bf4ce37b4b4562933137bd904faa4dbcde0142485457a0ef41a26428b9ecab3297416c1710b05f3f3780e4f5c8353f7ecb7425f38e5a9c202e3056f74e4bc1eb6921a2f3805f20dbbaa9808d7a1477a23b0b437e14a94798c3ae6c08e30c32c4a188ac09ac73781d657ca097a20887e58e4a9b3f12f38b3117561062929c4877ce67aab6e7c1bb539a419857b339ab31581559419450cfbfdd4648a0cdd2216788ea52c91bbe3d1f8d32c80e31af44abcd0fde7fcdbd134749780ee0da07a96cf17664abed925262f20e1824d84519841d4bcbdd1bea3178e4a881c3e3e05acf35af56cd41a1d845901d84cae3cb1b4a14f8e44c0cb3aab5bd6144724b15152d9e05bf7f5600405dc2e23d9056bbc5ee29d32c7f6d618bde1eaab90b7afbd63e3e52a9a6a0641f8acb1a208bfaf12a3cec0d6eef9e3d3ec179314c4fed56e3b6175851d8795c1858f5682705439345a70a89abc4bd9e19281624f19b2f3715765cdad77e10d42c1ceda826af0a92f26b7f362832047057b2e57f9fa53023d579c5f82450dafb259e04b22cbb55b9f7ff723a0371cb70cc8133337702c1f2ece56a00c8fd09717dda257c8d433de1e3d3d3d2537c56f05a5dd43b522d3759b1aa274a86c4a46a68e4a2b1c9d7f4f77a76418f912e0899d4726e2d734d31d149e49669d918940e227848c2ce5fa6c8c641a3d5a7bae74a69747ddd7f9ee4553c6fc2068b99c9860ad0bd48807d2567ee48cf1d3e5630200033b5728bed13de31673130b64fa6a6bbc1ff2b058e62e2a966719e665651e9eed9a6906db67ed59e6f84dd1470861bbf7aaa21ea13b7bdd14b0e815d0077e7add41bcddb2399bb40dc7e484aba9412c092f69f00f3a7ec97370f76bf37948a372fd4e1060330000000000000001ca760101364ffa054aea209053d33058f50f08a00c8106360630835f7582e26c2ceb7b5b7646107b2e6597600e743b20bae6be9651fd5a01399e83d5e1a1e90777ab37500ef34fa6424e8f810b36b19b07bbf20063ce75af11544dbe037d5406654732443e96c9beb1c9140110df0277202d98f85594511a3a42205834ef9cccd25d38cdd2f0a262e7f091851ee1233f7b790aca2674dd83d36ce5ced85f312981c43a02a436666c7862b9b0116116e6bdd325ef1397f25f96b915ac270ec322db5477b296d8c781b29f397f4e28294ff74933fe024d5f020de4e137f049523ed7248c734be169aa92e0e0eb5b508bab58a314f428279b310688f02459abd39c733bcd7d71e423938c39de72a9836c3e408fd04ce1996a9552eaa0b3d7572bfcf0b5b0e4d24d56b68c858434e87ede8ebbff2d82ed71b0f7b608161fc613efcb6fe79ff3110676122db523511bef3afa041d4d5c389a1a135f813b24ca90ac38e187c4b69901271e074ee961e050f48bec12c7edf531c49366f3cab6e50b9559662d093cf100f0b99b9527fbd95d93790e81723c255495008f01f1cdd10642e97239ef2d44961f766648f751f814df34fef0e2504225d4df08d50c96576d5bfcc1fdf301131ff74811dd385c4fb376034b2fc0ac7efea1aa9a51129b62520a14d29f08b3355685c5e28adc5ae20d5049e5794c2f633c8c7c494be059312f75a1f94b664899f3c2a3aa41104e31b547cd73a1a277ff85c229fa3ca91e455742189ff897de2291dfd6ff0cdc22dbb1de322bbf1836597a7c98d52358391a25c085be0c781c768114615537a17e50e347641ef865359ea2c8906c5cfa3459284813f137857c2cb757d2c42439cc69a774c73c78d52e5d039b3fe29ce0b46231956e72365360c39596e3384aa0045cd5bce628eae416471a79a033867dcbcd8112ee2cf4072e2bf3fea00dc0b53ea8e9475315d0d76de96fd25f405fe92f5f5fb5267b608ee32e4eafc4162daf6dbe85f4ff93f740fbd644cf15545e7c55115bbe152666f59ba6ae6ec7d1ccb669efd2a23282f00f660b24c75205edc802950e9157f9690c3989a4890e8bbdd886499d2a402ac07e865b250f22a6e629ca37e698b6e0b7056ab2a9963246c70d62d001c220fac5319876397d7ea37cf3e01386ac588c7878153ee5c5f38246536479426c0b3a6f24102c7519df3772de7df50f766778411cd8289e0b87f3e4f0562e3a740f14ee1eda42f27c4b8ce82bb3eae2a7be7e88672a0dca5468dafac8b1fd86e314a2e72973597b1b11005a032e15bd788491ad10e805ba99dbc19d8002d54e21bfc50972ce55eb4a54d17d89e09669e6e66922287930ae14e96941d21ef9b0f15b4c42988b6d3b47263494dddc0ab29496acad6312b8ed7db788983ba2e28383027bc67aa0ece0ab64cf36b6e26b7d62f9e157ac47a6f318b6baa553cd5f09634c5fc0bbd0b4684a7ebd85b5847037797a983dcdb07beaadb3ea1b9688be60bdae5bbb2f9f356167ce61164882a032d22204a56b38509a7241ea0d51efe3690c7cfabd0aa424c56ac7fd6c284befe349eb52d09a0e779bf16ea9ca2827d94ffc4d612b62c68a77ddd563fd08fc96bce82879772b4e1466024bb9e6c9219286ca4692be3b8e0e16a15f42fd65446e552c1a9781e9bf04fdbd788763af2aa532ece25516ec2e5176932b3eb3e36e264c5268db6e1521cdbd1125968bc5b25776dc64058f16b9290e5495efbb360cc105b57fc1f910c9795c808dd792f99e852d7e40166b09a5a776a6763861a0de0113ad54413015d68519d6b57190ba1494d8614cc44959ed35dd6d55705421a6f61844317dbe7cdcc9575ad8f251b5acc2c35237fa3b9efd3e5148ec385a7701426d3633e375d744a25f30c52a8e330e6042a7c79633b4549edd29ba81da3d889e00abc8b1aca93ef6135436eb200ffb8af3ce86d1f9389261d06e80672ce3330228070a7c8c61a9a4ab89bfb83772bb159aacd3857262dcad74d1ca129f7bc441f2b0357f693f2fe7d2ac2cb767bae0e25821c3f8c9c5babfd1b98123de712b5645af6ed7f0d3b1472f78360ef6f725e403dcb64bc9d9364b026ed0cbf7f5802fbc04e0bb656469cc69a6dff2da86720f9362fa399b350c6b736754602e8295f78fee67c3c981679d4974f2097b45bef54e3e3b009bf7c82284631ab9d672802d76ccf895b43bad8b163e6b372a9e2475a85733bdbdd4c7aa35a4ec761475914498c55cc25327ffd8a373d0caccde9eb4a357beac4671d588ac70d0ea57aae2a90681975d2270539f7dac796bcf33783cdb9211084d917ae0a08a5c29c45b355c1fc3c05c05dae13ba9a5e30a07dc7550e6e8c41d0dfeac3e768b898332739af056b615541021610a4721380abe6eb546919fcca7764c8fd18c341dcb06a6611c1b204ab2443e1531b35be395d4df6650b1d36c6ede69f482ff8685de4a1a16d1d0b42a0ac2e482138a568f3e093820f5d78afae4d8f66e83023bfab450ae579c5a478879e3db64be18ef8b60e4d2c7e5a1cee0176e549f53807d1fe471ed4598ad37a07ba11eff41a4d1ae3d96965ddc8660a9bd353fbba8e7b46789da2e8ab6e279113db33586fdd863f8f3021fd211a38fdd1a4fefaa66e66db4f5dda705ab4d8ab4e2c7819a9f4bcbfd53cc01966080febdffa375eb6da3744ac8956881c6a08b8d0420202ba7fdd7e8cada76573b5583323875ecc1d98529778a330c37274c50d26a99c4a4a127571716c4da1aa797f2e413a05f50030d509110d7501d7adae15b13e6b13ca979c693f6b1a1dad65ff05236b91800b7bbd30095da60e11a837ba7aac4a4b432bd797d3a8a832b69c09cefb3430b3a308ea4c4cff8582df8230d658def78a919228eac502e6b1b7d4f1201bb2259bf265712d8c21a9bbd12db467f42adca53c0d219a2083bfc012f8a12027e08b7925782e7d9f8e2e0b3817933956b6570b1853eed4ed59869576dff07e568383e80f5f18b0d07313c6bad67590f1768c95ef16d60e46076848be46b79add5f042c153d58d424545f24158e0e27d8d2cd5fce6b54d1e2b70a8db60c7321dfe698ff2353a3f3f400da28611a758b1ffb7308800791f6f01d1b7cb94b5ee6d04b4cfd6c84a1186139f60cc6436decbe86273bacbcb1ea132646843adf235018d1eba28360f6dc276f01f6f480e1a77ac975e8298e02f100b566224e4973c630836972f22f5977dbe6d5a2797962f75a92f83d7e489b9040cd10372921ed2b7a20bfc094b9613601c43895dd5abe32e9970dbbf7ff77923aa2f70e5b58f685845222b4791556d75f9123431a2b80d2d96d53299dabc42a9e0a38856a5ff64a4a5ef045a822e9945a10657297fdf680b0d52ca976e793397068a32a469b6ed2781655742c9c5716e1bc7f7a9aeb272411260a654f575c8a423d0ecde676df5d23090422ac0ea762bfdf62357b6ecaf7b73f93621457890ff51539b8bf36a21348e488901a9a1eb620dcda3906c0b7c9a8b194fd0d841b19c112ab52766ec88f3458e55c6a80c7a6e4de8d3d036d2e03cfdb59040a9d9e151199df4f1a2682c6b36f29d9222cd61a1b925c463b71d59a8c8e5debb22cb161ae7e3303319f3ec26cc68c821a95e671bca9fa6dcc68b541f1c02e07627ace08d34dab13a991ddfbfa26ec907780d9d1faa13b60972db886e6d33f011c7159d9902cfdb01da06088c67f5983793bbab51124f7eb44e4fd56106e9a72e1c424bfabe940a8421d694418b2943b5103416ad497e5df89445cf391393cedc393c1424e5322643139044a47371e6dc38d15a032fa0d27c2e7587b946c24aeb1882cde431b26bd409f191722177eb59329b276666a7c48796cf62c5acd7ab3442249c0d72d2ade6934cdb68b879a505a3b2f6166bb7b34b2b319587413d22a26109ae16afa6e4f690ba1763cc2797f38d2c96f6d73e6e5b4a780a4a0b24dcf8972b6c7581ce31b61b9c8a59b6e7b256af854a85c4e93b4014eeb3ed58988fa19252784b3c34bd818c6db5f42340ab99c9e4359ed5d8f7f1ae1be788c49faeed87666a9b159a09ecd776c44172195d178c98ba267d8be773e5c343ea13174d481a3a045c69b90af87bb842974f913eef7a11a0f55a63e42915420738d02cab0a4ef31e9a8af998a5ce949c45aa998c3acc91d9943ae0957275b8431bc16ad2e01e5eeefa254916b7d55fb727b561fb928a1d630d08de61c0a6d7d981370ce3e12afd35a93658fff453a9b7a16c0a83b3af152aaac3e5a928630d7e985e6c8026934a088f9a948ff442cfb0d6a136d9e2374519d38f6958784fc4dc48b224a4000d09a2153c3a0fe83803756cd5cdd97c01739e88bc1de0878f51e3364456c78cfe335a96414dfb48acda7332d641981ee55fbadfb0e47d3247d0a473c4d6a05a6f940ef2226c3cf98314abe7e261c4b1e5ae742946cc1a15779e4bbe35eb4e0c4626a9570626d618de48f7c80a729ffea702603ca198e584a832107585889b387d4a533a0585a114ddda42ae025a4a9b071700b914e85e0c26a2bfe2c2abb05e7f4f1d8e755a10daad59cdc175cf328e1856a233090589f2fd0b0826ac16b9a0243888da972cfe3c682dc08b1df47776026b099ab3690eb329dee348b81cda7204144c25e78f8d1b05dc96e94d912879df3442c647d3f9d2eeb5678c45da7e63397a31b8b562a1a3046a19dd81387c517178debd255f7a034b629336227fef6df56efebbb40e08138c053991fe62b52ce56bf51e5c2d7543188142305520e8d1b998929ddced2b71d7c2d481d0b0b6a53588955d2418c430768161bd9b883c882a1c3cf67c4d43924cca68d9d34b642f79b98bb03f96c62cfee3502aae5b216497bb9e5ee590ab5edb1401eab11265ab3e0edf4c44c6c2343d2e14172d8f0d5d4851cbf7b01092855c72e4c05dc6ff02679e4b01c325dfe12a4c0799655f59d46066b85dcf0c9cac313e10dec2b01ea3b9f53cf2bfa0101bd4c268ef58c925901c15a423eb798d7d20c652c0a62fa376902cc1c2959f6ea007a7d64407f5ca107a5205e6632e3b10cfea9cbf62c9757b96041119243050e57c7e5b964beaa74f1133e8b55500b15d8abc3ba31b963acb1bd9a7349c60db65d1449b949909f39020eac8259b0d2367d1c23c2ad29b9760c40efe1f116e0c0aee243eccab041ba3ca8d1edb36b831fc91d9029315cb6b3f9413ce2c376eb12cf507c734688146261ba78d4f001dc7c3ce3f5d7abebfd0c8e07b8c074861d8024824bf260e748580519a63397b76c4c1d61f338baa53a2b26ef49c023c36f70eaf7e6f886063b7b740b8b9db89120a553033c84a6aeccf423346757d67a91fcadcd19fd236c9f10946d163f3c838e516c9cb2818826c847e3b5899adea1d19f3d2ff3781347750220e0af12e3337bb89ae7401a91ae277c0d207be983451597a22f4233ba49975951d2bb66a5ccf4b02eb6041871d7e5e0f4385e5f4dc071203f32d3d0ad6bac14ee2860831dba2167f60945e09f381d9eea12f5046d896c88c93a0eed3db3c72185a4b9e2e49961c6511ec2d5a134e044fe57c25fac235bbfa5e2088ac6fe00c1e96febe67d1fed23494e121c78aba3ef50652bb17ca8bd92a2b05e8fb42c700206796d493bc07a92b2a5c98d80fba0799acd8ed2bb9c8c01c84c439158550cb415ab15920759a987cf47a4502bbf6da594087fb5e1031a2e37891faddbf99f2b0d621062e57f0b732614e64e4f66b3af2ee495392af93ff31ce2d606d577cc1eaa0cab834756ca286e6c1bf5a9b056b6fb425943db545b9923b1704c911bf8bdac81d02d65d2be5c6bf15037bc3bf23b4996b58b1bd8005c25412b91793a65b3a630200032bd37a0a6f2ff38470fe3d57e0ca3cbcfab9f2051325614695a62e76fed856f6482aa89c5496df94376f8ab32df34c1e953cca8d31674fe54fd87340226ae1258136999ccb814192c9429c497c49487b3273364fc80c430b525607cafd8aa10bfd4e106033000000000000000198073d01f6a37f774c29777793be0f2047d2497fadb9598333221384e9c20b90d40f4d554a96451462fab351c5efe0d383684ba200317c64c4fb6e68af11b1ea99c7a613cad205296917e5db49ffcef33ed9f837ed932686bf513bd75ed10f0b4f21f877ba8e7516ca8f005e9335fb4534ddbd80933ac2b45e1afc813ee9711ff78dff2e5270ac148e1bfb49f082663122d32dc37da06ded58f5c1c40f9a1c5eb5efa415a282fb12d9b8a79979695231ad6fd2d9e2bdb2bd1fbdcc20bb535734010e3e373875ac5796ea14a2bb60bc96d79f8913047e69301993e615b2d7db14f1cf68ef23d0f5a3dc651b13655c4f53b6b4b32471f333113df25c0563af6e81ac25ee54edde60e9de2c5182dca97cfb5f82240946d592ea34a733db513dc324c7d4bb36128e9272da71ab75f63602290a23038ddd71e50634f4cdc65fdc008c732f452ce0f66b543ef3707218d3bd42ea8a6d30ed61632330efc5f5d33e693b3c3685914110430edaf86c216cb204a8c5c32e00f5fdb4b78117e98d9909001f3ef91d7774927fcdda88237d3e21d124efc6b5e9061b65ae0577bc0e76548ed4440503012031a623029f6bc5241753d5e669a252c3ffa683719e246f9b3cd0e91b5fb7c438eb94410de040990d7a715d92ababcd3f940e2b3e5b754ea2d4e22dc535e899dd8347d886d0c23079e9675942a60b70bfed853053bf42341ad39f753786669693caacce1a4b09e0e99e92fce4dda16b5b494690bd3b4d06ff5627bc2e937eae4a9189214fdbc4a5fa2ef3a2d4b4c1b84118a8b11daa429672e316709a9ab90794cf3fb3657bba49b58c6228dae8dc12a8ada905650e2d19bed62df022d58bace41ae2bac6b803ba8b9de0ae793bb4ffd403024d51d4ca3020f094105fa85018643e1989eaaa6ae20beb60d20b459e2622458cc5e115afc695e3ca6d44be8c86c5bf6b742c8c587e64aa3b11bbbe34a3e74057ddba5531bc776a8a14a7688c82ca391f86478e8035ebc25a1abe1f61b3101a4fb25df3e7bebd1d013df6e305e15932d3e3ec789569bf8003256db34dab3e40a1cc603519f9e32fb07fe6ae448127750b0747729c194140273874d377e2c5d2018aa4b79729163f6602ae6dba3944c253861d8827038a23f87390644fb202a4e4ea95425df065d847723019e2c9df0b9ccaacf7a2e09843c196bfc4577113d85774b99f6d22dcecde92c5dad8abbc8512078a425070fc957d707ded416c0e24b4e0d19d436cd57241eb7975bc1077e2fb000d1e383f397cdeae7aa9098830f2132b5212e3af71377dae78b9bd01dd2a8f6ad52bdafdc4ea156371d58fd751b1a15f048425c073288e08cc0603a8abb8cf97e4c3a17ee0430866d2ec6890710b77b02ce14e2610ed27dcc96d123ac39d6d766ea9be303e22192bcd972ef566a93a0e76ecceeef0af07f4a52256543889a47733e29b39df1822633d0d6d7df94a69d9413e01d03f56cb72a5fd9b979f7773185b088f3f9c8e3a32a425943d8fb298aa8ec9c267dd0f54e84bac7d8a5973510beaf3f5ebb7a04148d5be5a94c9983c709382672280199ed8a981390bbc7e44a78ca7704116463035f96b3cd0575427e90a7fb1adb60b288ae17fc7532901ddacd61b589488a46268d7ab6cd35ce3384309ec9166eed9bf1c2c615d1a5c85f457cc77d8eb4cd0d62c76eb5468dacb57667312edd79b70f372a8888fed6bc938e2e03e00a2b98f1cce756d51dd1bb9a58cc9342b6a6152e5914d1fe8ed7a89f105c85a2bcc68232c37b6bb8011e48736162448f7063ffa516260792ac3dbba23680874236189d8452da864eef8670a54aad3d0e1c7639a1e2660df5ebb2d558115d7019459398400bd946fc94c55915e22050e708830127bed1a325ab42a2534cb6eb6b9caf35b3256a663b5b2e5094022ec79b69119ed05f79e21fcc0ae56b68fc96c3f44f9e25415c84ba5ca2a8ca1dae3fa5341aaa8b8654f00ee02a488e1fcbac573f728ddbe9ab52fd551b1e7c70d9031c3c83474df4a96c2ea407ae1040846f60ef067cf944fac0a02740722dacdf6bc498bdfe853a02058e18d2e9bb3cbb7dddbd5195b2d570e4b4dbdea3488d6e19f5fe4603a720c5afc73be9cd3ff627cba81e836fe460f8c3c09538898ac928c4dbcec037b6cbf55288050d35d39f998118c28954af70c13e8dd763655d94f3b21aa56fb35626cf3ec69592c3f8848356a0e074d603ebda5c6ed2d669b5bdb01f01354e614f5d847bc7abc34e32f52129481206658fb6062291c975ac1bbd82f316a3ba94d7e7361a1b950d92f7dc7ce209806450e223ccbbf2a13822abe9b1148d1b0dcc646df726f64b25b209b9ebd93fab723fa52c3bf4c35fb90ce659fb7adccc27ee12988fc818ee0e902001c5558874a17d1ec0599d93a4a3463349990589535bceeb36c79f959258e3aad63dc6682542ce6139aebec5704a3d8a706f2a727754aedd879706fc981561f6fe52172083d168735b2b06c2c8d4f502e637160db5706f7f0186ded68215840bf1e2c2340ed0fad731c522d39dfa3aa8f3ee8719b602bddb4dc7817b125474ef739f5260334ad79e9c3d0cf0fd4f4b6d3a9c3f16c0381af67378f7d51125bfc537d025d3bc04dabe91155bd7dfbaa022bbb88513cfb834ba7dcea665fc934cb5b388c6f7f671bb24abb35819f91edf4e929004629d8bc550ca4f6926d8bd5d614b9e484f5fcd74173e8ea31d773e2a01025d4f8d7fba1791d03c832a40614a75b6cf8c95f531e9f0a2eb95a5305d271cd30fc351d36ce3c032982ea4db16ab47eee42202ff28f12925be83192e5e75e61f73ecbcd41d71e3146f9cde8ebd0731d61742d17d4e01e3733d92b5d4897b2344db30030bef93c7b99a2ae0ff5755b1043e80f99534c485871c420e0d23d0a8ddfad00e55c41029de953abb18b186b85a2bd73d6b20a76562ac289fac09837a799244131dde02736112e391b2a59662cf6f86b4cd2d23b43512cb422917f3021d5079318051bccf77fa9d3061cc76cf0616aad67b15bfd16eb0f7bf4f0a41ccb97ce124009e78b94bb6130cbdc422ca7bb9a9883c66167eea8aa0f7392c792dc7dda06b956cb65fe3ae31f8def297ce0d9e1eef735b85e61253e0eb365b115fcb956218dbb09511bb396081c95e680edccc2b4a04d54283fd43291bb6d393454fa7339f7b029c67e76947d96634e4396cdadbe84730ece93ee3bf5f0eff4ac5b17c480436abc241ff7a3437a3769c2a6b3eaec08175e3c5729e541e05d2d1e1e3d6c04e95351159674fb25e7f01a57457fd0b5277f2fe7a34afb5fc5903d4828016eaf7e29cea0dc1d21926b2fbe45e32e9839a7023ecb6aaf46ee2ecbd2d76673a9531b611a7b460f2d564b4b384009268fe78178f0115def0815fc5a546f7ce326f726b33f3d53dc9fff010bbbe1b15469d4b1c0974365f9b241f8fb9d38894e4cdb2aa535cf23ffa064bf88e0b9369c5f34687ad5665d571e72671268268c5ceb688e03a1bee1c0036c3ae6a665198ef459002c2cb9cfc30e526fa7b4e2e682c1b62a43da37320fab1f630d7be8c5b35022942223c6dbeaf12738d23025d2b439e420b0967ec88fdded39727a814f83b9404d322790a361ea3b980038cae607792b9651da67a737c226371120535c2a2e2b98ba4268fbe9ccbd7f944a9d43417e409c7ad6d79440265836eb101cababf82c0f4d2362e64800a349f9d029ce969ecb341dcc3b4da07aa68941b96fbe18437a16e721a396c807aa76c1f998dc622f3dc1bac66784222dbeed23252f6299aca326516b5005853ca30473bca5eb5fd9f52750fb0e4c1ee5242b677e8fd87cf4bd9bcf0f5f719faf35490a7a8d9c7e87d4847a533470951570d500ef66b750f0adbed19b6c5fcd425edc7db7910de8f92c8e72235c3018128eb27b6d8259fc119f3227ea1e2ab7ca24aaa93d77bd5ecfff16e723cbefbfde958a35f597e4157e685fe7789c92dd4755b84103affcb4d60420b9a154de5379b344415399b183bd422cbc68e3f2a65e61759412d31ae2e4391098e64860dee3a81381226e87fda7a617dbed0a7473844d7c01fc1a3e23d13a163cde988f0a8cfd41dbd065027e4bc1037b3b6b6dd1e4e91a02b97ac6be137425e647c94d49a8f6384ec0ae89d56ace0a48a5f012758451a3b9dc1b722f1f0401db7cb91fc8b5a30d9b63fc24263533c71ff23488e823884994212e6d2cd16e4ae4d4f91c459351981bd859337dc8953c1f00702f4cfe94967bc62581d35e4622937f835ee66f971ee5a454546eaff9d90cc3de3bef208a7846aaf7b85e44f474ef51973f7881046403a87c84855fb85e37e7c243c3674b5ed5f5bcb59f80ef154c191206c893142b4ec0b87380cc9d9526a9e1940be7f7925f41babb8341308ac2d4bbfb34c893c936584d3bd2a17d3848712598d91ee5f43143e8a1715f46fef826d71c1df73891567c5d476cabee0f0dd191046562b652564a4fbb104fc128adda0a9060b5e195445f04594f9d352c61e9c7ad73d6f67078eea9d31cb3391c0c72296209acf91b10aee4ae0d7b49c40597e7c8ca9fa36da17f94d87fcf60f8857a889640b40d8682bb27ce2920718c593e3b12116f0ef50c4e7a522d41218dca1877012b4c7786f4f1381db68a28e8a1f6daf7ffe285ff40fc2b00664569f5d837cf34cd3df3bab71651e493a18539fcd83b79bbc4b61efea1fb43f6499c1314e68d09a572e84c6812094933465363afc2af22bb66dd0fa412f2ecbc1982f073896cf0c26ececc13d864b07519b214c38ea00b55253f35190e263402bd6544e224ca4613a9111787c7f88d74fb40795d132d63fce857c2ccfcd12330a1b7d3dd76a8488efb4e39b2686c1c07a5b776a1496f10de7417fb4a78bc895433ce25a0f4f05acc5e6e3b65ad5b075d8c13a7524bd7faf179c92bc2322574bb01b7b71ce1bc86be62ee1e6c2960593f131c078a153dbf64fae93b47db3c85fb57507e645ce96389f1ec38cf4df460170b1c6391d1dc79ecc0bd653d5d86d10c06aa091d43f78595ff434f8e89a2e8998c0788b6c01c4003416a6a835d452aba7dd95311a19fd925ded197ba6f19a58979b32bff5fb2f83848f1c7c0c303f2df6335a9ce1899e5a62a879d2cfdb8be1d5b0aded6eb213c32b8ec7dff63f8697316dc66610b4d25b8a789954d8c4d7a610a0f8abc83b318e679af81e46f009b3de26edda1f40164c7645a1321440f9fbbaae7f39d179d156acc0be37270a9dcd44c7bfb739d645ae6efea863e79563b01d4897b48628b5558f62850ffa0a2cdcdc074196e6f0fd124662afb87526da17f2abd9f13e4735ab359f9e47803987394c1717016f724c323df369d0348ba7e31a0c1495ed1f6fbe83ec48e35ceb3fc1b9558c497c294d7ac5bbaf4167c287f5c8246807633b17ce639562744c58d3342d86c89824dfd216ba493cc25b7ed068dc0102141b114d92d1a86810d8add066786a7dc9dfe70f473fddd0e6ade0005d52ca1cf99f28f2757db164de0bb833b63800d643196884e7cbf581a419970fe2933cba4f78e0e427e1b53be214e7ad8a513d574f012868d9ee01c483c6a4368c9c79009ff1f32ca55551ed94c56eb6963a7e2bdb059b0b4be93e801ca703726a4262ebdd57160380f881fa2a2ac5e52e5d8b446cd2aaa18e275954e8bad1ff3074abba975223668df251062fc10537d1e4feed5d8b8ae6ccab3ef33047cbe313b5a43c8879dd6965aa1c26d3d69001e21c249af6c30f66e18053bd7aae86b7785b3903228163e65d84ce96afec60cb2ce00de9a79b23d095231ba933d4106e8ba9b99d6cf8acf8cf345a2ad31b8aaac06591320bfa9a2d3be6943494f630200037be7736591b2ba8ca8471fde566706a0109b2f742d4f2c347f87fd601229f181a8df30e72c640ff779f01936e968b17cf140e3a1fced8dc69456580ded3a28e010ce0d49a80714ceb55b748eec5e31ea1a0f302b8a34bbcb60085baf562643ddfd4e10603300000000000000012f98a00000bd5d41c0d353354784d6799c94be5e1e294f8b119c8e813bca0b8aa2ce663c8ff86bbab583ea25a99c425dd50198e5122e2ee933e3122d8ce975c357acc282f64f433009fa09e000689bfb06ac837e6ec4f441984d2960a0a36a23510ba3f3b925f3da8b36b0e270dadc8c18e3ca81fe4cd782a99087d34ff3dc4b89dba133915f7626d1bf1ddc2e9ab4dcc93dbfa43658e20badbab6d2ed8f46ff667ea549c46b5b15e244b513466c361d511f612c45486d5c0a80c3190c39c27b5f42782063c0eae70f0009d0e181a603343c858381e7bdabd4ba4a56fb8a0b9c8942663eee02072b09f59eef3d6ce60acaec61ec14c604875210035d4e4251e99ade701706f3521e25ed9c5c601d8e2206ebc35c8bd89ded58a3874253962a3302b2fb1b23e8d73f3c0666d43ed5c2486cafd01327ad97c36301bca5f2d0e4953dd146991c2fdf69b6d8499c2755070118529ff56974daf3718d2f630027d4f76a2cfd5fb0f2204986abf2d3b5ea823a276db1a20a00345631c014bba065c49dba21348ce9992faee928530027425e4da60e57b91fa0219af3d40e08976f83121d0704dc100ac3c4add8449c88fdc77e5d3aa787e8f156856299e935fd77744b083ecb6ee8b799c4a979f44e131a2266ab7b4210aa79145d71d5709c2ee2d016266e4d5eb6105d36bdd80b896142383a9f58bb1b6d64ff57810bab9f49ad3f3ea42ef114f55826ca6f506bafef836b781ada2465820734ba3096561d26658e95a028ce320224fa03c97fde62d6281d078cf1c6f492f5526d3b3e4dff3904c0665af2bebea47bc337a16da08c827f28e4e1b9193fe37318f17c4f941c5fc7c0ff624236398438334d3855faf876ebe81f22e5f45652bd582053daaed6ed77cf3fcbd20a13c9ba0184f56ea01fc454a679058b1125fb11068bf0066da17f1cabe7ca107af08f7c35f0244f4309a52f5506fb72e8c3b2d19131ddd63cb723588dc2588a572d1c64ab6db9e5e5143136a98749c8dc996c08502e8ed92990f5bfbe22c08e3dbb04c2000ce147c2908c5e7ed1fbab86a3c0d6efff499ac6052fa8bb038c0601c609df9daa6202c1a6a1b99ce3b149d0a6210eda32ab45aeccc8c070833f1dc0aa7bb282edbf92e1dde26c401820d5c1f0876d83507fd8f3013ad3cee46cc3c7ee8553678be58aa1593131c08d68dc8e74f8a0c2f859d40f7f1f0b61172ac32c7ef7d1c99d6110e3f36e651acdd5f5e1762dcc611c713b41212f34a746fe980506676f4c0214bcab019fee3ed65ba433ae3e1be6ccf95b507ff7ae8e2f1a3294a4a7950b77a51acd51b0ca33ae452ae5ce1f7a8b6ae096af849a0db06f645f42a110e67a0acaccf1def8732f719ad57609bd04eaa4183497be5d3b3ea16969a5d141a8676ae1e61080a406590c8b8afec30adb798a932852208e897c25c636048ca5d7016b5ea0837d005bc6ca2e740063a7f10bc8b58574e8107a14ff61684b9384e00417df16b9573eab2158b2c47e421d0743dd6b0f2ad35176c5ac2b2dac6f4fffaa24e167d7c4ace840b9657bfedcb635fa850947e0042254ca005da25a46b1b98f976a81ca9fb6c999c65e490dc425e7b255575202910477f29bd9c34d7fc364f449949d0cdd860d62bf38a255966b4b567b6e37ec02c8b2e5592e2f681e0fe9dec7f4ab1bef82e328a79824f15d1a721b5540bed11d80fe5c278cda6a69bb13e1e9f086cae88b3e529171613bd5c7d1e4ffedef6d21f523a2d9bd2983d811b71d8d4ef8f07131acace120c74ce4a1ad3f925a90817a3591dcf996f18434b189a86eae0409ee815a676bd5644395d695e76939d8df803a3a34df7288e5752898adeb42b12781387e3319bdccea75a969b6bbb2a6628a0f5c0eb67970570984e9f066aa46d19f9a56783e4656d419696c162946cd5f921b904408517faa1eeafc8928f79c0455e518292d671a38a7ddb8961999b74aca5c6f6409b54c1712bee9832b7026c73a10c7d73b1de030caa04ee2baa2bf626b9eda1f92d1f224a2b8d810ccbe1fb24a5f2d87f427b99619a89d737fc7be1090d8aa06c52ca7b593d975a8921bd596fe211720556b1ba57d0372491453eff3676240ce16bd75c28bf54dc047c126df5374760effb3bbb5c38577c5944e09b0bf47730d044666112872f8f8675aee7b67d229f0b626fbfc7f5eca429fe9cc415f390abfb0b48e54468d55062b13ef0ba01ae055e79b653a0bd399362d0d8a0a49581397ae3cf5dff95f9066b94c6e7d2197e566f82f1194c748d0b221da36c35b4b727d44f9a772c662107f4aa6af69e9e841d54109d0e8a07bebe4ef0410d4488df446723562080d398667100028713152f00a67a00ba1116522fe98c3fe5bd453570fffc274429f69298f1adfb81c0a9809f7227e609108a0486056a71616b2a9f086bc6cb4e7ea0dccd490704ca34a709cf64137c323193638cc81a36b5ca770a29d2c9ca905e60a66e87b9388ec13ae355afec3ed6759061fd6a4547ecfb8c973c1649848bb24062ab0f95a7d827635911eb5a58c598ddad0c764d7e2bee73d2d16058b44ad07d954e22ee5ac27edfcdd73745053ddd94ba1c601e9684587fbf377a9952f0721f0bcc301c7200d4ccb7eadf9b7a536e8276830854e07cee8218921afb32fbf6ef595acaeeee67552303bec592888c24635d6da179b4f1a2e60aefe24b7daa000b81a4fde748e27dfe7812d267c5e903efc0e44020fecb277dd59f4b4b035e9c68347e4afa6bc65c3d90be7dd9602956331d79a07b4e968cce34c2821cacdc1d04f31c8df196a7f1c5778fa63dad97bd93c8c77ffe6237149311cb0857d282a0607d2e8c2a22afce9d8e58e7ffdbc4ba54aa5ada945ae93116c643adf6beedd4c75afea8f497427730d0639f0607cba7cc14252c105058073fd1a4aaa1d334400ab67e8000f2b98bf51cd21a76bbee4c5c646319dd66bee51c37cc26bc2b5176d4a1e7f3a773f814b2591b0792ab16992b8140374e787ca3362b231d9b01dfa9e719831c5e13fab6de0c52414257257cb9a98b936802ed197bdf32fd218fbd7cbbefa612f3a54e91e7dcc431d63b030236cd19a26b46c6939ba741cf02199502637d80bd8867bcad952e5fa1d060b6a224048b88bd848572dea4a16848d35d9e023ae0c7c23111730f5e67199f12c623013e2517dfb39f3ad49332e326299da54e8b6ba5b6f365adf6f7764cb7fe4d09eff2189e766da92cdf5a103830d8a7ff42779ec3d5ec30f5de61c756bd17e54413c6d93af2e1170b253a95bc62cdffaac6309896f7271ee4a52df82dcd0d09fbf1b8d3d3c1ad218dffc285e229efcbc6402aadb20399533d452058c4df0dca12bc6a86af33f01d68a1aa15637f8658bd84ba5cb794f4005eba3d41db92a4c376873e8409ae9b8206843a1b0d20717cd045d3ac262d9c66f4f8ff92ee57999a9de411e48ee6b979d738b897033092f873626966ced3285f90094296a4c4c9593233d6530743feff6b1f9737fcb2b3f183c5ec4d0807da9554bf839cc2dc950161d51a697ae6734b1a0a7accaaeba53a101bbbfe0a7516661a2bbd5fc7df0fc2df04d0ab1a0a4a0f0b0663180b66ed004828fdfd0ae711e3ddc6caf09828750f396b4fbe3d90c111e28ce84fa227c12295d96b15f56b70aea3bdd34027262eb7302d8087933a8c21c3d9fdcc7481bd1a27407f5e0789df2b3740ecd1c91b913ba160c2cb2a7e1f4775e931935904c46992d5f2167c0eee7dd78260337f5bce10fc6243a602d61bb557fcd6ce10f76fa7057318599cf29e3b439a8a3efe8cdc0bbc71cea9bef783c1e5614b3627ae60a663740dcbaf2e31e52ec713df9d248db2da8cd02a1dd27fde69d59ba23b4523c21bb5802ce28ac03f062f086c48a30518a8a42bd493ea2cbdb52b0a7c626371cb93e8d3ab754bf6357120427c454313926d6c38f69facc4ce6ee3c02e0933c3ee0a9d4e0048afbbc0a98037c48cee5cf37d0dd6a6229b16ee98e15362839ee0fcc6447fc67536135a95bd23d5718cc94e4446fa35abfc88849d9be5257f79e7fc1463f7b190ef9e8226ffa106618a0b3d51f5b98b7594cff74940d341cb110b4426ea29c05a1ec03df0b2707c096bc1ec9b1ffb00fea4b94f92ae485a404d737006fb90fad88adee8fce4759eae0bee9630c055679410f087b892e94f68867ad2dafc3e8a819eb0da6a2f8728f39490c25ea340536f5abadf0f34d0ee35db2ae0e381e88ea89c0f9cbe782b9a487a9ed6c58989799943a4c0b2cd32377393f92a976eb3ecfbac01897bd2a04962901970c11acf3546665dcfe07c478fda7fb5c031bf9fd27bbf8da6950e3a0a0e22e7b54139ff5ea2885e3edb25a93e3435ba52798c5b4ea6c3e9e1d4da373bfa26a1e378229f1671c16ea4a31b31c96c557e1c3290caca01c9b5af61bb9f5f5156cf6c5fc29c477c4401b2d5ec4347da0f97b1a5f29962c94810e4655f591f54b4272b6b966fb2050935af6d81771344beaad24aecd8fc7b587871992cc5fca42a5893c5b5c8cc725ff92d823cc82590e1805eeff2837d525c5cf5d8b5f2b1b8a86ad6115672d5c2887a1a77ace73fea0bc89045ad95e300b487d3f5d171b8e7cd807ed433491c7111fb15e7c27bb1f89e51ad90f28e3d874295d87a4087a71e4822e8edeab6b08487d40bbe4eccd29dad8c7a091cf5e018e64320f1281dba81be48e02610a3e7758244661d16b93671619372d1f7a60d41b4bd2d7b9290a0ecac1250f7c34a6f4b8733fac9c1c6cc1185286c1f1e0389e70fe44e4352c7d875541d582eb6ac056d07cbc2b6d4e6101506ebe9460c304a80aa52b61241243c2790ba214b12a3fe48373dc613ed898fa36e52d22edc787ee3e363b0c7bede14e7ec94902689c0f9dbc07c04fcfc20982c01ef01bf5554767269a6aef082cfa8e75404ca0529b9b228e8a46b5d759c698f7e05177149c06d2b35d2afeb3083f32185ec198af1c5f6e7d3ff383a00334c338f6ac5cf0d5ddb4d21f1c4045a0e99413f66f511dc029815f53828d510bef8492968d4d89cd527b2532f0cccf6760b67431b0753a5e5a892371b264ea5d85ebca309fd18e0f79e751d5248d223abf799efb2b3f73a95a6969fffe99938dca6587a79d87fd9cc9244a8b99f5cffbd34c369fe2b8b3f602edfdd79ef5553b65a812335b66700f6bbb4aba3cd74aa52f05bff763cb13771038b3f4e481a601182cc386750bf91ba6e120750b99319c7a600c18a90407b71da5d59f324605713ce9c60cbaa0bb197132a35b1df94fe052763d92b1ee191cb247db9f9b11b650d36ab963a55de1bbd30c8907cffa35cbcbc4d7f3023d05c36937892dfc8aa1748d1b0602477aebaae696b2ddd64ff5cb4ee9efc4dfd5db2d03bfc542f93613f034627c68c818bbfa9b11fd5652cf26ecaf51cddfb640bab63bd7f19d22e070a7fcf4a3df395eb5c41e4d45533dfdafbe907b1a320bc1833bd999fc9d0dc3e0e5afff0ca51d93d06c1dd27f8a115f01d3e8247fa9b6329edeaa51ad1b968b99da66c17e8b5adc46e2e20c3ef52c4fc345b21bc9a9883bcdf909572e243280210ce98b9b88e5232e832848a76de47dfa62184a85f7e519dfded8486591d97fc1ede7183d839c8fa62aa4b51f6627910e960dfeaa22eeeab9e4986bcf68c7e7c5ea303b63a1a12830975ae8c193b7168af521cd2276ee08751342a069bba903e9e2a2064338bfaf81649da05531a1fe89e8176fbeb845edb717399e8cd7f76d3cd92e3d75497bd9ee75254e1a8d58c40b2318f55c2f457997c0ee15ad24c787152e0b319247ee86a713831482e8b7371141ec18513c6a95f4a02676a0bda26dae13aea65344ffd52e630200036b03bd8105030ca50c875abd786ca17adc6b36da14ef7febd25e176e7e62fbc89083e17437d9903f83f66083dd47af0d8cdf905d570293bce4796a10a905b76808900121fd1a6c1c61d1eaa50725806a83a83b2830885f2aca8da225acf3b69bfd4e1060330000000000000001e0b5b701508907a0ac40deabcdf2bc8c7cba265ca6ea447ac57ea1b1569ef26120c8a218cc6126522fd9d2104b447f7a170f19155ed2096f8a5fde1741fcdb9525b9bb52fdfb971a0677bdcbfeb94c79eb5d2891a0a3ef3e6944dc1395a3cc23a61e4e7ae1b6eb022a2b7d80d2d676efdbea03f69b75c8b98a96c7a9256bf7c3d6bfe76e472e38e295c1857a0547e2b60725052ee393df384f033e5c74843d1e7c7765aeb3ad8f80ee48bf529353e1bca2b9b63295df3290d179cfd94e44dc0095237e1abcfc5de614b28f6d1b8262f96d3cb26a7fcdece97659b373cf7c6120fd1a0fa306e2911ba58aa1d9475f5ccd160eecb31268e5803899c141c6b99e54fc3060a4280503d651a94328c9d419e4bbd57590f04a36526831d012ab27053261d948df950536557a3ec147a16d32695afdb9499a055a591890d17af50cc7cfe3650d3238f2104c43068c3c8809492b2de38376fdd707c4624dff2edd658b186ce2b7c53c94e420cf532174836fe50484479bce740b01124794b319da9671a809b957b58c08b9beec59e764d994c464022a471496f7297a64d0fab8e95d9aea2391d024d49bc19c3fe449bc158066b3bb99a8c3f95bde4f10e3ce50cee386c0cf7f867c5da3ba50060646d9b4ec5676c6926a2822de598502c270b90a05c946d9dc15cb1919286cf54e271cc411dd7a6ef5c765104cec7f0c2d239ac8d577767224ade773bc6bee344a69b1783097f2305faffc5726ae85a1a641827ac581ebbcfb309faa59b206a1f14bf36ae8f6394f942858554072a620eb6c7dda109520acdc2749838fe0fe599ce0cc0eda9c08ae47107acba48eed023e0fadfb95225bcff5f1ad2cd122ddacdb7659977b03070bd5faf9eacd0f7ada7acb48cc90b48f1390a6f825c850f44d6cade8392bc59535ed2da5c6e67a05e787bdd312c075838d3d365786e82a20c934802ff3afae930002b5bf594fcd897b02d2da762e84bf8cde00fba138336a4053d87f86def2b78de56f7950ff1f07579a401d5fcb31117cb1d243d139fd5a1a6dbabc77ad27fd5facc6db62a29f32c274cb63e69549fcbf86597338a399ecf198ed779525eb8cb12e651bd50b8171b2033d6a35c7319afe1ed634db8258d43943f3807ea3c29c69db199e40c1dfbb930c851ddce91ff50e9ff3651b2ce05675b2ff5a5549386b337fa42477dc39b1e4dd8e987aeacced62547743c5b016aecd724cd67dc7748f541aa035da8ec2f90b7edacbba2476adfabca9a9c6e32ddec43e93cba36daa0bbed99e2757b0c10e3c1d542f55519cd211a9f51dbef6f1d9f72e999d95450a9109b55db8b1b6850238a8c81b56d565cdc356a8dd2018d560a93f5cb6cce46460e90b4f93be36fc5044faf090e429bc52c348c223dbf8863a22719a5592773593dcd2c26782ce82e6042e01f5d4498ea1622cc5f82eef9dac219f263393fd6d4e47ab702618f947cae95f2e9618209e65a36827572abf5630508ab7f87503790aa4349a7b624b4a5d5a3d806684664f27e0d24c9f56cbe10cde282ebec4e64a772f4ea89669177a7135c732a30e33633dda56143c32fdbffe34c7bbad4b4d9410c8e7a93bc26b83bf3f900a55a4e3de8fa3ec483aca29a7b711bfdbdf1580a12dac63e2361cf89d1283675ba01a7439b4f46eef56537946713fc3f854b458b6707ae2cc44680e3fb05c3c24e2dec5955253ce7750bdeb1af9df0de09fbba3151e91992a714375a9cfee361e5446221c101b47ab262a12ef30977f227ef5f7002233cf67dcdf34072dd826b9950091b75027608d5bdbfd774ee80f098b8823b2bca996a04266b87d7f72eb5ab272b4745dea8a5d7f226c2ef752761c49dbcb18f13b3916b1663ab8c933d1c8b20e337e66b512805446cbde46a62aacce46330be4ae1331ed7565890586d3ee2af5fb227fc71cf112088b16a841a136d9cda79c6c9f9dbca642ca84e1a7702f38f46aea59b82897e515925e7e58d59ddbbed65de6b9cb571b2dba044615d85a5e6fb53cc456b1e9bf73d275947246cd36b07fd8602c231a1464b5f5988f991bc872c15da9452e66b697cdb4b5f8fbcf597369def719eaa3a4e0305386773502fa8dd6b9773282c5dd1431f1881bb6a09d3a7d5a271b22858c02e600b9b00fedc11f0cfed98361851cd34e1894187f40828dc7ed8f9714b12e812eaee8d3d163c5cf0635737258f587b91aabb0911d89dc8d85e0369b12462aa64f57ccb1ca197ea2d13bbcfc2d7cc0d37fe45bd063e2ac243c66a80ea4a5c14838e33060686f79af868ddab218e6d60b546b54d200de9e3f6b7fe092036948ee30dadfe708d2cf94d276687a64c03c0591188da5382f3adf0815785788da538bb4c75c3020319a542fb22432c86eedf3644fc3dcdd720bb6c83bc8a136dd5201d2c1ff8d242e5bf685882f907c0ae25c37072e15a29ff87a211bf5d9717ad1a0709c6794ee60af50d25f5b0b26dfc089c3410493d6571250eb209190daf8fd27e028ed44ca27044a52a94f7d5e8638638efbf95bf80dcacd02645a3911451ac7c4a12444c7571cf3cab55e4d655d62cd4fab864d9439f17f9e60240c371dee07ff6a250c5667fb2f38f729a77f12ed74cb479f6ac165ca4f2197afe4ad52f55a1c5f9b009b15dd24adc682bc092cebc68fcf467be1aec45df556feb23b925498d9bdeb374282ee90c9887cc636f6b235eaa9c7cda84b5e1650f60019c0fe6a3e28eebbf3523f404dd61da36d3a7c3aa7ac434c9d24037ae5d96855003cb4d4a081321fc5a1f333befc62d6442e30ccd2dc2b6c2ecba060e7642dad07a4b165480a88fdf53df22a298278bdce1f72c0df4cd6ed717018ef1a10d843ade3cb265fad4f6547481c7db01b0c70dde73db94502d5a11e234e87b12fab35c212f7a06e4455550eb897b7ba465ac439a00a19c3691d6bc9506f50ce11948768ee00cf975fdeb59d15629cc51f35d74f875a4b8dcd7a7fd6b6b28da5dbce182dc9e34eaf6c054576b69773150fc4e60cdd59195f061270d8651e57ef60559c1b1e7194de405bf60f6380179a2394f0f778b63f7138d3a03e276e7409fc93d90ba071dc2369472145a393bc357a250291b69082ba3e0153eb394fb6723e500521e3e7048e53ae82bf37064396a43f182779e9ee33e47c88278bf0c425de4ecdddad2e031fcf903b348653afdb24f6c887c875c89f3d9cddfe16ee5b9611b5b6e864cc145707e0bc524a7806872a875db04c9094b927d03907fc2af080379613c60f1d7a7417c1f6fb77227db09ac2b31ddbddb0d8cbb454102d3e2e306dd199707d8d205e0367ebc11e3684ae045a168c7c6920ab69260ef65a74ca9feed7f4423eb2575dc50a0e441593ca0064c3df12e778ea4deeecedd1df6935a84fb07d34590f449fc45b6fbaee1b25be1431624d05770dfcf7758ad70c973befece8feeebdb4d44276aa7362953e3edffe7fa99e872aea2b79a1e9e2a97bdd9e7da218094f479467f9cf494c9173694092b4fc2e2606d542407f02b09bfdb00819d79133cd614f23a006e0d0e2d2ea82eb89726085ded17a0931637c3f0c2b420905eac345758f255262b4efcf6a765974bcdf1d3c0875044754d316c5e6b0fa6b0e815afe85be70bf2e0204d19bd5ff540377cea70c9ac39c7a246efdb06328e57036fe9ce5eb6e2c3e0cf7528d581d013c6503c57e6b1634a0fcb8efbdcf7be65fe33191a79b45d7c396b4ed15895428f5e962d2a55981c0d708792ca0a68b5eae3358fa847b41963600a701c26ae788d9d65332c57446179c335c74e5a9029ff715098946b62ea12237746cfbd51fec119fcd876bb3df05ef1efbcf1a6af929d1e9fcae8a675251e04df2c31342c31c6d867bb456811d68e05fb0450b030c37723db3c7c1a3c3f40add847048eff5f9e1c4153864b978446ab64b3e8613ca7c855302129c7c2b4657634ee66679125728153ea4a093b9949d6dfad8d0758ba11aa799d395cc556bc433cad4219b5d6f80699c9e19ae4818c5bd5826c01121d8da63d362a45693271f0dff2e86df220f56d5d3ddb0911558ae4afcfcbd65fb707a80c72389f529a2d419cc398926becc44e789ae21fef3f7321494f04cd9c424922be1d63b2ba0dbb3916d2fd7ab2621f7dc3e54f543c272e3d27056de045fc7e451d7a4445171993de5e1e1e9cfe7d4c364ad779a73a930eadfa431b6d65668e1441c8024cb409c93c442e305b3b29683eab4960884a4664bf96129c41bbb7a5434c1bdbf40a32a8791f8c8acdc98c65dc04f2aac2368348d69b037751fb390198dc0ea1d8b35637d978835c21d42279c8532a2240a429a587dc5a0fb53b6d17863c2288f70030e3f78364ca26aa0410ac0824f298af09c90c073f1428c725cc90325565dfd711d0b22fcc51c3a9e86485beafdf02f8c57d547bc0ac0f1d64b518088c8ff54e1f93bb2e9587d07a71458ddad81693db75c126166d048a6a26ef103848eda1b4efe82856bd382c013ec3342ca98a657a12c6d5313b87dfdf9d62a5e42a665e53a30fa3d8747fda106db58d8384fe16c5340cf05af3ca04c9c9b3d1595070d8d4265a409a0ca6a2469468f869bcba578e0b7313109e15dc3904404977f986d8b740a30e89a818d5b517a1577c5fb1817f71dcc239273fa8489ba06529e3c4bdb23dd451bf1bbf2f6486972d1b401111d360b68fe32db7a7a871811d8cc19ab93c93745f431f9402ba7685fd990dfd7f61e76cba87faefd86d99c5510ec197a7fca4cff237de79d9b8a9545429f3e17b54be06820f3d651dd0e1bbd0a19cc83f59d134ed65a4423a00348d49b205c3d5374b10edb19dbafed8edd7f6776bbc53891c7d33dc98bd8c568f004c3de719099ef370c87f99424adf255e7c0c70dcb353aa599cf79246a1114d80a78fd1b673f7932f5cfce1c9dff7695e3908cc1c303c64176784510073e94bf09f15c6153ffe86a4857eb568c61b48cf7c1fda0b5c53d39dabba10d72b51e152b052b87abdb0d270479983e90687ecfaa789461661ff4e51676ff6b92db9f8e2e0e418387cb375e3551a87f9d5e24017e76523e6485c877126933e20ea26c63c3b58da9f4fa2031110bc0d79cb82bd1695d8b41c32786d9d6c5d349697789f00a0734bcc1ecf0da15b5b52a7246479c686a18fa02309611078abdff25c0cdcb924a42b7e73f0c496dc73973854d8d40fa34173715487de2b2c3361c17b6a5e5b21fe69a8533969c203b5bf76a3a785531b9de2f2100238e9ec46bad2a65213bd2dc292757c325881348ee154f86bd71aeb99672a7c63b5ad521423a4be566bef28649c2f106469237bed78569dafcf99eb1445ceb598a9d5622075e4e6b5f058c24338546087b8e9abda739135e93d4ff124f5255eec0f2a776cdf2b768e77f511c178e4690a497d9af829cd7852cae40daf57d9b99cf6df190a9d1d4854eabcf4e1226521bbac0ed19260091ac016bc66a17be50df890e09faa7ec82e34a92cecc32d5f5b2e2462dfd547624196e25e25a9a9fc0e70459c985b91f637d3656c16c9224aa64a491cd8d7776a9ea645511fdb7af7c82820e45bb9e01afcf0d7ebee5b40ae2a7a499041160526c988dac88054962cc74232c28441cfc91bc1a82d457716f487dfe854fe028521a0db91343e40cedc32a11c108333d245340c02a70118953f559c63066a6d374123fb46062792dc602fa04101a543c0e36343e9f7f0736053904c7f3d63cea0fd2d812b71861c5c154c5788991377f615382fbc05113310fee5f418b50eafe05f267896fd4cba3a2d745a2bb4b413c68a98ccbd34e51d350513d0cb19ba06443c442eecd5cafad53f6d2a37a21cb48913bb50feb50167ab1e4ba630200031f5d1df9887703d6548b94e9d86a9684994bbdd35141b04f10b8793cc23f383ccc87e2d2785db1fe8d3a9a28babec48ff1e26c737add23d33f575ed0e11b539a7bfe7c9918acc55a9a3d80121dde15bc356de7362c7e5143c4f7f79feb02cf34fd4e1060330000000000000001eca413005d9de9561070c7ae6f3e6b20d5ce928b19a5b476fdb499725c64f6be18c8e2cc7fd516713aab398631640fb898580a492d9c1f0ad6ef18581cd21a74466fea19aefb65d4dd7da1ec0546b7b8a163e4132680f5731161b8d9c1152bfc10e5fdf6fd4064a3b56e4819da2325786f98ff80c4838fe2fa8e48bd600aa429cf59592d80dc78cf5102f001e7054339f6fa00c75fd0e7d02d8f8b351e3103fcb0a39a978cc257c9dcd6b6a8af82a04df71dc1cadc74b7f78df87aeb5a11b05065f444ae51629feb06cdd0cccff25b117fb60b56dab63397d7fa9511b8332585362997d3d5e349e95d51f3650cea33b7c6b9cbb7543c79fee1d69d5cabd52b74728c64dc5fb660d52dc443dc6451082cea91bf2ef4407fe780a8d91ee86cb45c94f37b0785c255ab9999430ebf1c7073e0729026f8b492f822346b028bc83395c98ce367f9c96ecc386c354a9ab1cfa425813de70f32d854d83a744c57ba8133c4f6af0f5e1a7c6bc20b2765c40635f0bb073e68f32d234818c5c46d54930d79530ae7752491c7e8d7e530f8b75ce6cec9bf0757e2f2f24a8cad3dd346cdaaf24d759d70aacae7f06fb66d48c8ad4c54418d160a6854bb62e28017e8498648b4fc17d77f6ef314d58d14d3375e830b6371865c770c55838521b7499e063f4c1f4003f2da808888ce14070b6869da749bb06530b6494b69dc0750a56014d4c3d7f1554d44c8749d8414f8e826747078d4709c69f573dbd30a3c5a2634ca7990e175e22b7a83209359a96a60f612035b94c1b0758595ee39d21b21b4ce1d6318eab37d50cdc1ab9c91e27300ea039360469ec8e1c352cad25da9f88aceebf5fd24a3b27d0068707df7bf9cef87e739e665272386b4b8893f7362e42ec026cb6fee730edd3cfd30020c97d972a984f77c4d571927d067e987c2d4040592c070e7512cfedef9c8bf31caff8b3167dda3ab5d4d5ba62317fa31acf1814b46b7bf69ca844fe31eb1a658a2661f471b5549848f505b7720e09655c6f6a6f7f0439a1dcf70e4f3f557d6e3666311776fe2773bbc191b3691d92a58ae41c9b15be29ccfbcc4c4e4fe0bd9f03d26e7c2770d23261375593105dd962094e82cf13450d30844192b6fb71a752ab76ba573ad57abb260c9ef039d18f45a65e4e6d94de5a48e44c4cfe9a0002626f5dfe33afd386a50560c78d35e836e3b7318e30da390d474fdd1ea6eabe98cf42b5caaffcf21b95ea2506e3a3d3e148685283b8dd00a8417edae4381521c38956fd0097fe3fbf31c741194151d3d4c04063ee8730fc2d313fb898b62b3a4f6e7ccd576be62621a7f5e0d738f8896ff213d101a5bd5547571d8220cd5f8c18f1de5415167fa1703c67c93510ef6483ff5773cd15b7e1ebd15976889fca65ee281c7930f94972efa72692615628da74373f051dae23342ba673e673720252dee86c4b50fae384b0348a60276ab938281fc7fb11c28571298abcf8d83de248fa3c64803797b1e4e2159e914cd654f897854663db80b36857e9a46d9779e7686486b721473f13369a705fcfc6ad651d9d0947f5c8f40eae0b8c03d5f72418aff1178b2c357843fc82d8784c31dcec38c66fc1ab2f4098ade5d8363bbd6e25f4435828822690463770e1946e992ebfe2d260efb65199963f81470e1c6234a4c40d50b2579097fe512142251fe8cc35427886d8abd05af79b32c816bb938c57eb875ab90764d1000ad975e16f4b53daa1fe450da5c5d051f844c658e424945738a13e1fb2459ac9ff9e22704f6caa2e25da13c4f6af074b5921347b936f721eefbf9a309349b35ed90af2528d0b5ff7657d8b3e32834adf35bf165e841124452e6d92f437936abdf1020bde6cf9b799877798e38e3bdb0140c50dfa6f7938b15e14689607a41155f4bab389cb370880f6842add409616bc323363ecf6442dcb6b0f85fc319c480a9a6290a2cc2574fcfb08d2fbf52e8fa947e883950bfe2493aa73e6bf155a89fcedc989a2b7525fb18dac1fcc2b38a828e3a2d6a4144f24bae1248678fd34ddfc4da52aa87ee3e76218d1802a217f5e28845da3b18b6a7f4a864c09cbeac2c96794ab8a072c8a7a5de6717ccb89d90205662938dc399e7c476955a6c64857fa69596b1e22e3643739e6ed77f538ddc2e4324aae970e84c23e249998bbc9777479b4ad60c8a663f09f5a6427bdd791fcaacf76bfe3cac99a1316306c0d2b440dd3390e5dfad6341ebaffc89863b95c2800489c7815f6139af92cdc435501ca4132344d4521d8a4543e20934d28058b8ee02942c1256ae055716cd62a4a7824fce19cb3bba365bccb177f58328795943723d5c28a9d84cef9b721a3b548d0352601c16f51cfbbc32fd9d283f3300bbdad8b62a2885c94684c722cf1b6539fccfb0a43c0197117a40b92448385baf3171e44f2af2eded03c67d0bbb5a8787764b8e04294f2f9403f8b2fc0cf66af162a5780d3860f0f4b99205bdd8a8e879299850ea4daef95869398c657f84c890baf9ddc60be0abb7d30f4a4451eb263bcbd7d3da2673e317ac7c57ac58e564bda3ab49b84a4f750e6687d46776b7be34fac5c8cbede97471462e2fa81eba870b83646d29d8e6a9b5bd66357f5b71d3923611e624efee55c8bcdbc9d5274fd61d70da158a707e1d9594dafe39601c12000cfb86cf8b5eaf8f76d972818e1e941a436f62b6a994caa6dd59e40c320fa945c00d3ca2ea5db8333d2c609b3ee4294bdd21b3f11ce815f9dea9840801713a2e762b260977141fc74e8201d70c466dfe0f8246b2166991b2868a4cb40af97d82334793b9fefe99f91557bb1b4d1b9f77314920fd2bfec31e5bdd5d9e5a71f32523fe237795b39e13e7f1fe76eb0f56f2ddfd99cbcba32b4d2e7a82cee13c5a03e716368102b18e8566c27f51b6d8859f56ef29c177da654aed948a1bb4985cb6e22c58cc5e7d1c1859152a028280a7feb6a78e9647aefc55d46d0c4db2fc5b0749005413d8dbdfd9a86900ca63ab8cabe3bb8bd85d035cf0f610870cfe0ac2a11016d5794eeaf765f8cbb949d41262ef38685152f72bb0deb2454c2e2b024f2ad600c41e802c7ebdc259ce4d56d5fbfe3df4380253d85cb17ee316a9f3ffb1aa1e98e167b29aa4afbebf324ca7a580d396de68cad97c89203bbfa937e98d695f67e9aca0d3fade39039edf319de02cdb14a4c4d448f26bc6fe4237698fa83081f6b001df752c02e7dbba8c601c2a21e604aafc182cc2ccbe94062dadc7b57f29fb70ca190611a801bca7c544622fdf90f75050fe1686b26edfab0e46d08aedcf20567693bf73762c8b3284d5fb8be661ec00b682691c67435ce00d055c834f18e989c6dea9cf6a8ac8d2690076e557335199f72145df0b5bca550afec7cd1d79bf38ddac9742779811893037e93875fb8b3db14f27f7e05a0e3f1bb0da4a87fab3405e74181dc8cdf30d57c43b68d70a826765c843e88af35f820a9176aafb3efd8dca04205872a046cf93cbd9096c1e09d7d8898de1806203bdf238cc62d846cb15f73fc7cc6067a7389b5319bcebf95eaa4c71cb2855f75767a822203112f0a4b23dee9257533933761f78041b5d0703accd8c1c89fbec95658584a5d9d89c221e1001a4a957f99d8e3ccd61a83f21c739ee52d13a73ee83b163cd32218fa412224ccfe87d9eb7f4ae02ac4a45fd0a5842c276d7cba2b9d467678a3be7f13e8818fc8c1eda0636f203b7dad104262fd8bca12dbe7f4af9ecc5f448cf71cf361dfa974912ba23ef754d33bd894ede9b721cc82d04f84d44eb6fe2ef45b0e6be77f2bca1d50c03b1e71ef359c151c7595fc1a5be422ab57b4d7fc671dc5a83cbc64a259ac0fcec5939f30f98db9272757f03b1a52f4979161ee9712911132dcaec9dc06b01ba299eaedba5627dd3e13f6ae270d4b6c8afa0dd58588ec4e4b94567d8566a877b215d68911b2fdc4ec662bff2a3110631aa003fd7e87ee8bd543f6efe706649a282beb97c18d5c16c69d23d08e01e99a5e2ba2b08712222ccbd0f39fbe177ece56f0baab123acf0a3e0bdca2974b80ed3669608f9c2648b014ce2b22a442a1c8f00cd5777f7013d3f234bec87f56b557e38756177492edb5ed6aa65b22ffa9dc86deae0d9a86a3453468de9e235499cb42186435fb61dce99d5db4533065c9784e2ec8c4cc95d92bdeb56c63426a6287047e2e112ba99660871f76595c77d6e3f1c3881a4d312257263dd3690374ec562e389ba99dbde719c0f5853c646cc621421f4d21a7daae208185d31977626cbcb6428f2dfb8aa27afa3321ba4add6525869ba874053f436eed7e3afa08b4f4897f84b768fb4763d726401608e5b67640e1512d14760ce92113368f2a9fe68c3990424e56c911f3d7e02a04ffe0a63facc91d64f080f89fe3512c86b863af4ad52abf4b413580dfa0ef640edea7c74c255b3fa2f00045f20ae19d1e85eaf24c358be61c301c246e71f638ea337cf75039d15efdeebd86c27c2832d11dfa1e214e21fb252a3ff63526dda3a0b93769ddec45e612d3eacd14c781fd2392d238c1e2a69814961a8453691c3e4b1a58154430aa2a0d9312f6cb435eb83f87532ea009bcfcc27601f355e5d201f82b8a9fdc5df07fc4c395408d13b39dad1a100d50ed232f27c63582993c7c0313358364ade6c2526b0bac726adeb5480fd80fa720b7cf91db6d45fde7e1b28a01e8a66962edceafdc414b997e634568145248b48fe114e92e4ea79c5da850975c5f46412bdd356033dd4f8c05bedf3db012fc3044e99fd68b77391eaff9e603d56db5e8d8d436f5c80dae926b195d9e49d38e02ee07105a3946b63830f4a6a1ad62d261ad0dd7560d261400d310f180594f0f7cc8db3f59ac27ff77624ff478adaeadf3d7aaea0b4d34b66879678c0df8aa7a16d5796d87d1c005748c094662ca4198732259dc28c4d894f02dbeb66cc56d63bd4fd66aced2ede5ecd6493a7e88c79775858e8540e85fc61bd00c432413753fc9a10c92ec80dbe34bdb90a53a66faddc43f1f76abbb5d68cb77537765c61034b86b68e03f6f86a3fb1d94e2803755818a81fbea72d17e89c5838c07c6c972824166c2973634f6bd099bab0c528fb0c46e80d15713682d3d21ce10aa8e2642178a543bc0e5535c52e632e6cf6fa08cec887cef36ff76b83dc81605ac4f3a27f020d59a03af64966e710369a89853d1b8cb99d4ff1afc26bb512dcaf59e0d3adffacf44fcb77c31baec44c36add7779024e3c6be4e1706098f43d56d8d083430e47dd592b5ce1fef3e0b440dcfbb739c04b62064e8e721d07ac22dbf26d3e9ffc332536d4c68b8ff3176cce95f915a9cb5c00f98a1b7c694e9a3ce5e33248731eb3231c6c3326a99cd6b296d4152904ca902b6d6237b300b679693b434b23765ce5003757a4781a06ea44962c00c1444615ad700487b66d705fb11dc24f240e5df8fbdc838ded790e80919bfc03d540dd0b63927acd5be553cd2ba89a565a78a4f71cc805107836f878b9969c64d95bd334b352024395e99c0e2c8663a0310e7f123aa15d25ddbb409d11d59633905d722ecf93e128b75e03a1387525194ce99e13b57f66c5aa2832b90f8137d985179168d7235111379b30e5436e1b6c46ee56b1168c5448e9590d89d46f5d81d9b834c3740d219831cbfd4755e37b8347a0773fdf1ce0ae1df17f9564d9e943f7b99a14d71f3e6290e48e32ec5c1d781235c2bcd68ec66d0beafeb80bd388dafc8a6385785e90f8fea99d9f4e076e2d4bbfae4a6921753af693773ed934c3611f53eca0491b279cc9f15e9a54315779ed5c4d5a2d0eb9e3b8abe8d18bdb92ffc5738ce88a06beda84696c15edb6b556028446ce2e029bdd139aa9afc6302000343b1663f248dc1d7bc9b9fe1d368d6bc11115d924dc225ff85833f2c51319a858a7c696e3b2b5cf4cb12b8d20653dbdda13d0b99578a24f3b35490452cd1836b4bf0b42d729bb22a651599ac321a2e2a41e6f03b16454a21453f83da86cf0267fd4e10603300000000000000013251fd01d1c901ee60aab31431ee76531fb3dd63bafa7f434559a1f86593e27cda53f3c05424c6d34be460f33226bae6ad10de5fbe33e488f914a5600b756aaab8d61981e480b5f6921416be364a3901e11512c40b2639dc53df4fcf851bc7ee6ad8a92c1159ac349be05a27d820247e262a0ed407d2d21a0323da2bfd16667bd810e0744de41dde43c7ba09d0c0c099ceb74764b79f999e12a8426fdbfd861d7e4378021d17a56383ae2b07b4a480208050cc51d2195979fe25ede6634fed3547fc8202deaea8abd8ea026a88f81f51f62d31f52bc28ca7604b94f4f6503b802f57d0656c46127daa76e838a2537776b9fae5575fcedd9a669b305c1b0e224327bf6a1a0a3b9e51c0ccce51efab89dcd87884f051e597df49056910fabc6b5439966952da6f165847d75a937dbb22985c4a4efb4570df1aaa8a71c9c123d195f0e2694283e016b747a3256164879ba3e3f2f537fc83d4ab5e2177b52865cac7811c0e8002a673e89cd3d4ea319ac403c88fba0c473c0583cca40635de46d3ccd95a876bb0211a94413539f8e9fbad7d900eeb72d45d53efcec55fba624149c1c681cd115f5954ebb4d623cf97be7a7f2e5189a5d53baba16e22bed742f9483b5bf2888fbee5570d6507434e91efcc4133c06d2b45ad97f9153bc9f15fcf4be31e2746388bb29abca0036be44bf55cbf12ace37e4ddfb97b4ec01027c18c3db4fb69aee2e8108e8bb5c4db7d8e22e1f7ec4b1426f15ef5eece2194409fe6e8e305e8251e2ae0ea54c4683a6998e64be48140a03de42b08c77ddb2321b100532317d9a3f8f8670921c6f0237183b7b0b20287f23052996a771583120d5d7a97671cbd489a0348ad36d6b604c5778e946926bd66b651aa2256768c8243ceaa5c814c4c71df63300f00f901dbeb94c68e8f8434f774a756f27f4c7ba1aada8eaee8af48e7e858b2bbe613f1a765445637b21849ccd61b7bd0edfaccbaea6a15f3f35d722ba931ba41f7865e7268fda53c302e08964988d051daf9ac5092db40592266a253a0697f877b6693c7f1afa5aa276a83834c7d5ce35b70b528a34fd61477159d648222218f68f3ed0291157b8ab4a1df945c955a5509287d630877b1bba9a045ba5f7d40b33cf3684655510ebf7adf9813cb2bbfc6377f4ea52d9ef09dae32b5e745aa7b776292c0613878d5a04d49f871cc7f9df3178becdf195e427be6249299f6ba874cf39d0270f8bcccc5a0ae407b675ba897d2bb9ea654ee02a2bc591a6380ca85b5f5955196ca8dc57ee78871b5f1fc2f3146b928205e4e2a6fe7212c77ad3adf876296167d807263765a6eef112ef290aa433d1d38c289d1856cffb67b90bd0d3a95d7c7f19cef352500daf2aab694f636f09aa063752f3280ceb58c8ea3238d19c45746dd4b44e38e75183d1442d70552b3e9b3e1a381ca57352d57b156e7aa09fe12061647f8efac92e5c587c5c3ea546dc5ebcb8fea52e62065d6a7b9ab31645a3746be8a3e8287eddec3fcf7b713fdcff243d199034ca35e8d9d8423d3a952b5d5707de1e166764a4708aaf0eb452f79435a01bdb9d3b1e76cbe7f8e49a02fbd287b276a863dfbb29114396fc6074434c7530fb1f3abc76518af4ef2b78982e0b308cd1888a2ac4d3962c4d298ad276a158c415e647a8222f501da82f60cf31fd0f9f2ad83bba0e43f7d09bf19ad655fb0dc5ad454cf295dadb20b7346c1bae02f2e05be4321966dae36830a730deb9bbbd25c80ed3f9a922a53b910179205b06466b00ee7e54c2d94802f64adb2dcf0d6d6e59b973a891842677b9fdca70f725562dc2fd7c543e212e78ea52f9dea384f14a3deb155ee408196f4d34980084753627dcf02b0fc6a87ab772cf4d91212ca8912d3258d5a41d7befa4ec9073c09f56c08d4a14cf4817b3ae18f2e235ed7744b29950765873df77ac6c283ec318c226b19566759f68979b6a8161ab937ce7bc6d6628b7ce7ffaffb2dcb4e94dd4e05371547f8a1a3892909ffa5f991710748301ff5a89d845ab11ab7c131ac19252d4cbf3d73f3d14a42f1f9e02f2476c51a214ef304306aab8d33500cdc27f4dcc8b6986a0d40d420409e3476156b7b2c713d2496a554c2c906970f17b0965971eace55176544c4441e2a04e237b7a95dae389033150c8c7549c0c17dbbd4c78af6c47311dad080dfde414f6fe1a3ddd5ebe8f747f140cfe5e57cce474b4bcabf73f9094a1670f84cb9f1198e15407dfe2090134c3d3459a2ac6f3616ddf4ab126e810cf4b0947eab9c0985263f76a5156de6477f8dd26061462933b7bff8b28e835cc9c4614888bcf6a6921e9a57203cc53313dab9c2a5804334915d48f588c7d6637aab73810016650ccf70898367361d11ae8918dce7d880e34242e5ccfc15c5e0c6335621d8014332a0d118409fedfa6f8c17fe7289e62cfc5a32bcf3c2a95a05187faf76a215ddc7cdae85eab332551db896950d38a96e9400dca16d0cf9182664b59f6db62924cccede98d79b63c73edb73c46884fc336fff21ba10bcc9f6f8d8213c6fa60e8abff165c71140459ba56533a0da89b9a92d737c90fe4ebd66aede92ed9e48d62076c0f68802ec96ace2f3707319a037056d44427dc880480ec8f688f189cfaea3b4916fc8cf5932fada94ff7232eba9e997fcaac39fd9555db96635d7a67d917689795a72ae94de0ef3a5de01c8fe9d7e880327f6ec0163bfb0e6e12f9b6c5370bd3e77c71fd3462436444b93cc1c69bea512b2a401d7ceaf8fcee829d77b5be78913479078633a44f8e9c4a2be908f327339f0871cb9e3f053218e7a25d4420ff084a29fa8b18c9ace45e881eb2f645cb9bc4a0a45974f09b5977888a2b3b04c41156de32e4728da4bc54b5f833109e226a205f9bde6426115b274db6f0d108c1a982693835f1f952278837aabd08bb7e8768f9a2299f29fb60facc7e80ea50413f7016f1cadd816d00cd8af0042cd74e3351633667d3b2af640e429234fa73a4a49f091dbaa418a7a599474b136e5deca51f585a52cbec3294fad3fbc45b33b70727f2228d2c021c7b06609d8a960766f81b0c68c37dc87c994781c60db2ac4875c02d906ebb5d9128192f7336d38f138bcba963d1414f8c757d84e8021b0df501e745602f4c53a6d11e04f0edcc8696c62ee534243cf178018e715296283ac7c6345b4347e70d6d18dd38cfa7fcd96fdb9363f48b0647ff49d31f5c5335e19ab8e4ae56659321c557901110b9fcf97344e9b52d60977323057051f37f14b2f2f7ed993e8c3b96575537b61cf27aef656719b60f632556464f3d4ec324a5d76cf6aa4497eee761020ce5e10dc5a341aaa63580a0d8ec6e48efd743f82280d5c23d2353eb3b06ad1b91c8d6880a22eb84946613dc44246403b9921f11e4d6684f66cd8de8e421490591bc2b193e998bf0f1a024736e08f65f0bdd04b9c2858db6286b5886ebf63e6d92fa9378d2655dca31b036b90026234fbeff9a61a804d24f7fd9a6c2bedb67cc238a4390e42a1efff4cf61da4d6a9af26ff6b1a3739da2d8ef59f953e5d754e481666dab68501220cf2a07582e3ebf1eb4ff7344b4f755af5b22a193786c135b3d70b24e2aed5b9027c6f70b71fbc3e94df1d17ed4aee4259f1e30674c8457f801568f71d8aeb3cbc1912dfde8393b349ad9c90cdf6a250b26171305457e019c291d00d85713863c378cb1076fc43bac35cc32def88a06daebd5d4788938df723d03b35480e4978433e87e177f87f9573011985b7bb4fd3a44275cfdb009baf1783ffa336fdea712ae21b9b1066e9df41a66f15a58148e479b15fcbc5934fa41764087a6b837da014c109f4f459a2facda78b318419d3054022d605b38f21dc9eaf70e2595e3d98b12dcd233723df476bf225e69a7fa3d3dcaf9700187f07f106626d55cedc83baf4d22225851a97cba5566880cb172ef9854d96f95b314c0b485e8c6f8f22a85729b1c84cb1c32e961c0894d0a45ff13e1bb997c0bb7e20d4c3e4453a368f301d46f65778955e88f00850b0977f834e1c56841e14037c1cccb6619807854b368ca12ec4826c82f7a1563ff765eec420e8502148a05419790daa55b985b3396053a282024793bcf409359433a1ff8f9cf9648d26916faa9ca0adbd88f0fa6378e8f1f926b5c9e5f0cc8aaf7f362803313cebc89e21ea7f9410384154cdacb1eea32aa6ed6425bf607f6788eafb507f4878bfd4b0b778f4062de110ba256596cf3eccbca47787734293703af1eb0ce831c6094cab79e15e108cd38a4695d5a7946c18007539c1ec3ebc326a8ed0963d3836cb23692848d654bb7901391b251f5a32bf00ad267578dd99e44567fa134e9562ec9b63cf79a1ffe88a129c61e6e7450ba76408be53a7a866d60186321c71789bcc9bd0f9e031abc80269f036768b4eaef61d931d692d187a2e9cacf6c5082c6aacd0352e76fb2620166eb72ec3ee396b5493beefaf12a45c770eae34b8bacdea144c371b09006cd5b2e7079e45fd47c53e3bfab172aed6fa69d3139a53a5b95e9aeb3bada5d4a0015d9b4d5ab200fc1575543fd418540459e85c36ba0604f7924bef43581052eac1f62a32816b48a51c2dcf6252c8869f3b75b37493f67fcf929133cae0e1c3cf8ac6b3d35ae4a0859fa6663773cdf5d5951a1696ddfde65fb31457dd58fed780f6b8c304fc3e6bcdfe80938a78c067a4af31d7d8796644d3a1190ebfffa6fe3e74c7b4c97bd0b1d8243698399aab9165135aa8638ab3338496fee59ab832a3ce01b511d91c1e1add4ee471123e9dcd7e64f4692a6fc4c76f4cf84fca51f102e340031173a3133c8d29c0520076e12f7bbd5a4a01a73d83a68296ca13bc0b7a7dd412e994314a9fe8877c4bce4b5fab5db80efdd03d2ef5c21c1e45e605b6e744f9aa5847ac6a94931ffe9b7b2de53686ea0262d3a0ac712cf34724dae8f5f569398f762bb399fc0d576f224b55d3b82fa78333f9704f00eafa8f38238887ebc67cea981a6091fe311c6a753a24de0eea7ddec1a74b267a6fed358adbd59ee4db45cf41d72f0b5004fa945c9303e40e1003bf40eae7646e453e5671a60f12aa489247ff12da8bc8abbdcc2eec1628273e3443b688e018c68697a81fd9e793dc0eb792f161cc8452caf7d568778d0b218eef619b270c5c3ee16ae293648e3781816916519f8d8cdffb7a899f337e6677ed8f6dc23dc24e84c9e750584599005f3dc7b53a3286707e67530726af4e0f075c47824b0d38f571c658ee4ec690b73e88994312a19563cde990f727838db9fe1f3bbbff15ccad169796d5b51815bbdde4f2a405222b1e79d907f178557a3979a5b8893055b14e921316bb29082b353a8610ccd4b406ba5055ff22518d55ef8a8c5f2f07829ef84009fe505ca0e721948d3d235999564d16a597b867df1605dd68340e535f87dfe8106b4f4b26bfd353149dac2ecb1c7867e07ea661a639bf976893f32a4902ba961e4b35630fe273a156300df089fdabcb904b99b9bc6fdd203e00fe5448c3aa75d4ba27a558e64c4c55c95522f81e91ea60495fdd3f66551fdfc9b6807e04e92b37265b7c5815604d993845b87bf0c9178b008aef9b2977e1f08569041c04ecb2ec36f6c8b00735ed2775fe9b7f97b5371268b4e1211e7d8fbb5cf592069cc0b5678b0708098403b07962508c17800458a9bc9947a9bc521c3e915a95a892f4fbba0524c37ef11a633b4fab270aa52615a1727fc819c01df2c52095f11fdae7beddc219943793b45c5e4ea5c585f20416daf6322c06c441532071b4229dba3f0967a94d0ecd2204c240c4206a9d838ee1c0250d7b0b15c537e77f1ab4de5233269ba0ddf2c5b8cb94ae7397fe4ca685927fbe5ed92c630200039ba8729f388d25a913319b920182c147150d2043c084819a4611e3bd4f8ed3bd3719def2fb0fa0572ca31ce13c45796b46b25592a3b0f24bcb304b3061161d378cdbe6c5db524063d8d1b965db5470776ac305d5c6bdc324abf49b1617ec355ffd4e106033000000000000000138fa5800a9aa2821029d76bb8807e76904f4e5e9b1036fe178379816d3f81861211cb68334ba2f650f95740f72ae75ea1d10ba9a2b5b97fc2734846eb109322142f9583b4721e5ce31b724aa7e30b6f44b9c56084302b3f36889901f2fe6164d4644b603f96b919fea4915e6f7e879e9a3e3535e9db76486633173a436b670d347f4bf63234a5189fe620c43722383f1279de3c3fa97393e0f4a5d7be1361ae4b93fcd371a7b48d8594abc508aed5e450568e01e4407d19270fdca60b7442946a4d6332ac3b10f27c0a0531226292deb2dbb7386d02b05d51dc59854071b5ed1e22253ffae87c04966c1f4e00ae1c9d244f243218c9fc1cbdb8f4f1ba847edf0708ec908a56e293eb4d632848be74b94ec7124cc63fa9439bc88a625928971ec5eba80bb865fddd85c94dcc132f7cc837cd6f28cfc3208171cc74ff53f4c3dd9d10324646fb609fa3ec15cdefe2384f11d44e2a533756bbc6d5eced5e74476486290fd45258f9b0e849bb43e2dff83e028a74f5e99794ae871d7f3d52e8a462346e7a74c581759970917bdef1ffcbfb71579aa0369defd4b040ee927bc09f26b01b2d7424244c75b46dee829ed87e9f2882776f1df41541290d4d64115e8491369393bb474abd03ab12a962f1f5a45e080a957f5da8a53ad476f526012d295e099a0b3614a15f144a893aac99577459f4f7e1b5530c0c72521fcd0df2b3f4d82a86ba41c9a42e1efbf775d52eec117653c7c39c3b108094255e13fc38c5167b056cf603826f5d567b581746e54993343ec4ad636c20ffa22fa5ef887ce2dcb5f2a7179bcf0a6543adfc15c2d37403d62b6d7b53684e035173b1a85bfae77721d271779475c8dd17ec8707ba0562e9799db1c7fa5c51caea0b9116ca5fa98877963c9f04ddb5ff1e00c06d973efd202320e7aecaa44ec2705cd160d0f70172807840f18181cfcd918973771e38357e7e3411714275baec57a1e211fb3b5c148c9909f69f628bcb61ff775a064e6dbbf9f3647ad7623eab0bb6814063135c35665c73ab119fe211e74ff5a354124d1a8287116d6b6010c4a6efca963c17c750af3d54ad5945aaecc6c53bc154790b43d0b2fa01c5923991f3e249832821977180d0522807a77d4d119677fcd09b42fc0d9df18c9f8e0572b4bbdefcea14e75e90010ee4138bcba28bb936543e812c4e20db2f525df7bb94ee92534a8ca67824051a115728229c409424c52298c151dd76fedd9095a1df7a4ca6b671aaacc664a720812cbc094ace3a575e0acb7a7bfc698a4055b4dba82998598789e3c1fcfa7feb1b6c8a14c42ba69b37a35d28186755505fa6acc9dca53275cdcf45908c5a9f998533ad368e3501e1eb62e2b66f9b6109c20e25c1514fc856df75fb72013cec318dbef7592774ee1433dec86616a4dd3103c147e49be4907211cbbe8b2ab59153b25e05fae35f749a10041761c280c68b2de31383509a46dc7362aeb550ea28981ecb323e3e75ff6a7167cd73b3bd4f797b11170f3c3befa705a7a245c83b9838086d3b80862870c6196923c5a0320eda60ea64c08f83e171317e97bb0a6cca9f0899e1cd97fab050f8a9a8709153cf8c5fab7b755e6e9840bba029cef1ae970047deb3156815b88ac15524b0d7466592756ceb4fd7d87ce612837540ab3b226d44dfe66a2651a72374b9d4096c6ba4c9cd5d6c5398f5497ad862523780f788f0b55da1400c8e038ad613753e3f1486a251156f1acce6a19844dcc021b4bc797c4b8de59bab5b4ce56dbc10a3098219884bc569dc105c60da4a50e064182f009623f8c13959d537f8e506066f16c0372f6e7a64ffcbf670ceba653f041a304085258369c082a8c94d17cb0be6e8abdc65a4e7e48ac17376a58a7598bb45e150616d5b7099eb4c5fc6f3e9074468c9425916f3333144d5b8faf6bcadd6837be952f938673fbd29f9be2ebf8e21ec379f4ea83e47549be53a3a51b8b4ae957072826d4e82a16a9ff20dd8c5e92f4b59a4706317aa13daffeefa36a87b9ace374067a03b4eae42c1a0c39be684ae196dfb3256c87962adc683182f1f6a2ddfd9d07634956719e87bb86294660d48a5a8bdf28871a0d4e8f65f5c38f404a40a7d09a6122d915863bd87fd5e1417557fa953c5628c53df63bf8d1b5fce04be5190823f17ebcac733f32b7513ace05b9a7dda60be86c079c25df7f5459e304a599b62952af80780b1330ee92b28fe690a16cb1ebd6b3dbc402d033a88baa353d42d6316ad8a439ce72fa57d02ec91baee848956ed54b416c1b7d16c681d1ac6daf1e0d3e3b33ab83ffc5201f063542461be7af7a4d3f8cde347c8e51252ed4e4d712959ea2c23813b6ab269f9adfd6918c28469149891943c661587dfe710fb2cc50b261be87d1b1ed05bdac8e830f683a60bb1d5ab1bda3e59ef0b3943a665575a5a428e0ecb9ec902ae04537602290db479e736cf8257fb73fcc02dab3f63847619ea1a76f1551ebd381bebf17f6985553d2a8bcc21f384a47d2355babe622ace2e70bb91561eb0002e0b94c9b31137792fbf8dc002465916ef8edfeb958bf927ddaf7f5703556f6eb547cfc3d99038a24430a609a9e443c1ffe581c00514e99cea3257ec4324e551d8f83af5b67bbd6e61ac3e3175b6117048088fc916f80ca948500f77707c4bc54c9799c68b72c381a3b14e477234ad787f4b23e1edc43d139fab5a5f8124a3fbe727ec68e4b0e8fa7e877b5712cd508773babae976dafc01ea98183a3a801ae51856870b4acfa0a20bb6ac10f207c39d9df40bae368533bdb997406b95fd1fed9c6f85bd8430c32453383a3b6b62036e2ad4ed910c941a783b612a8a77f7a4db0c3f75dc88884d06966710f56d71106d7d4124e24a8b5f42dd2a0502554729be7dd41ca38fc8df69c032d4cd4feec93d09d00a87a0014880c26d1f6418e070c7c1f25165f253792819a3b1634423d177474dc660a09188f13c8ae10ed5084100ba8030bbf47b47d9e262797138878372aaa20b6d866c12618e2065642cbb6ae2eae49e41f1896c89556250f8d7d9571c7dca62768cf59c3bfe82ab1a107ff8d619bf17b8833a61b51bfde48b85e705b1fea0c151230caff40f8a9c399086c1afd95ec33db92502e3bb3c7ac5d0396b545344ae9aa6416816e65f14bc2d11a07a0b1c8273fab2fa98238acf967d5f7886a11b49dd64e1efef50c5cceb15914ada42af346fca2f8018736a847d7043a6d52134b4090780cef79b20ef12f27de40c81b04c66a699160155f18317101f39a74b329fdf3ef4d274d1f3a5439fa5719eda1af6d319e460dd2f5f33442abf4c91b0ed53285883269438850f8281ffef5954248f9ed1b513c05ac48c1921ff2940a52408468141d91f87a647959f1975ab8ae5c54e0be59a2fe8d0261dbeaa2e4aa8f8bc1e46ed2428eb3775bc5f12edff7861316ca3abb5f3dd60921c0e51d7dc2e2d719cd4cbe6f64b83722fdbe0ec396a6107f810f77fc67d85d0d3ae5e01deded7927edcc00c625614a8039ac13a34139177c17146690ef7cf9b594f34b3fbee93bacd2b077a3f6ea045786b0ddd882b8c0bddc96cafaf6a82f2e96f4dae0d0f73488edfa8e3843c4310fc86db52476ac26c8505165bb28df08b2c87e39c74cd2df1c55aa8e70ce9008934f0dc54edcf0e532c17038cfce8c26921f0382d2c324aedaecb452c030ef875e768aeebeb6c7ff865201a065b275216996564a1b272d430ad4009d793173c485dfc013d78eba6a111bc2ebf972371a088d95b04f791e19f5bc55e684ad7d9796bd1f24dc00ba35e0e8379a999ddf682e14484ad513eb8d8d839307e2b764677ff7b628472e7253889d8e69317566ebba94231ee46874b20abcaf25a98b318e5699dcf934c8971ef6fbd427bfc4c1b66cc2983155eded779433ca233f4dcdd34d2524c6c3308f561560d6368ca58883b939f775eb06f8b6e2f27ec12eece4e0447f37f7091ff2254ab6a19a2ba57a694e317eb8e84a6b81e0a08533fd946b51187d2b22bd81a33891e1288b41043a41d6e59b0668ff669c8240282382c132e952faaa39e5482434a3acd2c04142d9cbb91436af617f4d3e5bcc7e77257545a0d790fd041fbd4feb3fa55f7e63e5d06e1dd95e5a665c0f6ec3dbe051c347864273f4f9f1916f927dff18dfedb4fdb0a503b0d8979affc6929ef95d2f589cb2c95d71cd9bbf18c942b9cdc761b22028fdbb0eceff131c5b142696631079bc478c14ed97373c8f8dc7562153349288d52e6355b41a3ce38ce66eb04089fd0142141827a78631b3785f656773d319dd12aa4c422594be7c282e65803c734dc4d6458919fac94b3c658a465b8af63e4f85635e5ef90ef720d230fb3d639845353cad740efef67aeb0ea8eb8b48fee33ad307651ba8c0724998ff791700586d055c7fad8e525a9f3f54305f0ea0ac0afb39b392eac48e2687bd7638127fbca3ebd532dd88ab15674f0263fcf42010a660954c187e9dabd55ed86ba1089a8b430b59406b13bf47179180842845accaa0c021e5e748b8995379e98bea88b85770a9163cd5dc319be3c1d28cdae53ac0364f8d9bf8665e8a2378d2ad1823417fa2522f08135c562d275a06afb6111e733560026781f06efff3d8f2fcd1c95bdf63964dcec607a0f55972d65713d0efb3423a050e56a9d3e2881119405d599bdfc951ed409147245c59476b31e1454c676a7dd79ff7f740c719b418df965bd9336c3fdd39e84f32843b89a416a2ec2e25261690ebb74370cda6e6acc7bde72549b0e68c546cbdce72829b7fa421da283e06de2b29b30d24489fd3cca6d925a225ef10cb1ee8409354d678bc70a87903f8a21120394f73d8b28c2b42fd75b48f3cc89f454b106e225d7eaa6127a8ed1948de4f467afe63708de7c6b52241ab24da3bf3c9dc48fd6d5c7f67ece6583234368e8428cea794ceed48eddd83d3f7d3ec2e47996a3c01e8e8617f10456b4ad4cf055e140db74cb675eabbb2f8557c23633c889739cc5df2796298a82dfa71b99ca57b97225acb10bf76d67a03209bb35766ab64e2e554528c4a9adedea0f83a24656be5f22e3c472c4e22f890a323e0cc88d3a30269b01e93f778c3816fcef8c614398e90c93b0b17ed04b8ed9ed23e8bb5eb4628a7219176e6a527c4537ee103d353748f5871c737aed34e84352ac80e577aeff1f9fb7ec25a0847f7801e7eb7e6e8130e089ddfec2f8b0f80782cfa66ffb292aa974d9346d85a04124f55016b0fdec6545b65f299fd216b9030b1083421895522a150ad97dccf5e52e780945bd88d60a85ae290d4393d06c3a57a2b44ee882a7f62457a9adafe1b1b60edeeba7f39ea7e37996fb0f71f29138d347c92508a7ed44323df620de985e7d7327fb0410f798f46250262ec9dc3d2012153d9ca022815895fcc38484436737f7c9c5acc4cc27a00cf060daa0a57b2498e8766d882636fc1f641a0e6587e0a67c57f153fde09f8d277e6dfa8e7f5b80eb0b1d04df6b38f3254975fa2d60715b23ad3524287b5bd2c3a69d00155f831f7a98862bf1fa3124ca883f23ee689d1df17a5c6b168a28b9de93581642355b9b1ad93c1e4b39dfe0711c56ecb77ea080bee112d830540a1d7502927f6b3a00b89b0210ac86540e8c102cf9bba8ec88d5e215667c1179c72e65875fc136f28b72306090a78f380cb6e70348d43a5d2d85f4cb67ad5fa1e0586af72be188985473ddd685e2e92352d9ab9cf6de22de371562c13eeb8702a6b99221d7e7893ca9c886c3ade1a2e576cb9b55e3fc0b1b7e13fe6c7766c49df4ce8d584f91b37b4eb748fa021f20ffe714312c23aae0bb5512f3dbe68ebab3619c17ba0e1c31b5edb06ab5bb3b67dbe8394380f6302000313f02e174b1d8966844f6c68b2c276b6d2d42a12676baee7ca1181f4edcebff06fa5fbb4192ecde9ce6df21bc0c8d7d633e03e337b54c51f0903f39a3c8ae197414ca7fae17b619f174a729cf96804f0c5eccdb1bb7387344a1c3c1ae08c42d1fd4e10603300000000000000013e6bbd00b3fcd03c5543971fe7d6d44efa71f8651aee0d76d219fc9df315ab30cd9a3ef5eba08aed12ee13df57477ef3ac9105c4acfd4b998abba12d216b7095eee5917ed7a8e2c1cbc879a382c0318e325aa248210791d0d8a146820e2a6ed4567df099eb74f3e30c4de06a923f63acadf9c984d7f6dd00efbd26b1e4393385636176b66900ec51287cf880617fcd834c5c0f3535ab4dd07f1fc900a79ed938df03d78d719e0eebb2e64efadc610f48cee7a67efd6137eabd5205d1e15c0918a2a0775d10c233184bc018c2efd0f0209bc586d50a25e4ac2a6f48551cf1639afcf96da9a38a75eda66cf5ac5705cb0002511291c65ce6d4c91a9afc29c1b7cb68d083eb890530a67ec5a3cefc0b6241b141489ac31590f89d0856ac940e17409d948908aa138af1a240bebdd9899a576bcbe9165f1f70bd81074fb1fe3d908a0e56efb43ef4dc4e362cf6e54f9798b0742607993bb79a670f60c5d5b5686bf8aa3dbd06786922bed7b1b876ec72b4f3d7b7e02ce8e76855d351cbc6ddcadf55fd3daa5b5a5594fb1a1eeace1b4e8fc7dfc97c47d1ddea3fa3ae0c7e6740f6f0ad65a22c873818f90408be12a2d8cd48ad6b8684a5490918bf531dc9e39009e72a4044a6d8e0b0499e0467ca916a4ee23c4c26367070f9d1614780df1ce534cb5d31407e1189c7853ea3b92b8efcd647b6011dd75f255441fb99ac11d0d2c66f4fde61b61f1f080eb65165df6611ccd5a4689dee566834a5a2eeaa00a61bc6c34b7bc131e4146cf7742bdb6e9ee3ddcb0196e2010baa1e21a9759d048b5f991608b330c0dc58eaa555989430ac237f0223ab993f3f60037cd1f691ad7dac9640f1e885cff5badcf7912a5c83816b2b3b75617e48274321112ff47b55a03b49344d42581a72820a6bbbd462d3bdfe7f682cbc094bf1d644cbe9ffdb72a527148f7784cbb5a49445f91bdb7eb78da4e4c2a8bcd3eb147ed8595dca2249052d7f1fc605f7043b10e759682fcd9b64a49948ca2065f3c375de69e110bc0b02874d8d07515be346005e71ae7bec22daa67c0ec08067912cc176a65903f282526a61b3a5396bdec4737c7a9a01048f7734af85d9490605063e8eb9c2cb72f8b8ebebb44b0595126fe59d447e0d2aa12bfded472b176e471f21d10b0108cc48905f5ea325f6dc0b74227ea431cb788811eb123b6e2ab5618ded7e8bb2d4595ef6843ca9a1fa08785aa9d3e5a46f64efad9c3fc0a87e9fdee09ed96feae0f0b318d2815bcb95e5f61bf30257f1c063a9d01eec84796154bdb3ff0d9f39cec67006bbf69b2611fd352afcfff6ac96c6cfecc4392e0e82be9603f27adc50f9f464079c6668166692c3a9bb96f2ec2f8874b2ea7b3b4df303d34d3f92a0ad76962bfb20a01265888a80d40af0c37ef3cfdda0b320d8f2e278c3de2847ab4a778a2778a134d50719a6eb274e53f4bc4826baba31ccfd498663c62324333a337ef9b9bc3c1d32931f3a69817b16ae4eb4e6bcfc812025c5e845c99fed64ba24a302d3e881cbfb9a03882a2e1a10202d673c5a4d169d715bb14f6974a4c55a64a783abd11e0de6bb15f7d90f63994431cfbd9b13318e341fe750585c32c43d18db89b5484800720796d92c9a106f4093d63318c0ce722c976749d3039f4a8555f93ecc221b0ac5bcd13037b0a6d34b4b7418e166c8465f33f89dcc678a22ff67a1e3e80d566a5ce4ab3a1cab12bbecea58b49352d74b11b2688fd326cb09dc9f602bdb70cabfcf99e2f14eaed3cd3b5141b5290b2ff5300fb678f431ee24d3484fe097d4e2db1b14a3be38ef6685334b9adb28f237e4e9b8de49ae60edbebb0c650f9b99ba242f59a53dac327497ff3d8f4dfcc087415aad18f0f903b7e88f67066b360b9b8a56ae3ac2762e9d2bb2da17f5e48a316ee86338be0d4bab4075e9e84485563b587d766e883ae3832231e9a67b7f591e3c8062f8f9cc5ee7b66c5853e50ecc6ec9f5ee4f3822755fb436001a7b4266361363e796557e1f5280b0f8a7519b62233250f4fe7c668a4a5a4b567f06cfb3dab497c9a2794a2306e747aed3eff5c6aee13c1f339c80895b7ff824878910d4b905a6bb1d27a2c933fbf203de1dcffda968c4109c62d9c6bd6330babd4e0ccc3ac90a70bf99aac6df777d70768d8f13f7ef73703ed7a93d780e55d1b3d219f30060df68544fe0bbad3a36b46cdcc3f5b5bdd927d649440a544761c06d5a29a08a5915a833e58f9b6210f7954b477363d11c3c6e530fc6c88bdcb2d66f511ff96658e7a579faff55ca29ffe5650906b710c53a751c811f318acf1db8620d676c4d1c95e162dfd88d32ba89928ec55b2e43772ca29340106cac2797ba93e3c2a0a73fc17a761125997e7f4d0fe6629bcb09a3a8cec45e6e74d9cfe846684fb1656e92bcd1209cafb905493967ac84b3733e4a0523335d6742e26894b8be9f327f7497ef0fa44cf58c4cced064b2f4ed34e0583f3870964b5752aa0d9f3ce78592cfc56482202530ff465368732f7f1fa3d716a17362598bd76dc1dc64dcad320b1f9abde2defb05e1a6dc4283f43c348628cdfe3d77069909fcf74848a196412b461f90f36906eb0b87df564eb8edfe1d847337da95ff429fb824836fcf402d8d969b258a385ef0654b5593ce1308b0f2bdd9ecda20923d811b522e64c28fc795e1c2b87f3b0fe49276b706b834a31b1d216ca4f0adf5acbd0c3ce407288552fa7f0c39f26ab32ab2317208965263d6617ac87b910c5f6f74bba5bdac0556a1dd8d115677f0b82da29ac9683c372d06616a097e354ac29a838932decd3fa19ff6a6c182d00e1d8fabef9f24124286cbd15570aff451fab3b4adfcc58705f4ef9d7615a24709903df188ef5b8265a06442667a6b6ed67ab3edb5c04d65ff5e2292cc6c3f23e3e25d1eadd8794c062062c6377ff15109d8fa233ee9104bb192222e9a8546388a304e0453ea699d944a77a93a8fa05a1e33038c6833dfdddbe1a26d00821e0ad6acd3cb252c648e1f5ef28726eaa4a261a07828583737c965f1f148f8f5e4a9f75ff2042e9c481197a679744d1e189cadf931ecb7a55165c32964541235f171205f95003411e0371e4b2bdf1af62b92dae419e02be72673b172869440138491a5d2772ff30ce2c5f2702cd2d3cd68a4f19f18a790b29c7623cc18a58a11a5835d7d71e025a354485c4999d4b3d48b70daf7f7d9f5f5ac00db0175335375f5279f5483bc4b9ab58c87fce062f34a61ae3970d891f9ee873a8688bdbb01b5e481bfd66534105c449badab2bd5102c2654b2078cce3e08b40d0a54834cd9ac45bb71fdf81e982540a9fd2f643f935cb4bfddba0ab7005663e2c8b180005415b51bde44c282adc38e79696d5a30fc321bb9230f2cb63d70a818e83f7587681521cfada9da95df1b6248cfd366bbf30a5b4a45fb33c9cd6963707bd9cc7868484ddc6f783958da0cd2bd417ea60c81cace2a715bfedc7dc39915b375d351c6dc27c3fe0972a14c3f0821830006efc61ae89b9ac04639f491418ccfd87bde10932b5b1ab4d3ef72cb47d53fe03ed671059d7ca5ea9a856dab67fa0a8e7d2f38819d82ea8e5b42257780f21b303ac26b5e01a77a68a1b9396250770fc71858e2a2105d1f32b43179a2b9de6c60550def39b6123b0414faf661a23b64bca3e421ae99799ec5bd824f812843d067fd167255976ddafdd63e6170859a9f4ab2c34c26ea2f2eb248350207de653bc7d3307553301ea0516aa8cd782dc23094eaa2790229fc706ded613a0451957662945dc5767396a027bae1274732834cce5dbcf4bba0088fec7155dd81ecb14d14fc8b08e1ba419027b937f569e653b48597d3b2682e67e9fc9f41105a0a6b6c9bb55f771ff747a4f3159320cbb37ae10ac8f18340fee83ac1a134bff14147003e3ec86db22916aeb0de7dfcf37f492ff3bab7cbd923682390c597beb3e20cd8173d50fc1a1f36e9c39ad96983a21b7d8c44a8cfd32f87db736df0b2ac38cbef5a3f888d25db819657f3d224aa6da92969133e5780951a9cb46ba730297744d53ca80e04563b5467f9ef87642ba54ab1d426258c30ebaaffb8fbc8f0dbff7f29adac40fe7e950bf43dfed05ef67db46a9483e3edd705fee5cdfffc8bdd818bc60c92c0b3c4d010772fadc5738ccfabed0418bf2449ddc4ad347e7f48491e3f4921a6b42b26e9ad3cb46469c1ae75c5aa9c75824322c835f7b69cd4b2270439f2f628b87399e954901260517bbf9af03f10ee7f963766e1ebaeeaa975b942e77acb6acda06b427ceaef1c02697fcb9b74d97d9326e8953e1c75e300b2f8aa32287ebcf6627631d1aefcc7ef5a0cb751bf8be3a8092e03ac494504e1ae9721da86f070824a8309532dd9aa88adbf54b8385a647e362928982217422c11181c46a4cb03b46cfbe7ca771bc12cf3f8ed9441a491245bd7cb6b57814df1432d030285c5d20ab2756da62d4ea40b71b5e356b88b9814eefd8f952da762c2c1dda56038fbb075fb8e73ef7b8022f52bd733e170601a98c72a6ba7334b1a23613c37ec9e01245b54462c3b05a53cef84a0cb814fafadbbcb49987688b15264898362743455e10415410e44da55dbd6d7b896239699499b840707fca75b3fd4abf882439324142e5cba81b694b5b03afbfcc71366626c971bb86095e8b9211d6c2e4ed4587b93ee4b407e20c585b549ea0a827bc94d53a122087c9c377f1f905b82f537da12d052339826e6dfaa62638777ca29d2b1db5a3ed2a7a017d22b3854a84b3818f529d5b63c634fbe13b15b95da67ebd9fa18f7bb512cc60b4083bbb8f167f7334eea1df32ae239ca542c2afa1e4cfb492485db974ce5b86bffa33f8fca963e1849393b9e789620560f1b63c15181f0c14a48a15d6cf6d805f8881afc43b8a288e3e5023d29876b7395c8a3fa14edfb774b84f1fd63ff1da7adca77baae833f7e453b9e645f7dbb71334b90baae3c29b24bd61c8eb6726fe860d96cfcb7218d4af70fcc18a1a761e05dafcdd4f5d7a9f7650ed5a17c88fa370d9eb75f629cc887740e4e101cb8f6bac35a395a7bd41b17edadf4453be0d6117e996be703d0280a5ded3f15b8639dc23ec85bcf5213620f535bc65e91efc376f13c99572c1c136ea70989ca3861738f081016ed94533b835b2436c88e905d3c09374fd1a1eab7232365f0bbc9ab786574cd0ea30aaebd452bd3204504a2f89e0cac36f41ff387b69ba1a7f1efa0371ff3a180f5caec19e5b3be68efaa244b9656c7370a00b8fb509bf1dc13bfb93f1135b3dd9c148a820ac0b1ce7d3b252f84eebd4a9ec9bbfbada54dbf38495a087ec641fb3d943b5b4a12d2289ef9404cf5abe839044d4b6b018db53feef61f6d62c88483b76d4ce5451b551a6a7902d3bb98b7d00681b6cc2409b40aa9e74cb745cc10f7b3b3e45bb81b007d6523e7436a745390c63aae4543ef30a043d42567a5ec437be0d916edadd6012128ae40d82dc332aad95ed8e84148d1f58640fcfb6a288b3d5b70e7f4a62562274f0474d75880f31da39337f91751d2c9143f54cdc055c7671e1e110a26ee70b35a8ea9fa6e5d9afb72648356220ea4260c5a52f44184cfe1d08d6a31494c83eacfdfb3e334cefd2986f23af97f508c08e6db9bd8df1b382d92aea99e7151a4e0879cf26628cfc28136f72c197eb3f6edf2b64cfa190f218b4cd8663d177c477eb618e2ca1f5daae819acf332e90bc44781aed902e73d41948b162cfcf33a4eb6935f29cc9b10ae3e48c149733f934782edab57e0768d5f23119b8b2e85066fcfbfa533236f9371b67d96e5e2d26d3ea5a87d19d8241077c4658d4d602bd314600bcf1f1b5bb856038a59668a93681f065b65e138ab6b4a630200030245277cfd3145e833f58516581106ec228263333d231e900be455367f2413b91cd4689ab49c3a4ebb2833db5d4f368cd82099d5837b05617ea035a55ebb716e975202ff49fb67c86454f04b2b5a903edcfbed720ff26584b6bdfe35f2b47f37fd4e1060330000000000000001d90fb30082cdc3d8bcc31e71003547e43c0fcb9d4f9ecee9b7ce283b035173f4d8ddc5e8b7577a9a5a8f31cbf37fe2a3ec739963a73d4559ead97fb1a9e8f5b63600d0e052fe0877262b0baffd22f96d1a26a2172665895aa79253839c89cf9753d5cab06d616b38bf143c0555ef907c404d0f43e3715876f61b3889afdf9e3bbfd73917ce3814a538fac53ce7becd9cfdeed8302530d4e66634036237b8d34c41d35e42adee673d499c65792738b50e45baff5f091a3c9e0a23170bbaa7626ef45b4284f1a34a80007decb996daa061c98582eff5067e02a4fafa5ab9695869df669c66a0a7b69c9c63cb218ab4ac4635e10aaa0256e0f0e12459df00237afc406b10784ee264d7c8108630f12948a7367852eb2a3682705b0623ef3bb9f46bf2a7ab202f6d1c4363788abe05fecc5c6ef67b37b4f69c8fdd287d66e46da6212a6c3b3cfaeebd0164505d854c9d892a61c74308a01e3886a483ef70894b3e584c8ec005920ba74642e7f69125d7372824e426c1656cce141e5b074b713eb4126adbd2847e9ef2401c48cce6740b86083d582c4dc3be3468f3cca53b7005b8ae6ae5ceee4a0e2169f7754fd3ede4015626422830ab42e4971b55e04943d3bef97899ea321bb97774054e8327a1296d3f13d46d60540818d9d010f5fb0e04814b7bbd7bb93db28c14d2cdf77576d8c706c7eca784e8c0827e45bea9fbd351e3b3b094c6b017a3df0dd13da6d226e43cc97d8ae00d289d61bcd10a7fbc2c0f6691a4693078a970a6a7db4990eccbeea4ef42b1289d3a0e93a415f9be06bc4fb9b87d78b53b099aa498e17d0530c30bb648a004df430aef4120d0ff4725d2dd57da7c7f85eea4733d51a16b082bedeec2f5824fab8d9ec80de220e7e97da626a85a421924a713c3de220e84ebf215c0685f9ea1f1a2ad9ac6731d0654be6b43e50e7b1de578eff78a27b96f13fd7d7e011783c89106ccd886a695a554e4cdfc62c60fb788071b992650f9c66a3e03dea51366f542a9b9cf3cc778281915e4501f9cca2813fea036d816ce968700d67f3079988f3b0c8994a40d86c81594051b7a08db7c270072b6c355073fb49e40112d97fe99d1781e93bf4872e1950b7d8b47edd12fc767daef314bcf9f8d5c0615ca1d06b29d76c0e7b95b78514b9d0c42415f6be5ec551fa2f88e97a0e4e458b7d52a9df72787a7e66d2122c64389196a1505d7125c03f88c7627271f140f1787f32e6181dce9cde6eba3ead205d46d7847dd592127efa370bab8dfbdb32a2476005957fdd31c92e29bd00a82ecd66896ab5e0d62ce98dd99a3a8e38b9d1cf491f62c5e526b85722bedeb6df7aafed873839bfd4a5e8c1b53f4e82f8bc2e11577827a5900925ec9760e0f148d1c88d2f2e5aadf3da38fd378d43135495900fbec06fcd2a899e2c1cbb88b381c9da19361b3f6a09c3fb7c27e9fa7f51acd533a04c8039fd22af83ab6e1a5f24ecbec7e73ec3754142ca88b9551bbd9ab8aa42282a8d98decc2eafb3b94964667b270fcd5a0d1e60778cdccfd07fcba114738d961cc11a58434ce7711f7c2ee18e480c921ee54d4813ab8ce76f41204dde145518eb797c4277b23757578f6e955739d7aa7ad705e24ec942d615da533b3cbef100ab05c453876a41f53fcb87e6525467800f2e527c264700cb58cb87adcb262f01f2c8a44fa506784e83cec9dd8a7e4f832a7789ca45e23d1d470e8ce8e6bcfc3d64ce96dc6b891864f715c90b821eff6436e19e052fd44ad9254e2b2e0292eee61452345f836a9570504027414b427255a891807fc4e2de1cdcb06ff346f985b5713e59da6c4043ff63b43672dc77319ec4efc67572b7aa77680d068494fffc2386c705dc1d4de592966627364cf3d27ecb1e2c917c801f080c9093a5cb623b308f6436dd7ec6321085ed697096a46fc86f4356ee193e13b3b982968f07a278ce6edee8d71b683d32646ec655f509d6f4e81163231fd414d5b53cfd5be3459a3310f67d3b3e4d7618c3ebe2b19d870a300e702491a80db684932af196002c7a0313f984b326d947e4d342b1c519804a8d1e98c643a9a2511c669a04bfdc8c8bd2abbd4312a4d7eb410948e5ccd491b2b587dab36e18a666dc19d705f4fc4474aba0474415dca00e4ab57ace0d177fd4b423e873bc62022e5cadb41f3f34f757c6b05f8462a493232222974bae1ae0e8f92b2e1292a3629c7ae03c372715efe25522d591a365c1e34c4434cd0e49d9e8079fba0d25c4ebac537489f652e4116bb1ea76dab8295797bf0fcf8362a20860ef0017817c85cf7339971ce8159a19069fb0e2cf10c71d826726bab6307e14c3ada75fb4fced4ad6f251ae5b0ca1262c1274a178f10925475a1bbc9834857bb84200f0bc9b7aeef8131436fe1ac2c2f4f59dc56e01740a172dde912bafa2dff55a7d7df20e4a3d050c4e5bd1dc17b8d35e1b431515fe78b8ef4b63730cfd62f01ccad9c789d5feaff2b976a290defdbb8ca7d9e756670fec75bceaca1fd4e3601a4a73c69eb7d04040c66da1111131fd67ffba3fed9974e640955e0e428315896eea243d29571bfbf912ebdcdd61482650570e1b51d086c167935d34af1d2b649454b9e5c554578164f28a5733cbeda33ab9b0e63d21c445da1ba1c4aa7900f304e70819281f654e753fba245c3f86e3df6184a6632b306a63ca5adf65a91e8a5499b72a31755ebfa33e939f29b4610d970008cd2e988052854f0b63a0ceacb1c78edf8a25ba79628770e0ef27cebacc42f518d39e5560cc16cb7ed5e864e33dec817e6401681ba1348686a99b1a56ba8a9d802a9d0a8b80998d6095e3e810a101aad45a7ba13d46857554645a0405523058d554e8bf0518de32d829a5b711edfc5ddf727a1b31b45a31d1860dbef29522ecbc44cc35b28fac20ec98930e6e00d968654ae73a88d89beee6014ac3287c73676c1e8160b001076dfc9d7fe56ce6a64860d0a40eb5de4d4658dc4c6e9d1beae578cda280a4a8ea8c31a811d0277606a34d0c9c06c504dbc8f4d936419bd11e6a5c0cbcaa4e099bf0162aaa6640f0be793d8436c3ccc1afe0eaaaa3af977a68163e4f43da722bebea72607e4c0b04356690cb6f2d14a079aa13d2c742e82caab8a0e0df28c3e567dca41900518eb665bec775a0948c2c728c7a4d4f943c3b376f054598e9ebb5b4ed2c368457535404cae940e421560fb238507d1b012783845f21f859e70ea7e67a445b21a3f8f2540cb1078717111253bcde10f6f6e2f20831aa58d2ce549fd0bf975fb680890fc45b8113fc47b721958bcb573ef506371ab2ff46a2be143437eec3817f1c2bdb1130d449e8c9618e32ac5f7e7e3827ec23860eb1c0b3e7e1082fe2e6d9aa033ba4940660dd3bc238bb2aae83db0eb525ee9d73cf55c156f6f318231ef8559b3b7a3252d424902a9200d992d81120ba16f869394b1905f28326c7feb0456ded3b7cc08395a951d3792ef0a5cca534d265c758cfde197638aa3dad8f3174e573223e04e0c454375cba007b476587220b1a9d970d271faf5ae30e9629265804ddfa72725f65bcc200454cb9d19dc6c7bf256f78adbc35d505830f35e1ee45ba935f5196fc3319797bf0aa10c6c6510a975263c33315da6fbb2e3bbd7be1b2ccea66f98feeaa1415ff695e4a8513b0c9f0d3b1e53ab18c647c819f14ce91c3c9908a5e084d90c3381fe4aabb2b2b91ed5298977aced4a581b2ebd57f27aee01ea2e2f7df5467fdd785b3f9c1565b5f53592a7e9f52a194cd8da97fb9ea13b89192e045cbc60dc6867e8b9629bf85ab7dd1b74255383890ba267857585223ef8cb7e548a5555e6254bb8b664013f8d55c8383f90fb581f0312f351721c759999bb4ec59e01aa8d31f85a95aaf68f09238529a34402231425dc6b1e750ad5001022d3b985cfc18db423776c943552381c6be6a4cdd27e06d0093855a1069dcb64973cab318939a9d8fb7b789ff7695541bc5971b234de1d67441e0b260c2e0fe9a149282f682b18118119d83902e3f76d25d7f4e258b1334398367912d6dec20dc55b988e16b8d64d9fb50f3d9c923b7c11a0e40b392fd0928a5e7c6a458b03f31151d07bc9fa32bd618fdaeff6bacc741e50a30b3828904ceb6bb36f45ef70850963b1c7c84a9c64adc12f3a7ec898a52909a66778b7a0c385b7ff7d1e4909604ca7e9c42bf7513573c4446b20a21fce73bf8e62d5611c0155b0c882790745e01ef963de994231c2a2b83ea1febc26b4b6b17cc60b93b3602acfaa04d6e32ab78f807aeb217ac9fb086ac66d4192003ea4fad837833375d4738047241d99357864a63b01528494246a98f6fe62038b7519d6c29bcc54ee9a9f7f822d6620fb0a53593d259dbfe3da4fc359e5ea9508ffedf3787190df4b4337c7400cd28140b4425f230f3e103601ff06100e7d554e394d44ee3244b9752ec26838650cd618aa8f90563fc3c1f80c11f7a5ab41e04c3fa857e8b61e3788ed36a9b13220791b2c9d78ff558e6a92e8ac3448d103bf30f7dcc63401eba46988c40a6500ab54e76d45f4cea3c51563c0808a47834694ac2ba9aa3a38066d13e8aba1f102a943e4b78b11436457054ba3aa43ef5220e2cd92731ad0a8628f1a50329947e18ca7068da5995b6644e4704c760897c032c2a8af615cbb147d9f5a55b2056094ab762955afba57307f60798bae88d50851503bbb09775123eb18ae85e02476da674e60704bfcc21e63b30be2583ad0f6dbb6ccd09c53010a01a490c6c38a11f70f6bc3a7b5913de871d331e7b5ad37261547335b0c08ef485710b02a972ca96d39a20f996b2de57012ba2c0286374ff27815bb3ff41339ae2532a91bf1a7e022a3d4a459e86946389b30f7ff4cf9e37d2ad81fe74c17999aedeb7e234682b78dc839bdc907dd55f86a756320db6de847e41851ff1292a5614db92ad1e30b3ccda9110d67b8547efd5a8335119ae48bd57737bb4497555c3343fb05143b48c574b539ea60fcedd412e48259a24f05c55d945585946374e55404a056cfdb3c34e977d0680f0075967bd80c284697aa1c92ce1d004397a4b7d83141a438cde88227b129c77284968def22019e1f6827038b06ea67ef84abb1017ff3e604ec4a86f223c2a14406b347d9f4b409e8684924c9a9fc5c6167cade54c5c45d6c3717acacd8f7033a5bcf2c5e9a360df4d927d40021c3139720a23b2c412e53569055fa8348febbdb6c8779617d1e394c295972f14bb4d43a79ba2c60fe5b097fa6c12efff38c0c3df18977a03982687c64772289110167e8b3aabe2bf9561f5175c665ce346ed19c5e026e9ff4cea0ebd45252724031e53885b6b5b72a1eb8e291bca5a3ef8f65173a6f69d75356d254cd2d5474c66880cee55db7a71b3053b4645c1fe8074e5f6f9ddb9db7eab1d17a7f97c918e65c2f649b656dc3569522cb633ad5ae65ea2b31b1a6174d74ea1b8dca8d91611fbab42f1ef4e5524bd687d9d61d531796ef6c715fe741e10b8244898fedcc56a0babc46ae82ce1bef1407d94a3cb072df6e6738b47ccabce0228e1a9dadafea96848fa34887898bb3b859b3b9ec9fe7a63b380fca32798d377d3ed7c93c713b83947604929495a04c37fda61a63d3faefe8388dc01bf5ba165f0a257b56cdc466817221f611c5c7e93694c715bf745058b5e3ad16a0f8eebd3abff381e2f062c020ade2c0da968231ae8992240818653162ad941df0da03a6c874ec144da06a6c9ccef17f3518123e97138db4d47c649bb87ea85b7ef77a1c9c387d013af53ca8651c37fba4434714ad3d9f30cfaf7184bc28a1e849fe40abe0c67f6e63f79f2463868b933ab3211d47b916913e1794ab599b66633f6521cd56cdc9945f6302000347a0a7978def0da3671593c64dbe50eef5e39d674199a525ed394f28bee17e4b039d652cfdc8c7eb4ab59f8ac70ecbc04a6ec60471c33a3b09b988fadbcee17ec163be43c0cdb5bbc779ce0f5002f16007f057bfab6cd38dadc529627305a678fd4e106033000000000000000178d32801a33df71838ad5df080777f3724cf6aa024849a5aa560f8d38b69c650c1de58cb919f6ae215229ee8802466d8c577103eb8986d0483e98c0898af2075a4ed0c4e5e019af4b52ea31e4c84cb4eadc5b1a448894e40cf363fa36476bba4c9e1d2cb0b7744cfe2a275cd32373c6656e2de0f3b7a3e8be08bc397f018ff9665df1aa10c9e966815eb4fae21d411a3100d4e6a506931d97c3662161c007bb028ce01ae737c7001cf23b476490e8654be568fc1ce3852784ee38859db69d84701865affd9be414c8c97cb197c82945e11040ff9b0306b2c28a1092c938bb61a06e2ca1e9ff27c764f0d998484fcbcd1bd59edf8a6ade43b7b6bff2ad1fab1a2efc467d28300dea02de23e26b49048762e921c5cf07ea68612ea088bebd8a0f06e0df53b75d714998e79b922833ad2f28510ef306138830a839799359d81c71a9c801564db7a304ff35df9429f8bd371e34045e3bbd07882e876255dba8b7e50461c85457866c773dbd29c07aa72a4dd28df24b9d167f3cbd4f973f7728432d270c0054957a93696ffd04b9f13351b219e1bd4d37ca9ae70f251eef2885fd46107dae7acf53bac873e54d100c84fddbe7e9bc62857b07672adfb349c61dd13e873706211578dc661c3e32fd7b96919307c20aafa7c8d913648117efdac1aa641120ba31bb61f6260244fa1db945fd9497c6675c702788713a91a8cfa4328261dd267ec6e331dc016507af174f58e594d0311de4d59b98de4486b6afaf64c622755e36591af26d8e1c28dee04c3cdbafcb105232bf72a889b8ada4e44a0c53ba7664b843e93832100e486de29275c0df6c3f4fadeeca404046eb020f90be37761550c439453fe6b713332bef98a8d70e1724ce82e939f0c439af20452b2ec997faa52ad287d84a98716f3f38a652aa07464c047c0e8d99061ffed1f8efd93cf2e564b9d6c52767edee7cdf45a735ade6bc8cc02827e934acbbbcef38fb780797b4ff4aaec1cfd58068b90380b9c7519b770c1155b484eeef764e767a8e71152bb3fd1e55a8016ccc5ee1c10b144885f49b6d346afcb9582afa432d8397391a00100d0ab7c7adfb7707edebaebc4f4a478f0868a4f0b0097d131b6315e1029c6fb8d2cc92100fc306ddc86ef9f48458f3c046d5d89ad34fd31fd3e6575cb179487d17534e55ee2870a59a805c32c7b0df8893c13d7d3156898baf9193e517c364bc7a88deae703852e2e1b2564d1180d32e83501a95ff61dc7faea0c7b29c86b097c3900c5ac3cab6bd4e822449e49f5d8b29b7ec085c24876ca55aa535fc8a0f57c696823ecb71217db978bda14d30be6b922547354e269887f123f7f7cf0c54a62d6f669b8e8745330d99b5f69252cb5f06dfd6ddd78476798e612c4312dad7c2cafef90251c33c401b7279067dcfff09d6bf441dffa338b2ea7b8ff480d6268b36cce2f78fae07f013fb3bf73ae40ffb1ade80db28151c0772082cbfa813628b34ea8cb76aa07ac2d51421d4387c0fba4231aca198b703ebf7f455ba83f0ae1aad40f345885e54f50ef11d6356b5428731803c33570dd8a4e7270a8c9fb27929839fbd1b2b048270bb9ccbe620910ea5ea14b07ae6f066ccaf193332b3ecc0a7ea5ffc923c24f9a26dd2d35060a2869a3959805109c8fd453c2c9c97ac9f0eef632a0b39cc075dc6d0bc97cfb5c2afc9d632010444783ef60a468ea03de5cc0dcc234045aa3d2ee00999c2e9e51ea98c864afa6be5cab2e6e695dd8de39a7d78062ba37d09e92a9933d8ba542bd8abc3dacde32ab8d7a3c8dbfc373eba9c02045cb2fcde978c8e30fa39ff5a9122a21b23b11570b70b1df0ac36f0476ebe4f1f77cb6036d75151eabff5110996624d9b638198e5319b44483351cce4b03367520193e50c2e2ec4e9227985715cead0dbb6e9825db7c8f8a44404974b97383891c21c9dfe9630af78024221554f840c92bba1058ab92e2f2e1620b9290fc2d19d4e88e71c2935378b128d7ce3112b8993617eef37fc710f7522df2c23006ccb6258f32d580d96011415ff29a308118e3662625abc57ce33df203994c16cc3199707cce3f92e95f3d1c4597af790f8ce67cc3d6346527b4b99b689630ab4c21118c09cfe8ae00cec82d4c310f036befb3c3d608da2237c28b1b615c0ecb5157da7d67cbbd2c686a6c67e413bc7258fcd25cb9808007cafa9b92322f2e431d42919ee5896bbe47cbdc1efc1e97472aac7dd2b9a7e3fa4d1ee9d47d64fe851fa5ceb40d68200c0db931a922952411662b99d4b36fc673ba6d83574e9e50d0c4051a89c5268780403e2ead70d460f65b7c1b5500112c1f4a21bcef58c4d71080aff1281a14e8d177d776794cfbed090eba96d06bc624178cbed9869b1f18c31159cbfde5a87665e49f6119ea80c4d233062d26bd33e60aa4c92bc9abc5e8aabd0e54d8b1023adfc86dc0d5adb939b785eefe37c2fe76907662f376f997c98f938e60a379aba9086b5030d5574b671bfb6380b771a59084ecbe98567ed2578ec35a9dcc838c75e9d1e810d7911dc663f59374b9b4ef11971284e65f55657efd66827fcce8c2753e65c56316c4e1bc14cfb701766223269c1f2f3edfabda44233dcd4d07a52f0b661947f902c56a149382f958af1bafdb445ca8d9b9adb89bac7078994f65a097f02f2dc25476038d276a7e3b41913e04da2c549c11c6f0311e8a42299fb608adcc8a279b9127d7cb933128e6bb87b5b4a267b3e742885e9c5ef3373d9464820a458d410125f473115f0a3637287ce7e6a90d98db5396fc4ea451ab1d2b303d4ca022695ff8062565c25aef1fe0d87b56e2e0ecab1387ff7374f1321326fa2497690d890996968cc0c303d849aa36aa7372a72fafa82f68836c4ea9c57a9abe53e62bfd224df33c3c72cafd24905a807944fcb01f39cbd802c4f1722121799f484d105cb08d48182fa7bc21332e6cf1a961a6609e71ad0291ca810003c8fff433d4ba2215610c042bf52ba27c7331d7c3771982fc5452299fbcae95be68e4640d2f230b69e24456b861a81645da14a823f9a13a36fe923644749d29cbdf0a7eaf33ee3802aa7368eedb28a6d8351004668dd3f740950cdd4b1f64d6c893c35c1fd2ef937d7b2153c206e1d43dad7bdfcb824d6971ee8f86e7bee7ad7adac8b3700b0a46688a1ff4a578fabe06826accd4af71ea96fc6074fe37d10405f66904b7b30e987efcafe59ec473155a56f14aaa3b08a0db8d69c2c0f02e423d78c8e8265864f1f3f52457ed11f898133774cdfa385baacbed9eeae0ce7f024131d35079f58fab50938d985aa0ffe97ba457f263bd4f707df1c1463e636b5965540b10204a38439ef2405f298bea5dc5a6111a7e01f92df0e4c782ed4af2c0ac965f470ade15f3b12c580b8d6293e303b0751ce6ff7e3c9fdb2154d9d5ee7ce4e00cc372d1d9269c0b1774e809f45a089466f7923da57edd732a0e7b7dd22c5e96f197f08e71ce82551737aca6ac133ca895133b39c963058f9ed50bb860f88efde9701dbb97001c933954f6416dd62aabaf7e1dff56a414d00a26ba6cdd32171b5d3929cd4c46b64c44b961c46a131f69144e8392d38e57b81a77f803ad97f2ef4c8991c109c7bc24e5eae62cf6d81773804741f0591076fa9e4ecbe7809401b5747e94deecaa395e66564105bd2dd1ca23e38dc923b87d0727117d15f74437c29924e18722fbfa3ea12c42241a8a3e06c445d634631a709012a79c8f2d3447b28afc3795864bdf76eddc6330f350d09b60d8a2d463764a79f678c1ec67c6c33204ec8be18fd3a34ed719952d995d6bc523b5810f7be55ada28a62e968049979d529c078c0e50de151df24d668031906a2f26cb7d49ed3379aa6012bf24874f9ba5645085cf90288e3dffb9445fd86814a86c14ee8209d5864a2693aa03caebf8b5985c6eecbe4247facb5fa0067391025517477bae6447f9fc8b9ab6e81137f45c0fac937e48d9b7ef9b6b95ffa21bd1014be6462c3ed5301c156a98dc40d8eb79406bb5b4cc13007a8596159ce52cd504248fa6733cad4b139e70b5176ebde6a37555fb0b8b5feab24b12bbb4069c7fe4255a9cf4567227a1b724154fb17955e7e8ed34700030c0bfdc3f547e868bf438536e1dc646d00c3611c9cbad882c534a9c44f868a8765d0c52006d8582c4e326c198df35a57c2129cfc32058e054027d35bf9440e210990395618d70c634737c2eebe5f2f9ee29e89a8339832d5c47086bec30b4978fe0b0450c91b040496ca1b7bfe05dc58f5b23bbbab7cd4ed32f5e2b3daed9e3f4f1adea74412298f97db176c07720186db2367ca7202918535a6f103da4dbd428e83fc059789682cbad82332a625555b2923368eee47a378174b04e2296a3301812c51fd279fa8db4f06d5199106fe438817e9e31a6fd029fe542454996cf54d5873511897a01832c5372e08c765e2903d0e51a320130a3c4d8004df1e1b3eb05f034a535a79401be1f8672d05e5465e0bbc9d13780d0b2a91e8d991bd4ccae3fd993ca3b50d5521b969dd705eb1c7877a167287c064fedf91fcdb58e008b69b079111faa69de2d378cfed92d260d35f90efdcb4c019206ec9de90650ccf0b9c429fbaa9ed42ce2e4738e5fbf35e0d136a4f5cb31a04ee36b9bf8d8ff2a42738ff25c83f13e4c620e2fee18f317f70d2e92fa6c8c448f848b1ba2ce073a109632b5a3837e3e957b1b1c13919c1d3a90ce8fdb794307eac8f153b521538ced9f48272ee2f25fa19ebfe17849f3973d38515109acab28e0228500dda96d8623d5d489ce4c690cb5e8338a537d22f9dee7c509fac9e9016b5e67528db771f57794134c01a38cccdf0e396f33ef05bfa80632ffb32596b05b8f60745ea084112f2825b63749d0e36b1fefe288c70f3fd91930f8721bb1392222e29aca9acf11d74b6ab76a9e7ffdbcb74b654de7b9871dfcfb851d84f9258172522236f46f2b2c75716d801433cac66face73f424732dcb7e6aec8f3b888fa0e588ecb931b1604081871087ad1977e673b375fa1feeceddc18321a5c10b981ae12102f0bd7d2dde2671147a511847b528552478c32b9ed2f8165748b18a6721b432742ca9f19767286353006a63230afc09eb3111a3fc61913bd3491b4998d3fb658607371f8d85b93c0f10a73e822af3c2c1d9f584a5dbdd12c9f8cd5aeae358924bb540b66c7b6687e13e760fb24ab4dc4da168b314216c77b432abe55ad5829561539d925d067eed6a6af532f79943dec71dd787cdd22735388b43ec20fe7cac0666d4bd009820e65ebad70c9a46df567cc51a082b3b06d31b6418fd41ca084f72f889bd5618ecc6010371211e90b6fce11e90356a72c3874ead97591e63dbf1cb15a10f6860ab352e162dcf03c0d5dae274f952d2fb8b29175e88ac90bf657ed63b508a612a6e2343bbfb1a3ef4a84aa6b94e986c52c7311f78d8ad1d9d909f416b5f75a3382c8d98916d062280876c1f0e6d1ba8f00a64c09589e2fa8087f8aebdf90808fe68c9406e76c0b49d9e5c5047ac88eb7f68146c09120065fa11485cdd2efff5dbfebac93533d36b86afd00ab6e71d807cc9be47ecf24dddb3c4b4f03bb64da8d39ebb538647403e3031ae788e81892a0b6bf2bf7804d0f6a1afb9bb5a6bbd8d771ee112f858c0bc907e7558281f2f2f35c547cc8b4dd757e06c381c1ca71dff030a7c4be8d14f4b779a99cb8b44e1d923c2783b24682d11cd83bf1d9193a9e34cbeb452568998b313576c5699f38126060bf7ec868e117efb063c922a1b3aa45824486f44cd8eec669c810aa34e2e10f83df786759a964a9f79d849ba9b20bd9b82720264138507614a4d414798b917238c85b59125cd98737a407748f466302000360a02daeaa4d3dcc3199cf9e55acb1aa7ddc745875a6f94178e2077487fe83d313def8b05dbae094eabe5e1f5a609676feb2b7431f0ef021b35684d63df26844a47147388bb84fc5b064875f213f02530daf959590709d85033fbc59033c1fd2fd4e106033000000000000000153c3c7014f44b4b402ed6fa24968f3fae1673912c23086dfeef803e749ab81625a5964c44b48bfeea92dd7e1e60586483e0608816105b31a62a7adc7c3d4f25b452e0dc53b8efee0433c5e0fded9c38a97deeb36ac70eabb39fe1b89f5224a743019ca077052fadfa469096d50eea21808d08f5a4faa38a08d1bc90633023039f6de3e002611e9bdaad2b573a99f4b0780b3fd2e19e6b3ac6e93e7bc09aa4aff0d19a76100edf714a3b48035d82df2f62388603256f9c2055f802aac51607db2c335ecc476a44c6b6885ceb1ba79c994b6e8def8043c67c7e63d7ee73d236c51949afc4ce7d78580c2adf2a129377db99d8b38ff9e026a2eaeeeef4eeffc71ef426459713123d57ba9535d201292f4881e1ad0ae4ed59570141f320f663438fba24afadacccdcc3f84df5366e6133ac38a87eaa072b5ee362f053b5b609218c87119e530412ffb1e5439e9bddbdab7ed97af7666ddcc7a8d3576637f0088a126d8bf0778def345867e4733ffce3094e0265809faca89161e2c6bb3fbc37324549030bf8c07cc16735aece7e21aba4d9a21508962f5f823bf777a1d0ce692e0ce8ec6c14c3a2ff2706201596ed41fbca4f6da33f0839d51f1e8c8a31621e358976d3db640c808aa248329ca1463aef215c1a1dec5038d040d8e6d251f646d81c49861f4245aceaad0604947a6cb05f4b34253ca4090d997541b91098d8a7706b3e5fdb054c494c33c09040706d572787126d4ec4ae9dc7915d55737a3314ac181686228c6c5802a88a0a4df72aa7c8ae2cebb4afd6141ca997c3b5a5a04becee5936abaed832676504979a7df9b8a0b587ebf3adaab03dc7fa868117ccbe561f0796aae91c1286940effb1f8ddc0368aebf9c2b3a7c324cc3f746e14ed7e18049e4963f5fa0b05dc6d60f22dae5ca92bb93b880374817d53d1bab8b4c68d8d8e0e42858070bb90775388134c3c109bc2b47dec0967b4550bbeddb40c93c9e3fef221c0a73c4f50e33613958ce82b821823539ebfdfccde90d5cf104e819d6e847fd6c88d6ecb8b68e2faaa46d07c07f45130faa954cf212df6e6988ec94b72fc0788166a9a5307cc7c8b4810f6cdd6a92b97ada589a04b759d8228f9cded0dd58601995ee69fbaace450f3e4ddd41f85d75d8fbc3710b2e595403d3dff1d3305dad944892ab70479ce7d939d49ba917fe30471b37fa760acca992ecbe6c475672155c4fa2a9e6f2d66fb99b9ef99c9188ee8678b5efea505aec3c8912300f2b578badd63a3ed2c6f8ece99a5202a90764fdd892dbd58839a827b75899ae3f542dd4ee4b80afae2349afea33656d68b212687e6f267a3e7f02641d7fb4ac3a04fa89684730cc395bf186ce081d8d86ecd4a3b8fff66d9cbee1c5fedcad8e250a045dd53205072fcc4285729ce279ee4c19875803e962a1a6dc1135f3f00eb1e0e69e15808d2c889cac52cf177e027b745db026e9ab172d0bc421cc62993bea76f0bf8c6f0116029098d4f0cf26fca0d5b002c4c47589c2014e3abe8e50d676a73c387ed84c1823e1b61f64dacd094bc42f0343b869f5f7c23a9101e7d425f8fd8c991af6ea339762efe3a1d2cba0a6c08c29ae2cb6e93ff79ba87f289b320457b587f496f400f33eca1a7e575082204116a9de9ece8f74ed2006f3b3cf37360d1f281ce285e44ffe48b0a5cc01f9210cb390ede87f0137aaa2171dcdfedaa02fd960154a0a76e04295220db9260cb28c2ecdd32943f7987abee4e55629705a80c40ba46e624beb3a23bb99abaf0fa0896334c7b6b567dec9e6c3ec4ea02d46fa463847076b58be9960a093cde08fec75d569c47d0f2eb688c8d757b8a4c882af281ba3be4ea407aeafed307b80dd66917f2648cd34e0fad578e3f222c33da2f80ee087daf64fd8bcb30e7e3557fdb21c4f155c0f7a2d4aca1b6eb1699029576ef93c405c58d34e90221c0203a2f275747db4e9a028c17d47ea9d576198be3d3be82fb7e6cad1e67111c6739523360568f11df7a4a906f3e4f41b8b2b574282b9189c1a765ee80ecd85bb1eacbb8c28a4d4749238d63920f0e17922c6ce2bf53feed286b8caedb9dc70dbbd2f2cf206534e272119617dfb2783ca1c2ba646089b7f75494d993d032227bee83153482ee6238190a65e08bf0c570e3502f20603a5c634749006e9a56a7d501c18e367512e9d9b02b32ecd145783c5cc07b6fdf254566205d4fe25b357eba0aeba72220153ea4fc1d44474322ab9fa749951f04b7c7a22174b02e82e2bbac157b84d83fd2d99509e4e2b2bd078103f6d1d667fb34e574bfc02a75d3178639b88e2c15b7e719a5ee8211a1ab58c28b81aa78d3e393a92997cc9c2fbf5cefb92d6ae258643d9283e1e13ceb528c984248dca88cc7db26a04c164db90f3bfb9b642d06cecefb23eb2f033c67a79e560a19ee01d12d2fb6f4baad1549326f7977e733b57d5c02c8fe60fe9f645ea7c59c587fe13a599baffc34be7156bd27d0ef92fc1e6a1d1478f4ce15aafe629050fc0ed5406465ead8b9828c9f1aee732a2532aaf2bf89d7cc7e9cad576243d5905f6b332e46f515acc9f424962d2b30bc64cad25bb761a64dc8c93723b97a0690e0f6d8a4ef37833df13e6fc8bae3f023adc4368fa08cf842eacb2b55a9bf52346ba02126e1a2409d3d54fc0b8f7728e8001d7ce9bb62e35c65098e429f3908565e7c112786f31c83a20a88d42254cde5eba3af36db8ea09ce11c2526d89b577b40c977d9039cb119b29270d3e45fa3b0d72d19b664ec6882b8f24fb60a24ed35e4140acc9fc8b580f7d44694c436e4522b59e6e861a06f061d74ee028da4a7d5483afaa6f105fb257bbaa6181b9754bd04e8810e69dc8c7872d3197857eab1245917e95ed911e4451a0a17b624f3984c92cba878b58fc09f1e1075f79e81539d50f41b5af62a12e4db4633d2cee1a91a507ce70359e88f5f1a2e6431de5b564ea2e7dfe130418aafc047e6facbddb7d9cc5d0a79cd981a9c1decc066b0fcd74b89e8bb0025c27fb4be2d4560ed918f55e58e74dc4a606d14cd7c2c60049c94868ddb26d58a8c032664553d644d6838b0ce057f265b6241bfded8933ec36242a9494cb24997ce061c12cd42026d771e6282052718a9a77cf6fd0122c0c324e04bf7c838849da16bd9eefdce831ddb92e19679bc40202be394d1f25b1a356fd7d23630da7860a5277d155057bd7a26910534471c68d3c24e8859a8fd79509e82ff15eb8027668c266d034ead5708bf6924a0f0060c39c74c030f63f7dd60c40ce7d73644d68e4589f20316f60032a6fa49dd7af49d98f9752ec33e025c4c982dd82bb9ff7fe4f6c55604bccea4f5bfeeddda2762940352b1935571d2f9b8a06c959a688b4c39b62dd35bf2148ead8ae52852302de27ab91672ef52e8363304d8ec6429bc0f10d920f71bb85c4393911e7610257d7eb23e20718800ed8c4a0fe484f63823a04010e0e02591434967b12a90c0181380bec86c8314ecb047d1b37f8a0351671a78168575ac57a0d3ca723ff7f1f6c5bc99eb9309adb0b122d6ae81caf5355c2bf1451f220a77ca6741e86b15a98a4912792eef9b05d6883aed374c15b2265daf6677cb7c9bfde7bb41c6170d87d3b1a02b3bdd99ab7bf95568722d64a76ae42ccd94de186a77f81840907bea8366cea8bb3860d82206bd1ad65e1f6b6b85c103d13be6e42fdb0412a65a1b6184b6c7da3e92ab2d0adf59dfdd9369eaf1510dfdef16e638fdd6bacee1d52c6a5883dac6fb749b0cbe88974e80b1ebef364336666ce77094348d7a9e6383eeca4d31f76d04817ee118c32ee71cd2dac541108cf6a399cf4d4c9eae04fc40d39c81114e94dd2679a9fe96c619fd6c4f8762f43099696a57d4405b4a36692b242028ac72464f1267a6e0d51dc80c4bd5d88147716d76a3d2d844dfdd6aa60810ba049b4fddf96591cd01ef4891bebbcd3882f87891332dbf6361296fd3357e7e04a9bd4a482543ba7d87cc034f4b3f082d128fca2b2500b0fdf6862c5d529d80b952cf479a3ee483bc071b3519c9194395c985584a586b311a9f58e0e76e765c8cb5b0010b62b537e03c1a72be6be6f39c73a5f68e6d259e805a903b61464af4c451c00f80b7debe7aa969dbe51061e1e66669671b55fc12dd95d1b4f3441e1c1d0976940d39655e6ed1203d0239969191eb520e637a7264d849e1ae2a19efae945378dfb0aa1c8667d47c1997b08cbfd41a45ec056aae76e52f7d57dc963449e73aeb58761681f0c5bca164c16fc1452213e882ec95f98d935a9b836d491cbaf4ad755bdcbbfea00b4cd97c036d2e0d3aadc39e00e417b3bc6f2ea0b10ad612760c9669b88b0115500f64e227de7e828afb99556a1b16c292e0c22451858b3c9b5bf7636435be61db14febb925440cde40166fe5f8b7b3ba2c77a2fe694ef7449f80a50c08410f158a4b238db7d3fd99639b2efe16fb32bdbf2d901949c9eb627adcc649644e72ee789f8174828f159f548b957aefdbee4d11924cc567f02330ed8a9536726582f3e7cfa5c9270238d6d827ad3cd64cf4c56be039cce0184cc9744cd09a5ef7475b7ee90f1a4850d1e4b8df9a71abbed87a6032408b6e7ccfc498f2d57d8405b1758570f426bb2ceb12bfce967e72daec08c2479c9c49193f3f4bbe7a927868d30ff1406bf51ed99dfe7bb77bdc46982bdfbe14528ff642f4370a89b53a3043a639a0d9ff79b3ff0b25adf406d68fb87c283185c0a307ec55eabc68cc1cc0f3a2c25ecd57cdb14fdf89a645eeb26dba74b2614ac6f8980c85f045cba0c8e6513e3700119dfd6a542748b33e4cebc49c32efe55f2517ba58ad9ffc0fda64eff02588196fee4912a3353a93a84d13fdeed354b0f2a92cf7066b147ab7a7eaa067aeba65c21c4092967b7d029d0131368aa7ec05eca1124f97572bcdae062818145d41dffa5991a346b7efdfde2c1b27c3617a362f28a48eb009944402f4c69ceb926cd76ed2243ace4553febabc3a3a76bf7b7e5f7f0c1a1f1909353f68429f7f0f21b6f6348893c0cef20eef952c179fe949768c3feb0644eae60bf13e0061ad7db09950b9fa2848e6aef43d6d6a0e7aec824de011ce80ccc0975e84a38b1dde2a8a3fed2c5806f937026553adbc035ae305c8a8e9140be9ac705396ab9c9f998a0f4e86a1d9cb0da2dde48e0826fef0a57501cf3397a546fab9f9b5bafdc6229010123392379f3810d47d567dd972801953f3d8438e0c232038fd699d62dae6e6791677a3f8cb64a9dbbc8a31edd54ec9b042a0e50a6eadfe4b171272fc7dfa33cd0b23239ee93a10410485d3729df8d49a5b6ec3c16aa106c7e331b94ad074c9fbfd80370bde9d6af943d88ee6e771c05a3035ba114fcb5c6c488a7bb5fa59e457d275e445c2fd68c0f30ace456a52103eab5cf881f2189529c6a1b5226d6a0e6697df550a50baed2a5d59bc54b43e9fcafa255faa64a8eb20b586b27d9d220dd50fa96abdee0fec46d3eb785c4a9835c95cff9128c0a0eeaa508b33f7599f5861638b6a48318a78bb4cc4853a3922076abdb2768772f47cf95251e7dc26728d91de3420745444cd0a136ecced532784536be30f72b9314a0c6364e4655eec3306c24113772c206f22bf6e698543775ebb67f4a9ef87d3a45451ca7071a5058ed2dd9cf70a7e5411b11148785c3bf9697c3d77833c846961d525f48ae5310f5d811b77bd9bbec7031828969f75f304a58248066c01faf2fce0eabc4b366199d400d976df4ac8342cd244e2b72151e90414c75e277c311359ca7928824d8cc926abda5e5b26a98ba1f4a20e95ed8d4e5603bb0d4ef50adc590c021adadd000ce51a3cf48a7e7a2775c01a5b202a9c4a649c4ca73b3357630200035da4b6bf1799289b26fba1974d152809a7eb87ea87159f9f350e739b7c0a9c8eb849525515126fc76d80af8924b68f78808772bd737c5c2520bbe7882c35826f7372cf2d9c074941172db04d823ca40e3c9d038691cc84dd96798b1494ed311afd4e10603300000000000000012ec49700618f294d05efdfea3d9e825491ca212ded6d798f80f7b1aee8f53ed7fef4af055bce1f3add0fe22076dfb84b4c8e818efd8463ce8d88715e12754d2f12375f397926d9e618bb0f59f0d04e13711c4df0cce3f38563614f239cf51f56e3f5406b702fdef2ceb706d219682c9facd1ff1b0ca17fd88f5caa2a50215757df76bc6a6313891b75c23bf5853ca419bba3ce780d35057809247e2fa00a5d9cfff0b171e301035128585def66662f8a5f7683ce89f42497f28997fbaa6e7500b98c32df37866f60feb97176df5a1934dafb9799f6485d6a4e340f3a39c0e0c6dbeeb35e775668734ddaf0da818b520303ea40b7209fcc9dfe9b9cc9ce8240fcbfdfc4a61bf8d96d6b15794867d2511789facce3bafdf3849cf54cb8b9a6e19ab2e8fb234817a56341f321856db48a224855f129c764e33bac5cf6e3fb1c53563b1bf4f279d5e4df8647dd9b06538a7b035c38fad1e1b798f17f92a11c630e3fe14c2941b39155fad69f3eb802f6dbdc3f967e8f2cb3407961c6350475f82a5b741ebccadaf881bc64494a4523b53af609ec993cf583fa604dc4be4d3400af25fcca5c93d16605870040a09bb505ab6cd62e9bdd0cc2b6effc1820c8959b6cf9e06fabb7ed8c4e04380b51b2d6fa917b2ed5439f43861d6b39301d3a42df22c18834227b442576bb4a3dc2a6ee508a3b2220bd3e9f38e80fa491a9d577c191f8f41ec820f9a9735971134e7ccc877036a3412ad9c64e77ebce4fd73d6755e46388cd86fc2a9e5c3f39186b215196c8d8842817543e722ccf7ebae8b1000383ca61cf2b9fce389d2075c9eb36a6bd535068fb8c0f8158282a587bdd95d00b49c414df56cf923248be7c84d53b80d31544686692cf7f11bdbb3a247d779c6069df8900cc31b6dadc44c80443d980c0b0bfe4bb3a8faf7f3a2b072b3d68d433a6f401c10fda158638642640aeb9899fa1db2a2b510c4f67be45098e316b38d8a88d3e657e624f4c5d7b8f817dd6d055f961ba91e6db384fcc2a9c11d7d6e28c63ce91db1a52d11db1a84cf2f4696a8c3b61cfd280bb80428e26d6ad420b1c29c2b3e46f1093e90bb2f0164a44a74580339c7416ba652fe913bb72ca0c8e965c610295364ec820247be450810e70bc0dd868cdcd8acfbede016c9418bac5b2d464a7f5bef84cad4327c8dc32483ea816a12b3261fb78fe76ddf7185017f4a1e009d2646eceaaff11f180676b32c95fc5cd3020790e5542941cf6f16bb83ddbc4acad8ea7318a8a6c23df9ac229ceb2fe5c5b29ce1bff34e85e945c31ce838f51d565ae15201b4662859392f1203aef731ace9156d0df20a68f58b4d58401ff7ecd44fee02806951b18c73787e365d6dcfbf9df2af8e31b81eadcab3189f125f038747cfc122256e5582eeff56595064d378cdcb0f7b80188d7e5fa3dbb612b961759b2ea630832de5da2e6fad3e93284313ff6573c66bcf272bc1149c4c2863c475a729ce8e59dea0bafd9291e9ecb17b708e2e5add4430df0f24b3cc8f502d0549c440252d1123684bd1b50cb4afcf7a2b789ebc177907d0c7de8383a3c91ede3ce20d5ac3a76bc2c33c891eeef35982eca5a6247af917b0a470324e0f48c17caf43050a11b6847577a540c9f972c48ea616cf24cfeebf6b4b3156b961f0c203b0af7220c11f2f18d89ca61affc6fecba0c82cd5b9fb2a10e1510eddb96069c598a08a201e537568ec3109ca5fba7c8e5c9bd80c12bb7928b2d5b72e591176904377356b9182405fa837e9f78c3d8aed0df9514f7b2f638cde70cc5b172b287401a6253b5dcf75c0788e2230ed64c7cc34d400d608f81918773976b431d73c3cc6abe2fda1e0d429e4047e1a4acf620d0f8d8580676c5a250656c7859699d986e2081d4021a5985f5e31ec60c56d77f7189b56181eabf3c6614cf5c7bb9a2f0eba66cff3c48dd6cb5f7fb1336d988820d355015c1822254cd29fc8dedee92df607e0ec8594edf71b0f2c0d20c7d2d6cb640f83f4d94156ac4611da8cb0055253a8c848bbf13136d21fa382f7facd982df8444595d97b5dc592723402382a41ee504b8595749bf7ac09946a56ff4da88829acac409b23bf86c107c0d50c8fdc38b27f4f626ce0d80b5bc4ddfc13ff96e04e0583df3b574b31836c34305bfb87504654744c1e0ee7f1ce423e2765a564dd06a093a48a62cce18d101af001d0d6da8e0794b1a6309634bc1356b3a889c69c545e385c321f3ab43105e0c0f59fc51b84697a28142e9944ccb24b89b4a2ffa624b024447a775b0f66bf305e62d6bbd95341e4a434a5b586117f24c76d2fe548432fb064ba018ae3d894c5f26e8bc165e552b8378aedee5ac4b315f1d9c126d3f832c75e1138750fddfd4b63090884cef823a5e5456a335312cb6c143b314a7c915f5c8de401bf88938b5f3aa25c5903e2579061a0397ad84db07191aa4347985278531da7852bd96cc45f6f67fb9c3f699681ed5cc1730ab37ab08122da80e73cbe7fb0cfd508cee4d9d1869b3ad9473bd06691db287fd382367ac63ac97f5a11e3cef276d85eefabeec43e1080d3b553ba692b00989d1e62b51b81cb36e2495360e2e138c2706bd1c78a7db19c84fb5ef46d36437a5a9c3e220b4f83773df032c8d0446750a830596f95254252b08a9573003fea296af5388494664b986fc863fbed536fd25d1ec0cbd2f7ec533594f3d64691e7380d42de1730cb05ec5981c0a3e9cd5ec6c61deae54e2a781c085f6ea87d9a018ce40ccfd514b0bb2e4348985b3e4f19e5a0100d85762f670c1e0cd905e89a02e3701d946d2f1aeca82cf0b8979aa9e7716c0ee485c16da08476fd9fc2517442ca99f642abad0d40980cfde170afd4b0835f57097eb0352f717ab4c50655462b196ecd3c681d33a0b79033cdb124cd00fae2576459d77099bd3432bdf1c85833f4647b0d8f8bf191d70df1092293da5406c89aeba92aab47c0989bb0b86002abafef5ac690d03a85ffcb67d4196e1531636df366f4a332325387d4144472b1bb45cb7c504aa44bb43e0d3acdcb275845c0de323de196e877851085d1829b4bddbd02e561deb2a029c4fe8dae1d997fd7b7ae9db9fe114ffdac857b83d2c71ccc6bfa561f928e009ddebded2834061b04b1af9de0ea97cb4e0604d366786b58bc5a4d14c00e3af2f781c12121ec1e2dd000e5bacfd9b3a84cdb74e1290ef4710d4578dc3eb3d1718e609bfd663741f9e4d575b8417cebd4d2326a8703a08459125c4516511964fa59dbb0bd584188a73f7d34d2badf0c059687978df2aa5d5bfe179c0fe5c2d9354b1fe9ea30b16cfd59e35d498d1310eb4dd0d52490d488bf0dace801b3c35a0b7b484506834afb891dda4f35c44e4a6c328d6e3781490b53a0bcca409f5e4b00197413a2243d53087d218c1f9846bb789bd29e54d30357579cd65eaac87ac4eafedd7a5d0ade0fcee3cc8cb126dc0f538673e1cfbe2fe5dc29a3424977d0711d2ffe7e2536badc6b55cf4165ec8c6d2878e37e196993a1d65e15d6a72a218806f230227c4f0e387748752d26b93ebb4e69bccf2295bbe29ba4f112d35303ce9c43a415f5569cdee77755e64f99f09c10943aa8af89a572bd7b52e9488e364a8266e080fd580f36bee43d5d5090ffdab5f7daa3ae948be9c7f5697b68d84817ee33c53558ffc08c9af1599c4bcbdbdc5a28339d1e54c988fdda938f0da175cc8e27ff211577677cc720cb4b5e3f024c76c83fbcbf75daa28a3a745f24fea00340afb798244b3c9b14753ed769bcb2efe47891d2a513c3e4e98b59fec6309e526a52da5d009fecbde224f595612bb8fd8712ebdf1a62957e37d471c739a3b4f059f04361187ccd292b783d7d250ca0b602b4891749c30150b62ba01b7b84c6ca2de03cdb1f3a974a94408d7c487f2048b3bf08d67094b4fe7d9f0243404f6caee62a340837ee76c7d9d95c992897383114085377fb49ba6c7f8c2c40e8d97d2f5b661ac2e5fc61c0114ded7d38439eaff54c4bbf00528b7e99572fc42a53e2036cada085285a5cf6c85f2d7923e0cbff49c486f06d59d635974f67c7579095d599b0b6b9fc7ab2167023bdd36577179c1346ef755704a02973f5cdb22b72b404e81440ecf83e63c49f17921548739744e45f755e9b9b932369ad0edb4173964e1003186b1ee6d9dc755bd10b4e72bd7eb6e6e3242a9643e1f9929ba9995dc73568a65b02c4eaa8af15606779a1a69f5fed773da273fe85cb72d1df6594d4954b5bb838005e17e2d8f18b963a417412c616f346751eae2fa15b0cbab26cb68bc7cb745efdd1dbccddbbf55948d3749b8078b6d2b2929e09c91bb1d13acb184ba91b4fb9422c59ee785f26d77e14596d459a268a726da71b8ceaae00689b4c8d8782cce15f1376e8d384ccb45ab415725d38bf5011599ea568a835bbbf0b9605bf3ea7e694b3dea4a434b0fefc797c66f81d66291257964674bd37741069d08be03adc5646f93a172bdca6a5b654c62f4bae6a35c05f17e9151fd4a03bd528ae7bd0296a5d883bf750fc4dbb56059422394cdd77bf470f0d33ec7c7cc9a9cbac1c8d2fb6c6a91d6d7017990eb2ebb5c4110b01b5fb1752a1953d8228c46b3b1180e956b3b050077a15a6b93539e29f60f0ca37e8c47ec4bcb33ad598e2a85bb29b259928a6c71a0b92f35bf5375175bb3caec503f917ff4f9c232146ebb6eb855c4cc39a8014c9411055f7e558df84c4aed1ad71afea0d1a3a8119dbe9f0bcd0bdb80218bd00f4cd38108df9bc6c0c76a5c19de120cfcd9351584693b6e9468a26b26e8e0269c5c2a45dfc0a47366d64bf8f5fab5a5ac26382151bd9ed3511ef45ac04ba6ab0dfd4c9c213a6807536c21b56d2d7f8e51379e92b7f7ba2f7c0960637e12d17dad9b828b4e1fb6d01a7a73f0f39572fe9a85016362ef98d332c589da7e1eec561fae5a75a50b3e88ea10f755676f0a6cef0a7422a06e39ebf3d51baedb93cece379160b0d063a31fa93b81c84b57fdfa946e721707dba0d459e9ae474ffd7627aa071f342cf6abcc9ab92b92675a54fc60c9937855ca0b9d0e953d181ae9bf24498fcb7ad313e2ec06b0eb00e4d75b0341b67739c5c80f22f9a181b2eeb1ec8722c754a31b8a16eeedad6b36852a1d2fed47eba12b95202854b84f68a4cb13219072b25b9850829fa176276f2a4f087ddc74a52df87311fa0201c1ee2af7282427cf4240fb5cc35fdba2b1145c84166c8ddc907a0e3192825a94f4e7ae90ae3e675f6d13b8c43c2e14aafcd5350a388b0f3bb403b395decb8facc2019e21cc36ea809871e24b9eae8ebbafa79aefff7c548496df9c233432509151458617018427bb66554d77ecf7f5ee3676d65ef6462d87fa4f0a5cce5942ae172159168ee232ae345d59294f9bca9ed887ac72036546f087cf2989ab3ea84a5cc534b68faed59f121d032c2c3de49764ed492060fcec4e3e6fd5a239ce85684491e92d08d12e1119830894c7665fc173b6affaeb36c1859cc48584f7a7b92c95e26c963dc7a128ee788b18407c420806677b7de70e7c3bc2063f1bbba2003d325d67d60d936653b2ecdb9caf4444835cea92455fb7690bf1a83616d90d98aedad894304f3617e0dbc6022a9d07fe38a32034831cf319d3235450fe58188ec2a6e0b18c0d009dede33b8a894f9af1123adca900a4096a4aa78dab53d7d3da0c4ae0973cec8704774d08dc6a7d0547175f7a410cd834883f2e5a9eb7e79ee23a539c31fac6590215b6a4a69c7ed1c686ce08c14bf3c2d302db6959a6f2072d86bdb857af8833fac98dd67fa7f63384931ddd20bb7a6121e2d6bb722f2a0f13f525a6b5a87f6acd80bb05740faeb45b6d9b4f6d2115674507e74ae65f630200030e7c24c00ce389596fcd502c084e220a9d3ade25b3dd9951c6e097219c47d25a0084a6ce753affefe384bbe0d4287760870949bff9e3906f5ec7aa4214df65dde4842155a4c83d70cf3196cabd4a90d3280ab8220af6e1af3efef092d4e3e412fd4e10603300000000000000017992e6008992faaed376449d93d569185acc2c58fdeb3bfaa1ae8f1963ffab58d515bbffca2ca7533a6719910231580cc13b1cd39ef07a2ddc52a36a968bf4101c071af8f9630eb89b6db3d26fb7fed0e9b9662155c519826aaaf2df8c64713b18a83fbda62fc3dedb974a844704a48ce0f99a818c9ecbf14bd0f180df63c3bd86840e938dd27011e2363d47f3507cd69acce1315e592563130a17ca626bb66ac69a154cd28c01ec24bbfc867d8ab7771ed00047334fa280df18bfb8ad1e689a9727ad4cc6024e2eaf1ea1f4fc91152a99a31b332d3c52fbbc84e4d9eb387e364b207aeca5f75a4cee87573ae3b36054f7597edcf63a1c849b5f66d0119c2190aeaa31f4539aa8016068b97c89a1078747c4c134f51797c05f3000f0e29f4ba581c393b16947afc95384a124344ab541bfbf1b147659a82f32bcdd2e4bdd49ceed79a62e4f583e91e84f04a097b0eb063d6cf5d8d106962d5edb82e0c322b83d6356778bbfa566639cde4608e6d5bde47588dc0a895e25f4bdf368b81ae99f70c3de151f82b1432af68cea344148ce40e86deedf720a3715da2447c91d42660274c8b70215e2dd48e9a96e255d89deaf775dd1be27b31d6e2777b99c2196a91a44d531d730010bacd6d2ad35650e0b958224251761bb6a1a610b6ce8deec69e446424b0b688a96a46faafe98d0314443566322dda78387097f0e794bf84d097b5f8153fb60358ea889ff3dda17ff140b99c112ca231684788ffcf37ee27be5ef0df4ebbdf820f30e8740cdc6bae3bc85e4d6bb0d8c0e5ee1459c859f8187aa7830e9ab80e48566f298ea514d81440d0e7ccdc26129387126427409dcc84861f6da0a2c6c512e95f5af036af0e407215630eac4edf062ae4cbba537c23b3b309db43fa67b3e9ed37ad553d3b1308ba95f6405f65c28c110adce64092609007f31a67346a8ccee0f5a521cea8b392b386681398ad5138a281001d509f0714d31618b42f432b382fc7bd2f3231c2b1c1e44b7250074be26dcd8ce26a566bd3dbc19b59b5561638a26784137eddbb90e8b5e38f7424a0b7023fb91210e9e99ebd579b77b113ff6d925e0d607501a0416fbf7cdb7ad627f714c094464249688fb211b7d1647a5f1c6207d3d3378d98d388cbafc33a3b94d15f589e9d3c414295e93298bf0a1b99cd29d7675d04da209ce542285d2cb44f36c15986bc13d73a7ad1073fa5919576daf352975eafae480b5586df20ada6ae9a0b05d4a980410818d7eb17d9d9840d321d9f937883fd113260ec4595b997c0722416591a027317d93dc1cd255f2d799e0e10c52cf6a19b434f3b05ba33310673eb3117d5d3ab43bed583345625c3683f7fa7befe1cd156f726c78fc80b8c738dd8186de3cf76a6233b8101acc488546c0dafcd574e723d7bae0763e85b4b875927d45b5b10aa115750df69d51f2372f94141ea03e696e0b917e1e74d1f1470225489d0d4919e3e850b887c61ad78f04447b31454289703f57e906b47602979b9cc16d7d2dd6a5e6837e50ca8143b3abddde61270934e20a8d0e391be955294bf367e69ae9b957563177baa17406661e4c49955a98ce2e6055394afe52a1963a45d12829ca03bf40e9fa7564f163a1cd68535dcdd12d5ce62f780a768fd7d7d7cf3eb3982773d453e830ad3d858d90eb1fb57370cc45cd1fcac61835210e8e174addc7b6d5e724227e678c6627dcbf6977a3d805e74d694bd8bf80f3a65345700f436e9cfbcd5b0bc23b025b09091a26a7d88e08729d51ef1e6ee86789409230b2b6d0edd8de3b6b345560639cdacaa115a186dd97ff10bfc9d679ce268568548a50b193b5d07e9e5299f400094dd8dc20b090f6a8b7e825e4fd30f016bd67c6fef9e25e3c195b50cdd5c9a2efe15080700f878871946a5dd5d7f869fb0ce9598890bbe957fb5d49cc4174238e3262f1a3265c492116e00d69e7aa9130f58f2d9579beda6e580a7edcb884c79e5986dac2c9785521225f3ba3ce56391d5ede1d3bb28c29bba067134c3972fba912ac865a3f9fce68af3209b690dc46394c4dd955010e9e5f451a2ce02dd23e36d618072e79d58e2902bcef5e5de18615acb15aa4db079f9470ed15be7415e18bbd13b003fccd00927877e0b02f63068ac1a806385c6549e7977f75ad0e066e6692f0505b7ca4c7e94c336aee5debb8c9d665eec0f6253c4a435c37645ba1c97be6e60fae6e640938fb47972d2cadd5c1119c359b7a732bf0c8d37b8e124aa928c29dec24e8fb0dfcb2dc1b27c8ba1033acb7d91bcf549ae97186519ee9d257e9a17933047f7e30717af72dd344baaa7c4284e6456beca321105424a6cdf50e08dc541fae563b108ea69679e6233ce303b294f73e7ac169af780936e9a995445e93532368dbd91fce3055e373e3ed56f8d5b0daa74ecc583a6744b99e1201bfd8f67d5ed0411802d8b4ed5fd405dbd61ea890cc0b7a0ebf9e42a77b3f57ebc70286f49e83315550820e2e9691a8b3218d9def9b70a47ac3d0f520632a722475fa619d33e45c65034c7fa5621732d2309c1106a50b2a4d0a40cbd42c4caa9905c1860e162efb40013b138eb9dc56d9d4fb5e33b275c7652079ca6c28320a9d6963ea2e4ebd25b244f6e8fd5abedd857d02bf15d78a5b90a71efe84631b48524b3f40f57b0e8268cc607dc0a7abde35d77be2a04b8d13a0cec161504ece8e96538ebc0f1797449b21e5bbf5cf06edfcae3bcd81898e38fd177fa3d3a3a081d9f53635dd7984f6fb2ad01190c43ebcd6198fd1a95b95014e1fb9653b081e19be9fca464d7cfb51aae485c94d7e37f14557b2df866db39909c9ad7e2d3e748e24237b6aa982f0e283fef5db6a5085316af0554cc35b84fe8ca38864f53c395ae2a642cc6f9e886b0e7f81d6a8fc11401296011e7e03ef5b327e467e153f5d612b190950e335914c7957719d837e7868970cc0ff9fa24af08e105b750686750340c09088b06cff40f7b5c7ad7a6b8e07f8dc90b070000ecc8fade67e63d4dacc07b855efdde8d1334b2ea2d9e3a6d57367fc34ce9694bf2735a43d7fe19c8c3d35f966f7afa3210748e01c0bc887bf9cdc91b63fa0b2e02261dc534f559de72d88f248b8b9b05d7a81212497bce643fe89757a9b825f5bf3e8d84a129bf231f6efb13fa478bde185c62101510d329d6bd8e3473d0db847eea66ce2d1975232f87b9730c3c7e3421296878d1e5c48d4250b34a0c4cd21e7d7bc01cdf4887c8a954db53c3257549267ab405b07e51d71c90ff75c7db5785549381b9cdb348b5c91f8d00bd0884363b2a9f928b05fe81d9e3de3434cefe37b215b187bb9062950877e42beb0c1335f7ebe01fef8de02df37dd81909b2494ef279c496e2970d992ff90947f23ea1b1c42a0b75d5ac8ae52ff66d86dd0b1aba76b575adc2b01b9b374b93a069d7dabfa608ff37184902382aadb582dd10254f426012b3d6a6e180e38d2dc1599bd7a2c4ebb43d6d8d066340d9464af685758fbe4322e788cf7500a6ffbaf04f02821f72d9d824467a6b7512285298c78b06dac316da4663cd3714eb25cf188cdf7b5545203963596b75280b6b802aa3f6ab0679c9d8c7195702a4e8805e5e129c156897565e61f61397b3aa30f09985f6417b2a26842b9382afc56d2c7d3d4fe94e04da9a4a91319f8a0b5c3a2d38d900d56e4c87299ced5f838c9363258f61f0020b15270f5e8e5ff30f0b0e75fa453caa0e60556237c417735a3fa3ff07b73cb7517b47fe973b6998c4f536217d61c379ab47b04c7972e68839e28228be3e9a6f7fd3bef7fa2134b9d705d400c4a08e7150c8fcaf5f10a044fb39e36920fe7f17bb0e76faf58af6ee125e9226c0cf3aa48d46e147e00baa95e07507db9d85ec9bee0c399afe803623720fcdbc54c413eb0d8abdfce6907ea6461244e46b8e429dbe7a2bd145e308124d59eddc9f1b83f070999abedc427c0fa8bc5ba83e9948a159cdb29992dcebf35b90b5a8becd971730b055022f879b175c4b31b9dee5cca39b76641a49d8e99dc4c9a3262ed4249a84057c9fed72a28fd3f82dbe472b3da5f841b85dc7308dba06bee6389c357f33999236b287a998557594af60ea9f529086e06e7c4e5bcc698b18f4737baea0f613d4198726116c8a11cca7865dfe78567d62554aaccce463f29d5f9dcfc5b891429e579e31dfc3df8ec6bb03e37c6c97d5bea613ef6c6119bf8f3e90fc153a55750c2f7d829363f74db4e04955eb3908bf3fec12f1742abb7e6a174ebd30bfe56dc85f076ac809724dbf5c7a59b6915106492e5fe60daa254c963d1612b70a39124d89340d68778510268671019991f2ac15654d2aa699646814860f03de6639426d3b6b08f8f353243d556ac045810f5edd24c5550b50f7df9f53201a67da6279b864ae28281dfe6589f5cf7082883875f17826da76e1dc6c608aad26e7235ae883518c4cbff3ca930c74e67acd473119a665965f61fa8078fad135143e26163bc86e170ba0354ac373f019fbdb0324b74071a7c52607a0bf45c5fcaad148adff114e8c90e25c59f50e8b57511a111464234ccee58d3ee4f44f73fb9afd4d5604aa61e11d08154c45359cc1468c2c0d0e16f80a0a906c49f92174a6bf858d1b32ed959d5fefd0763852ac5f4df23d0c6ee5e093b3bce3d9a06d609a4df759a103422f27d52f702a9d7483925ab76cabfe39ea8ff885e62a9b8d13798dbeb6c450561720166d4e2cb931c11036c8681581d9520365cdbafdb2ad2b3a38879995ed8fcb0bede44b505723929b384e1aad64487dee16e8d62d1ba966cf356171de763ca7511e92e6f350f0c2d1272a0e66205de28de0fc8bf5f2622e9b5ae2f6b073af1609d241488704ca61dafa67ee7d732de6c944f825b2f124a4c6949646c80684f246888364a64ebe3ed8d21aad7771346e6b63ae009d5aa1b845a265c1162496fa638bed18398f59a1a6172c37004774405379c1064eda243d3fd5d89e509b8b859de15660b0b0bff70e58518cb106f32d1dfa887353daeafbe22db58990635e35c237de270ffbbb4d1bdfc7ad83338ca7b8def055acbab3c518b0287931836233fd99b69027263b33c9b92cfa1863b0487271c911ce18a9ca1d326e1bb6a07f59d72425e6661001ba80838ace3d9e31a45e3ab639bb8aa54528a9aba8b28e654ad476826a0132f283fd8f906796286dca6a3f378c82feba3e6b728a84614fe4a530017efbea1661a4930864093c7b6ef1d6083fc8a7b4634663a754a82988f7d17b54b7bc3d2bcd851235edf47d69e84e0016703d3a3421b959ff4fe72fa0ff16f1795b00adc2000eb3a662a89dde7f173bbd315cb9246de0d4cca4d4d39303ef1896e3c11bf8e7965e50f7913cd069edc94b47989b13e04ae3fbd8c3be2b54ae15c3994f47cb9251ecc10046a06fbc0eb793c039cf341939871e0affb358bf67ab06d99acf9ac7188bf03d967a10bd38f9b5538e56e41cad8be5176c57377561bb0db6e3b38db083ab987307fe23e5bd393dfa1e839e3f427ebd4070873c023125bcc5d71cb9eea4e741e78aadb6a66b07186156260469e552dc5ff4b47f6fe22c4ff888d6b809d473bf7c849248fafac4b5c1f6ddb92f3ad736a4be64d636fe26d72eb38fa9bdff999b39d1d42de244b12439a09fdee334079a2ff3ff70bd577ddb6fdadf32474d100dacf5500806d08c29caba04bab5281f1e5d01ad538b39a8ea94a8e2f43bc4c4a0079017b5df9e2ca10424c22269a1374e5983d36967181a97a703f52ada939ff2dc4316102eef0aaebfbcbef30056e388c465e69216dcd5d32c215f72781b71b07e7f863700c27f726d8ebc9f71bde442c7e909be84943f8438904173137ab28420cd263020003255da9d267e04b7732668549dfc74e6c25213abb3cae0037a8d16720482ced449d8675c1a1446067f8753abc1b87ffcb109d3740ea5027f8caeebd547239eb3824fd1d0c7d969e29ad7afa85216e863d753a5d15e2d38d5ad2cda648416f71b7fd4e1060330000000000000001c33d91017e2842b264f8eb8f263abae58bbf3cffc1bab7ccf867918f1e4f3a0c0120220e95875c38d7e2139bd0e006b13275c5e79991b60d86d71d0fb714daec525fc005addeafe2c7e6e566c85f7b42a03e5669d7f7775ca8573b2886c9b4db5c4d45620bbdcc350d481e79402b83b9f9ab1392e4def529dcc06b86d7d50e06ecd94e2e167cdda5c6138f3f2905027876a95dd284e847688a5670ac076696fb1d3758cc380c6fdecef9c42e0d2c33f81b39b9e132efad3581a0e3f6cadc18d949a9d3a5c40b715d3462a7ccdc62b0a04db16c5487e15349a402697a3d7ac367e1c2eea4db2afe32cebb472d95c1b582d1904354b54a7d38fda4eba9ca416f68a157bd4b336337ce9ba0dad7dfdea98001e07bad7123ed4c16b5df86a2ef65ccdabf660da5425f5f1a0dfcdcbef7d912f6d221e9e815e6dec766bc28ecae23908ceb246541fab61005633b290bcae07e680ca879e3d62e9389db886c236c324bca5a8db63c831110c751b0a0f795258cda457f4b920249ead7c624ee5aeeaee67f1baaeb33eca3bbd339ecb0cb87453906171205367e0b68700d2e44b93975727caf9644928d79dfb7f2eee6f551de021547f22e5090f9150e29b43acaa258a04f28aa9c3c8f543bcfe72278ad299c2db3194936873025c9c30af582906fa6e23d697dc397781009b5ac71e0a95d4194b6a823344da52fd8cbe5f185bd06b238061988b2381f7176cbafc140c47fefe826383fc5ca22fabc9b66f74c06662c6fa45659c0e56d454026e62241d97e089892adfb107b5bc8742ccbb90eab43120969f19cb001119bee595d6092806208558928cb37482931e2b7dbb4207af09c745509d59875e6610218abb2ca3df14802a55be197be2894ce73124cf41f91a3ba09be6b7436e6bd72f35ba37b878dd198917d9ac43619fa32775e2d8fd82eecb03795104b5d88799585d5ced9614e62fd68e16db8d6f911c3bb6a2cd6197a253f30d3f615f03b239f798901820fc78dac3e622961fca281ebc5f63e4a29d7c3117513d8c07bf7f8a74748495a1ada326410eb942110bcd7d72e367493dcd2dce59cc84fd40c87b025b4d6e40fef826cb0d5f6af2113bef4b8e400187a84ab7cc215ee52538d763f4d7715724892a4fbc29f6be31b47b47ef1280603113736f4d51c3e0df6b0b09a0b1b84b9e0e9b531d32e28b69df6504489b219a4d6d9e303b7496244b411a193b1e0095eda316df21b42089de67501607d1630019e7ee2007d4c9f3f3ab6f75d6bde6103bfbef0655a3320156cc9e17c00efecdf4cff74da63fd50d062ff051610f76f20ab2192dfacc0872bef33232260811490ed5dd9cd61e2d5512a74e05f1cd3caeacd5d3e233f936f1fb301171761c999a894c4bb274254ceb109ecdd2088b9d862aad60c28c4bcbabd9707d979c7b95043132798171ac3f48f4641230e6d9b10c12478e79130d16b155b4170f8cdcd0d80aa78b912949a51e76b9e20b669307ba2a27da7e12ee49a917deb0dbbb090fc163da993742205f20da0540aa08dad582d8a164c26731203f3531f52bf4f1a760e788131bbe3bf526864aafad0bb7d5837885ad0504f6f133259b68c71cff5784e5350f8b58e0e86ab390d943d33726438f0d23bb84d64dd969a5b2fe198608e28dff3b5e1443fcf962ddbe9cd52bcee7de9543892dc294abfbb6e2e1ca6a5b3b640fbd1141e46a6f3770dfd101244d31f70d31b2edfff3db7f473d8221a0ad9c99aae726377275b6c4e6fdc3051776c1e7010cb46687960c0650e09e054931f0b40f7b717e5ae3c25b28df31b387845ebf648b7300b612306c30d80a4c19a10e84f7149c9608d993c64b7713aa1595e101bb43b3e33ba0f188026e3d55d5f74f72eb33bb16389b19001d3ba197bad43a90850bc076475e375c4f45c9753481e0e2d9890720495cd19379b2545005d5d2030c565d01cdbbc25c57a0db7e0ff6e9104559dbbcf47cecea244102d27386f3c452d60a29e8f618a779472d819e61a8c8fb3f184e8db34b02fe51266849ec90ab99cf18e484fa2df59e9c2f69babc7a8fcb74d176ad6c5a926dbfadee8a53cac71d4bc2255b7ad6a832804782df0f23891a877269ca3fc1f43b80fbc5ec21c14f2be8342318abeff5aafef0217a04fd83ef9bdebe9d5484d99437558abf2a129300ae82375c40d6c382e10e4c33945630e1ab8bcb2e8e8bcb17d0ffe2d131b4ce10d88f99932707b4be6468dd39fc51ba9eb3ff75dcfde7b771415854f8e4b2ff0f7b1a7222b2b30be82c3ecd16022dada430e49f45bd5fc5dd0f171c5cb4785970ed5861504021300db6d5ba6376a4b9fbfb1b3b7fdf3f80dd09c2783be3e19ce0e19628c000c8c0b607b67c3b36f6a41d7f364e58ccafee04e554b8e293b609fba4cac4f9379f2c823d190bd321c0bc9aad08373f5e219daec83a27098c346ce92efb75fc863691197b89e9c3209997411bcc1348c6d96063697d9815bf466ff4c9411901bdb7918fb979ff0c942cfc687fcb8aba9e559810adbeeb60f0dbc1fcc8e2732d24882ce0f18a22f133ca9e31a7da1f32f98790dcc48a4fba217e7342586433063168e58aba3c646056cb94fec4b4594225a2b965f3c88d79893675d5293e947094717bac22aeccf8e3edecd7b9cf7df5566ab56e3010613b8d355208bd54cca7f9c749321be7544ff2db934bb2329b286e08a9deb21896e36564457d1b4a01c26a1cdb10ef41cacfd8f572c2fb749cd27a4537fe7a67a9ddec78f890d23b5d76185cdf7e57943b58f9faa90d6afbb9e93a8226bdeb9de351c7ad2149ebc33b824c952481d8f2a0f959061663d9e0d44b9b84f7b0392ac15ebac6e47137a809692bb9d765d2b03b2b425b3a011177d7eaf646b24035201500860f44514f9aeeaba7d30ce91c020b2785867000feb7b9317870287aef2acbc7367327e219ad35ac26e298c88f8a7609206a0da1f93f5ea90f2a9ab9dad1b7f745cf4e8cb2546a534f8a131289039302855673326520d93507e888c4451be70a140ef25a6a22809f92728972e8bffece5cab549b56e0daaecdafe23d9dc1ec839afc3485a0d26807b8c945da7db0989781441bdc9a961a267493ad516cc20fefd5b52ca4e56d66eb51c2efc285cde9688d27f38c5f957bb963e5cb5f9d905de7dc6d29cb03dff44c9ec0acc89ceb6e8e9b4145d1385a8cc3128e35795e9e9a84fc456f5bc766c0b555b6a60a981e3d53705b76d38c26012e21d15f448590d1890d5f6c473fac3ff4bb6088b1dcc6e0a12a9f619864a61af92011cf93fdf3c39a2a8f93098afb3414f3f3b3f1059c2f02f5e3083db3ab4085545415c372b0b3eb7c3ae492f1559f72742dd9877752747a14e2b275cc044911250173e251cafa05f261de33808a3a0f86f99b3ce1da8050292712ce67ff4ce89e09727deb9d6c2880326d17d4a404d374650b83927ab47e472377ae6700b48e0d9bff66e354482e1bf22e3c2abdd4eed3eb3fb50093764720fddf2a88ed4076d10c8be2e0abe1b16cfad4380daac7d1283ecc35b6aa4f5d8c8643a4dcfcc0646c29a9e51796d1f66e44d2b1f072238fd915ff57e4b4c02b771aa138c92a2e95dbe5857b1c420e95abbfc8cf0eef3a00f26f3b23f711fcd531f8de7ecbe9b80edc57edc5922bf82ec0f6938556ba94fb224fa3fdfabf57bc67bb2a187844b98d387c53281b30be60a52b05c3d1f888dea96d6381c54d4beba4283e698c251b381c7bf30bd24a7a8b2255b153c49f63ce88c25cea6cf0d01e5ed06e6267d0837c1c7c0b6535551395ae5ad1474f890c0173ec2bda3398963378f4116a7b8f8aecb9c1cb40650b9d1e68b2e669478240d28eda84a5a39ecd4cbe1eff397c31864aef93f836b6c44862c3398651006639530426b1efc96329a2df19384d50fb5f970ae5e8f709b121bd4f6c5d3861ad361927c015ed5be3c5ccde335aac4873dcb3bd3d6e2a503511109aba866ce501f56413aa1431fdc8383d60dec9fa2b1512c318b07357d377b1c77fbf9be17ca98a486eb586b267b14bcd57d9e01014527b84520e64de90ec7b34ae18574e58921340dcdb5a336cb2c1eb4be6f5da85eb438bf8240996a337521aee3e88ec8e7b26ace05cfb9db207d4562f7d82b014f45a83b6d2fa6a17b7fc0ae184da3113e73d9b07a4574660a193b7423c1ddecf7e461091c55cba7116a4df76476d779710570861163b3ded3a29bb4d6e253536fd47ada2c3fc6d3c05a18876767ef9693936cf11f52b3f026f7f126b0f845f01bb0b020caf22a5fd3df9f8f6eeec48dcde4d7ae98514db7baa8bbd7a5736863893d690baa2b4da1837377d1a532e474cb70d5dc6eba831d42d80e9b47cde97fc6ab7d24d0c1c3503fe048accaebeac4c1008e9e74acf01cd4ce0aea8f96ea066df63e8182428cbd2997d2d23f4c7315f6f503fe3b99322d43a0237b0936d6a7018cf9f11d4ff4d794f8cd8ed28a1f739597d1aa2e3dc9532cd77d40671c3b7f7dcff9aeb66ad94926d7a772ff02a7d0965f5346520e2f0cbf8460823091163dafdb0a73a93dce53157ec76330d0c19fa05db4ce6375b58f1cb1037e12c3d7a7cd7f6d8347fd24c86bf88929779a557c7a56d2218a8fd9de4026dcf64554176b2a5e666d0b9703ab62048bd227f42ffb1d8b74bc86662845ff510326347f53c1c012d9e1df0618b3f48e80f846d5a36e67353c6f484290d1a43488a67bd037301bfafdc3873f374acec41c620b984b722d12e3c37307d01bdefde0ab81c94cda747a000822599fdf34699f4bf03c1978aa10f9a14e99a225ec37e4acc2449aef3e7af422c5145c897cf06ce5127d65041a2685232eee613bb98741d9149e58c98b95707e5d584cac8b766028cfc369fefbafd225adc9a2d3ab9a3e941255fb57eb77d96756155b05fcda1856df92b19fedf6d1d01b47014d0cdb3cf8005b0684b43bca38b051b0925ce2584709796fa64db54f6f558601a1f825d42cd22fcd6be23b14c4c4bc58a49cb530475f8fc0dcabdbfea8f9371859fa0f820f3861eeee832c11f727b7de79d7767a907f2b0b0b38c4d4f912c9fb3bb5386efef15f516b6908d25140916591a6d504db70b0d19c0eee0293cdbe6b4de7a9088b5b12a2cb1e6ea70fd99a73275e2d4f6021e5ea0c774cfd6aa52b2f1f672e099d8db0a54ab4c7901ea4fb2a4008185eed5bbb5367a0789ad6ade9c658c05e323a7d31bd8631f56b0800acaeeaa991e939062187b81babdbca1cd35539f732904b3e19e95c9d474ee9fe5f22b04c505f36e427fa69a005fc731999a667c485e49af6ddf56e56bf0b431b871ad5d3471d1eafe6071eee3c9184ecdd7a1b5a8fae6c1e7d16923ff97df48d95c85672f75ef7f9b9a07d8640eaa080741ae93f70fddc28ed34d7fc675552523a38804318a6e3ac41555ebcd41a07eb0d851ad46be57df683e2787cf0f229868c72b65974f7693086811deddb36abe89f3c280cc87b49f1dde9041279dbb9a08572fb4504c5671e6800185903a72e604508beff834b0498a5463d196ca37675e664e8ea51a20ad8d771c9caa58a20d27735c5523f9df92b1302c83819f5af03b1f57d3804df49a8ecfe8e3615d2f6bc39b7a04e13ea22b8c14a7aa6ee86e32f3d88983247982caa158d821fa6ad0d9e452d699f6eb37f669d27fcd5c9fa8d4e4e338d43928023d938c752daa7fd628b6f3011d4bb0c39a02d8156a5a998ec15459259051f6647bcda8fad8358923c853d2a96b397e6f8f44ce6b0e0c984950b70d9d56ba71a397c9e740da8bbd17c56a4f2148bec5365afd341e0c33b08d7b18c092ed8598c0765356f848374ad2593665c0015c9ffd99268ca6aa25eaee6f4609563020003acd850bb5a3aacb206a189ce5e3f128f9cb909bf24600e03e5f24f04d904950cfac2430d533f0fb8acc2ebf8eb43c34bd65eda846d38235d473cad374fd1eb10ae9eea0318b89745d01ffb06535d38928056319575b1570d263c241a0c5ae353fd4e1060330000000000000001c6d1f400769c729e9412f8bbed33d856bd7d816cc231ad0b7c390506dbea00b4286a0c675b405358ff3a6e5a10686089f67bc8e5248fa54d05617915c96bd18a7153aadd43d91bafee548fabace1f2309b6eec4e60fc775b963d8c04f1d2559808cd52a99578633f2b22af7caf11cb42e399adcafd2ada94edf2637c777f5b2ee79582496164c0242e6e9b9b0f9855a758c5d60ea62d490a33fae4f92d58b0f4c26d250002466fbd93921c7cfd791887500e80732cf44e690d2b4de921758524e17c0f733d1bd00a200953af0a867c6e213721284a179ba2e931af6f9d75768fb9136bfb494733d86caaf2d9b5298f4018b5f6e006b9a85930f3a6f31d1aa843cc10fc98cd44fdb56d08241f5ecf41c3c1dc81ad479aac487db16fe52b9ee1312b0fcb68d756eb88427eaae3b2dbade315eeb5e740a74df2bb984003a0697b77026655352a580e17f9cd4d50a9c1a68fdaa98e5dad4e44cf58202434be6a43ec0e122c54259bd7ddf85961b4ede1a8a8ec502c27e5bbb88a3abfe39c01fff006b281ccfb0a74fe0f7db3114941dec08235ef29602d2b64908b200544215a4dfc230ed6877282de2bc99d92bbaaf35f66de32599d4173109e3594806d651b8ce7698b12e6c9c8f34bcda6890cf7074685ddc81afa17d13478fa3519849ef6aeb2335a86fa2a3a87d7d8c8398e8c7cd35effd04e47ff881f6b29264b8e94cc661b79dc9bf13b34675bb9ed8a6f22067a057625a774522f9abf1340aa2e054bf9438cee865fa046654a71271dbd67d2889f2c2bc58dd7b1b64bef9ad4c184eb03726977a154391d3547b64519be5203e758017208a823774287ce07744592f0ac6f084d14027d9e699db21fbd6c0ad24b9caff14254328683766f4c7293ae17f0bb61fb4b854a1035ca2b3d95096439fdf6d1547b560d7e0c7fffc04018bc66e84ad67fd9519d7376c083b85d4fe6fb6f8cb231d223ce5a9567c7134048740856d9a514c2166f82fea2f5d723a3a24c00f611eac96772eaf9a801f85006476355a18e145900d56a57e455221ccbc7d4789b0a19be91c1ea9ed8ad838baa1ae4eccccbaa2f128d8471aad10b1dcb45d7aa912e953875928cde1ea5d98fff76a2af3d2ab8afc6c11feae0f9628008fef54e9c0200c12680ed2b7aa269c6afbfe02dcfa8e70c130cdf48138ec68c2c95e9d0f6f2cd0139d0fd7bf220d91d2a3c02e6095648c74acca5ef2fe7f261408fdb814bd0506ef84a77713c180c89508e301813e2b427180945732077bf0c88fb3cda7d689d7d83a72de4599fe674a1bcc54ff6bfb44e2899d45ed106ed9f10ec56e3b62a23f85d75b9e6e5f3d8415f47cd572fb09769574a95f299249ea9eb0a4adc21ed7c0b82ad677d78ce3cdb89a66f15b8ffbf9aa43f19a270687ba9419091074d8f5a700ba72c964f729e4fe4ef3ebcda0ce4797c74f9f8583b72c709ef2058c8a45ec091386ce9de3beec55a2f5131f8a58cf44159dca3850eaaa14d8a57c9cd1329c76f1ea36df0b761cbe3ea627e1808c8743ba3af7dc7f14b75974b5c7f1c5b8787a723abd7f591e3484064403b5541b70059753e3e6719e4076e19263c3ec7bb2865c605e3329b792fcf70ad2280491ca0ff3ebce0b9f3d4612dee6eb8a9bb5eb2fba17975b31ea0837a152f87575784e0406c6ec0b8f53c0b346ebd9f913bf9c0b20f0f5d217e2cb333da4056e5aab642bec8ce4392ed54aa82bf91663f32891f54d2ca6ec1b8aeab4bc5d8b7fba4c47a5988a799fec72ae6393119833936d41729b81a7b90c78a5144b5ec45fb7029430644533e5e330d812a9d79534cb3e3fbd13ad05375436a898076e8ca4316dbeb92e9bc3e0d1e2d9f7f8561e86302f857267c7eb09d23950aebfb05487785c0114088fd434f6585fee9373b92ccc7ff6fe8f6b6542593e7c477aa7eacb55fb4971c8fff5ec64cd2731158795a6722d73f3829628ec9fe640f101f6d1c8e0fe4392693f15db42765d34bd2e28679aa211712ab4f191bd78f52a595a2642735beb6084990bfddb5cb36e1620289357cb019e89584b9c8c01e88eae15b3cba7d4d028ad9d49e8ef699be45714ffdd556155a6a03b85a67c3776444fe68a19d872c50803e83b1c09d0ba69b8255e52bd53a0423652e0d7d3071bfa16bc44b52cef8edbea817598b6673f3e36a58b22fb82c1a03a6ee828edcf2464c1d85d08f2d706f4423971edba40410f2474110e42c6b18dc733c34edb1a68019183754de85664f2f878da0d76c20558fb7986eb6b06c70dfd2d0adc9372080d367d7aa1fb2a1a2acd51ed374d71e0641b0bd32089b74ff758486fcddc0cfcf70c686f24e20f381072f992b55a9626f566e2938fa5da6904cb7188a9d53cfa6944ea012ade3ec02e6ce66529134f9c309ee23e1ac903c3d3c51c1dc25da18df7234683d341ba807928a159370b0204848eda4d1787c70268d7e5c87d879c9a51d122a174e2a6a758117c15be702d9867ed991b51974db2c7c68899a8144f73689211b1d0c54a96b9eb64f1d377e57201acb9a004a423812d1d94743ccd4d29373090649c70b90455fdb6131b09e59a81661ac495af3abc2e6f12ff4f43ba3ff070786f8fb6a0d5b0a3b30a0c45b9500c28314507355a2b4ff4197722500303a6fd7a179e6fa26ab1c2fe45ce3fdd9456dec5a5b227d2a0fc41cb74ef24d27dd73621cc2bd8d844bcc81c1e7e2437b04a4c4ec67d071ba278418e69ce131dc85f435ba5a86d0b62b730b0bff76464d5f4fec6b9b74562fd9f21f02aff6a039f7304da2560edb7f8b59447b197b9f9bc894b28126a02dedd608d10f0d4ac601cb2f047b9cf7081921a4d0e3f62402382f2e453adef81582af909fc34d2f2ee10c663f96b7dee12b9ca9c339c2bf17f739d3a3637b285450ef98daaaf551f7530ce1241cb30262c1b0267f3aa0f6361aff2808c05206025790e3cb1d42562cd759f4ea7a82b223b2f38d483af7625b4b6fd38b256b06fc8a856a6ac0271face148ba8a3ee83e751f7ffb434555ddf7894f3adabfd7ae5479e5b48a64a449d8adfed028fcfcf1371f3116a6ee67cf7f8c33d51565b0ee2f051ad3df182b79f3aabf4ee8574aaaaf934604c5571eae19e61165f6fb5a76f6acca86e112fcf923f0e5a0553bac81c27bcc2a9e5511ddfbbca4774c7e874f1deecc83341bce587395999ffbd6ac93e4ee6911de9f39c5f2e3687b89e18d98952e784369919c8563edec90d898071abe66bb2f273ea669e9b97955e358db56d5dfa23bf4160f2eb43646eddfc902cf1692d9078d30394ef3a42def26d8299125b20991f588357e1ae360c700d5bacb413d4cbee5043e3d2a6e73dec751f92a8b7fe70ca957e2f86b50839af1aac2ff10a952e0995ac26b38836145c6f1441864510575e38eac0efdb3f6ec6e9db3d18916130e5133681bd5ee2cba340f3f53c597fb76e2fb21947017183000ae142835d1a0fd9fbb7466043558605eba888f686ece4d1904f87daa0b91af687b6c6d5e0ace468dbf9d0402bf13f4d6a907e0d96dfa4a41183a3c5771092bcdcceb139558e5f9d47bf4a84f9ba49a753cc9ef8ef211b4e59cab5edd0d4315eee63a5ba1e7dff70b13384417128719b4804f336d2e712862b8401177b378045b97098f6c16cf342a7950e4f552498520c96410437eccd0cbdbc0111180d173af9ad1bdb37798dddb85ede23122a9bd8cdf6a2a6d559a972700418cbec04fdf21242bd98981e0782c8881af0875ceb0363855772f72b4d53e74ef03673e38dd225c9db9e36c596b0264eb91e8531a140e55255394f71c3a37ff9e6d3e77953ae0a7732a49b8ee94397832be9a07f8a1ab9b818f3de081e9b8ad27b9ff7bc84fbe1f5af0796ea813d987a4adec03dc1bda93421d6e746e43cdba796362cc2340db723c2196c366c85d050fb2fd89ea1bd9fc2c28f3345d0e4c867afeeeeff83d2f444ede07522ba5ab363c394b609050868921a1f3f8e3081338e41a931600a8b94e278af651e5ed06f0368b92931c1c6a2c6725cb123b7e25e017eea8284a853319ae1be9a53fc275a30eb5128277fb7a32d10c9a5fcc6dcde0043236f493378417e32e8a7e51bd18dcac112876ac4e092c91ff50607de692696acd608991a3ccda0920b26b1ee174c663bfdc48da1c0164e072ff278a566b5c5df52aabbc82ad5c77190e5da94b58b2b54e734198fc5901e96a537298b1735e235855c18ffc8c155f92c4b2879ac9462455eeca7a6b5867912c80d780c62f39f7712979b70ff0fcdcd7901cab5ff031f140369fcb050a848d5c69f5e11b4532f5a6cea261c2a05382d788a86bd913c554a2bbd8e9725119c01254e78766ac7f619326624b7cd8760ef6adcb2dd45ea1051a59259a14fd04ffa56889e3d53cb37e03699c1fec8711ef8d882a9cc068101cd6bdf96300c5787ae31c42121895782b6d8610e82acbbe66d69545ee1fd7fd5e6b5762358faa2da13b8204eba367f2a511c6f471f7d5a006a6cd9b808f58deac53b49f13329e83581e42b5298caa26e557374466d0cd561df711545d875dd75f89df953694b3a9733e9e0a6f82c4e6238502edae435b3e0e95841eb4c9a23094e1cc2385cec6b160d59948245eae04b3cce02be4a8104a22ba509a262d0d85748f6da2a9b7403211ca0966f31f91fe00296cdb43bd7b75cd07628c5dde7e5fb4baa5b10113b35810e92037328d127713b77285b8650ff8ac3d568a67a7aa0d544db113bed6f79aa025a98a3dea6ad4c8cca5dff409c988e781961781b223c1428930e3ad755d12cb8add748b5044c9310f936c8a9c14971628b14dc19431a5491ca0c6b38b9463ed957034ebffedd8f98732ab2fdc5e1d4bf1cc74ee24eec7237b3d74f1f878153621304773326209ad322cbd11429c8f1826c10e01ba4b0efbd61cfdb4dc8e5817c1fe86756a92eb063230fc864c7dba5e599fc401327d930846006009635495b9445f934e88606ff18e0e0e679c79dbeaaf25801d4a0a79e8701307dcd8285186c93c4f1890be402bef66d61635f4edb7a6c694429a7d820fb98612e3b1549a66cb0b4895c372465c660760dd402a87b4157be6fb889c390bed5e2e189a134ff6a7dd34766b89f0cce3e4869189453c94c67fbde2b5e9b84e3076a0a3c20d0627f70e24f3c91619163c43f31cdad8329b1c125480faea02e3bc08b7ce061eb3243707c83488359e94c216edde71c0b793025e1d3f85a0405ba30be52693d2d0748b8209d6dd99db3bc941b30321d35be533fc71ef2be1cadcbe5ec0377c44fbd34eab71f76e49055f3b1aad33f439a7cacfed87e76ad350fa599183f8f254ba395c3c03c62a235be942b11c22d45ba975443ff208a7d0f412d6cb6e1a47cda5f05c0a85d63ebf5d31f1a3cf6998d79fb06078c9e6dd29d7fc7a875fb85bab2ad7ab4a96f557e99f9f72c41ace596c31060a8e55dc34081f8570919a798e87e298fe5b5191f484e8ca3ebf0620490dfa2fc7676c32b8e620ea64ca47872bcf146f4a0e1baa6225740c4b6312a7533c5779730dcf86be297347f5bac60d4dcb3943bf7959dcbbd1b5f624a85e5bcc2c8785282d41cfab97543474c5a68ea5566be51cd340227ac5a6a3711d17c5d80b9657480b2f383ecdc7d1124ef2a77aed673e97b995d4077bf2bdaf0170f966a17eb69ca5030b2e4927996a6d48022a0fda8ebb439e0fdcbd0c69043ef835f5376f9a8372a2e2c41111f06ea7d06337ee9b408265672d2eabf80348b9ccce61fcd6fff7d655587011f6443eefcebbd3be579c3096e9536783ee12b6905b96dd11dd2e168b30da945fd62799db7cbf142aa4b8338b88ef9000fabcee1b3ad1aab57824875bede4906302000360ec3a4bb9a65be0cb2689be7fcaade68d250b9e1ec020fdf9d9497578e339b90a1a2b3f4f0a11419b95e64926695640a5fcde9190b20f2fe11303179dc03e45df098b231fbbffdf51ea820b07947d09b6d1c509b8571b42383ba5afeb3f1629fd4e106033000000000000000143a5b5019c60be7e16be5506d11d319f3f42b059216bf5e3d1a56e67ba1171c47e3d5276c7f540c5698540a1dad230661e2c5c6cd43f6936bb772432589c621a0d1d10b83045595814bf1bb84471e2279fb4e661bf97c4eaa08083c55bb5d26e9315efb5966f0c152d3dbfb06f81b06b6b6adeb00053038b8230cdd74014f3c69b6d9b269b0b3eed4020abbc457e1c4b11a425dd27000e5dc44493c59bddeccb298d2de50e3328dda9b079d8fe72ced033fed61ea41eb3ebddf09efa0c64ba983817540a4b5faa864508aa989708897858ca98c027c80b152b4bcb34afec416a63521e3fd8c88be607c3bd6229874250e00454d20509eb30fd50bbf03423fa7681c449c347ceb1be34994290a04e38d186f54f37aa829eb1cbc295888855287e4ab9bf0e70cffb3cd6768ff80bb6889353ab2b6253241e2842e4c10a86a0f1f26dcbe6d586a9352f8ef8440c4ef2ef3d09dc3aff80d8f5e6ad2cbbc6682bd974a3733deaed2a2b7906cc6b96b6b08e2868a9b13deb4c9c748cb45861f0829f819b8432911c60d456ac89af653a3f0c23a776846fcf1fc2c7f2b9492c261d73ef1963ca66443af1056941b0b9d72640af3f7f1ecd28f7e25385b4cf002a6d7848451d13b1feb65e217ba274ba0b95b688225b0c54b63e6e3ee006886f9896e5b89d1bf97f0d8b00e6f4d1927b957ebbadb353785a9424bf007b26f84af99fc20cb9f3dbff54595f33f4a1fe815d554269bb3aa35342033b00118d3320cbcb82f7b3d0c6f4d2bfc99a71d51eb14f15dddb82a553cf1f56f70eb7fdfb58dc1b9a409a9c33345b207e5c0f7807f25b00a6a4ca6979b06c02bf90069e1087a6aaf571a1df8c64fe5168932b0e964cc4dda69c664ef58b9040ce16ebd084830d5eaf973f846400e82ef3a711f95ddf35705128adddb3e7563c5fa898c138b9045e7a6813584fd3b48041d80410b115ede1c378240a44c36cf8057b16ba632b40c425d0e564d7f92716221ed99553a7d270a73dae65d3c55d07b9f8f1ea06c70eec8f6434bec0d02af0500e27ce371aeb77f441e10ddcf85040b42243006c232d4c816eb54ae61a24c92dce20a470cb4f1deeaff8ac5f6bed8ccde8a7b23b3f467f0786ff5439d862f5f151d182e11c3d4dc983774930b7cba8048fc04b71bc3c088f83c3354de258de13d02ec29b2eae91c61a831c5c487957da505f1031e5b4986ad98c955d95d4bf8eceed15d42784ce11e9fb6958c5e84d4518a8ad4b0760a13721ec99ab1cbf307053c939a7600cf7263185669051936c0f1a2099b0b7bb1cbcacd726df96cf6de3cbdb2246b28a14f67d6b802d733434b968274cecd4229b52b826967a8f8c97ac6323a1d327ba13a7e27be5a3b807f967e3da2f12466e51716a1863604a2c53c3471cce67f8ac0e816aaaa25aa48ac5f9ea76f825546e21cb7210c3cd11844bc4db9b037deaab1368f50781bbd245e62c1f1eda71ef8024fd7e23d17933bdd9b4056586ceefe53a108c75b64808e7d1d6b144d9cbae3637c2b3fea653ecfebfeec133306df5a29339d09836c26b891bf4881ff3e889b850629bb8ff0beb0b505e24fbfe094202954f4a2c48ed08d5c286194ca8fb0fd127d093d2959b049e0618f193d4c5c46f31c5a0ae0f79943029568cb2192393f7105922a272f2d8d1e2754277d52b942fdd272b464bdcdca69148d06e79b07ef773cffe8589ff515844065effda63015d2c66333127a7333f8abaf4a389c5b0aaadcce720d6aacb1c7cfac2964f922c21bda4be69d56c7cbc2de6c56dd2c60bb8dbc98a8d024d26373a694bcddce56c91d63bc2cd17b1899244f34a582c5c9cf81c27fb3cfed8be054846b4292db964bd3b269f9a4cb95f75ffa8a29fd4c0fa44e3d8bb5324da5a0b10b5057fe02e5e0d96e41bb9ef3700da3e9bb84f08a0e4b3e01cf41dd545e4635082cc350bf87bff8cc50dc51ecd447e488b6deff26069a3a1b6494074bbdf968b6435120338671ddc687cdd9506afdc3cf9cfe5c8075d31491b31f2017a08b3dfdecb61c21cf1121bed0292ff90730e31e8fa5317e321ea3821f56ff22f024b8cc836842c349ab98a7aa8dc94367fdc802fe2fdd2fde334b6d81b5be66bd69e211f940c442972e54c2f159b668841658e20d96e82701242b3ef3de9d311becd9fa1bddeb61f42a3fab879d63fe424ddccedf6dc63e647e83028d5a530c7797fdb2d6fc35c8c95dedec86db5cc3d8e4a2c13c89fb46232431d2afd0953d5104c83a5c03eb9b5d08ac9b25a788cd24df846ceddc6cb1e1051dea31c5a6c3b147f6a47e44ca9853f593e70e41405343ab223d8692a8fb6156ca2e87bf069350ef89795274f59b1f2315cb7cf503f07fa30815e7e4906ed62cf706c9d1ae0c7816155517a45581f046129af7958049a5a528e16bc5faccb1dd73ff17cccc0f74dc7d375d72f6759812fdd974ac0e96b87c0175b3814aa2efb2106449d22022473663bd40397942ea5b2e3577962b2ece7c44b7dbb5cdc14a2c33f7434149abab2334c8f7ee758badfbd5dd53b172d7ac395d820fcec3814dae32540b54daa3ee30c6ecb945298582a46854c7705a22312f128cf3c1a886245b832e47ac6dd3f07b1911d857407b6f3997b5ea83651a4cf247eab9c8782266a16ec35785d48dc46822ae17dd4bca33d6d38e6a4178733716d49320cb1686c28cecc79c9fe311a1866fd062462549a7ab99418c4eb1a4b99a3766d2770aedc61a9b406b7bec6483efb0b48c3a8b9b95c3e45b9bd4cce770bc40e627567b4e0fe1a4238debae657db1b3f5e41202b41bad91e54a16a1925563a898534f60b74dcad2a053fb2c5a1e1272b83cebddef2cef1dfb98129ce4748e754b7a573f9397b07c2be2d531759e0f7df84fd3b5b076d052c3569021bd22411b3130c432a830be73df7d80743ff0c1547e2e2b120be519884b06550dffc554a08961b79fabc91d5fcac7b98ba98df4f937116e5fb1003131504d3debaf37398db9e09ad4628d586c24e5715c974fc69d092e3e1e4e960c2cc9372f8d6de652fd34a23fe1f02ccf4ef3d5b75782a010f59ff7c950f8e423b0d388380bfff750c13b68913c1841e2029b9a35e5f0daa9f80ca55940ef8121383af699f12f1c540ef3827ed6f985690bc2a1dad4d9746978d423982a1602069ac60c32540355c4edcaa525d6e51979f9fab4582d8e579cc9aff0b9be31217757fd378749189be44f75d48fab4ff3749e8d07fca74ad86411dec45e0b820120e6c4d5d38d332d8617ed439598264e3be7a5d033fa7f3598ec0f309afef0ef8ffbc3f0826b4e1e03f73b4fb9bef889bacf5bdc3bf8f570d6899ef72801283f517bc6677b338633a79de18491912eaf630c89fc1e9ac42eb1fdb81ff6bbc1a0a7e148ec473de69d22b24a061e1526fad4a979af458d959de9aac53774f07a2d274d87bd7a26d93ffd623245fe00f5bc26e40479ee3af683c2e0aefebc3115f47de8a087605714c29876e9797004571c52dcb4ac395d1b973efe6fde5d1bea686e85d661af0436c3fd7d0d31b94794dcf20cea5b32329ffdafb864015f7fce8d5ee30f6fbf69fbf763e2fd98d3d636c4b182320fbb64ce7ae4ca52a554220607f1832502cab93ccdd2deb267e19977fb4030e2908d5792fd9540c8eaf822c2711d2f9d0d19348f43ce422043ea54cc3d9feac90b1b5a51fe996e3d33a62b9278c6fb3090a2326047c8a8d977615b0011f735c983c2966c506dd8a61c00c32b1cf939aa064d1b4d0bac6176c534fdb06e3a32fb9fe613add69aa2cfd385cd800faa2256458fbddeff833dd118e5a589d52f0f7e3ce12d9ec317131c64f4ccc907742a4546ad7c0beda679b215db0d4b37e8eeb123554333c7ed1a0f2456d627e34bc19f5ac6a42538f5c482de636031e23f14f6eb28133fc8529a395af6365668aa871f06a955d5305a24bc4c13f43daaff426ffb75f50bbcc494247503d96a6734f8a897cfe2d54d2be208f8790e0e8934c6c798e4d07c85fa0a080e2246242223e12b75d1389e4c789b28241d396c43f160df74c8f44291bb5de76078fe44a5210cd2a4b02235cb0801dba2a781037014f4d3c86be3839c5704110be21f43a7a90a0d2819620c7a501e96f1540460cba9319321100bdd0b023af3518e0e5e675f82832914f8d20dc8e9140a4a86b07cbebeefda16c5e2b97bfc719a97b8c1000c549d9585b3ab7ce68cd04e34f51df77c1da1ce3f5250b4ca8fac4bd906e24acc3b60fca8fdaf82d674f7069befb6c7027d9d052fa74add353316f3b50b24d304ba35b20bea481c4ed41d5d5e4714d3fd5f96601a6dc404cef76638edc84be11e84f2dadf3fceceaf92559d5309f116c708ae62cec72fc62f65c539ebf5929c55ab5416b7e037f2cc3b24fd4dc53d65b3b8c0bbc7e81909840375f9b4bc3e0d0a21fd4eb9f777a5a09272c68f74d645d8075afde7c3e3d533e4fd69d375794566f5fc6ab4a8922db31b9300f831b819cb0696dfb248c59e6580b4205fa014aad8a9a1c66cbce92f6236a4e0799840fd5da4dc0f9ce05a873504a75832a7653db84d5aca9f87584f1a4a8b6547c7dc00b81edf426d5cb568d520245a529ee1d772835f44e30d9fe3239dabed7bf6b61bfd4b1add4ade5a51ff6983fd51a66218676465486f02f7df41e400d49ad28abd31fa9ccd1f57629a029f9bed07c2a04866561de5beeb18a7992e82dd2fde42fe38b24af2b3139d4df1e5153f411ed1e848b9c2eb1cd919676eda3f3d7d646bd5cb73331e5ec39c83a16b8942069abcd5b85c2a767b5c0813b90035cf8080b8f999c23b30ce2ae2ca6e96af87e1e5f95eeda75e29cca622e2709ba4ef62a2a4853f6a063d058306b208d8996d8005069f832826f1aaba9e23745a61ff442f0d4567c076ecb491739363f42498e7178f042812a1c02e89ea5f4529009981e913a3207afebdb7ef23cdd28c79af6e68c199b765b825294d8c2bd577bc944bc01e305b36c5e245fea06a0c00a6c3d1600ad0f9bea27449c7e06f87034284ac24a9be38e9d73460089ff970d961bee12023e4effe1ebba183e18b6a6c1b8373879315f208d5123b4f9f5d491a3c235fadf1a8f26a103d56974f62d17dc7151dafc175bc4c34b2f1dbb80e029b721249145e7b1c3a1041b631248c3f411df7e0cb2f27e5e6eaf877639ea1e90b2e5661e80f90ad174e8e61c43ba25175f930e2a94974f3143f59a4617edf78657247c152aae09c56d02e4752c306c4a5837d2f038852b2ebb2d97047a3297141d70d617a7fdcc075e644a3332a8077487e9dcb1f056ec08f8571bd1664a432bceb7c90dc8c7179aedabead80a3eef589ee32fef3de6f62431962eb71098442819adee0e030b0e2a932eaf6424d3e21a2b0406bc144c98dc3d43f9c3d997a20db1c0e961dbfa33c820cc10f9473720e409a16291dd508e8f598c1cb008421e2859e5a69206037109e0282f20981b4653433e36637b52822513b3fd73573245fcf07e54f16a7fcfce5b3315f1945592bac6b504e67e0f396372fa19a7ad1a18ebff55601919c24568722057268dc9fbd615e257d04aceab8c4a759b1845e746de4b6685c8311a33aadd1e42b0e5511defdb279dc376d58206341894d445ebb2332c1f67a8a91da91edaec0c481e77b3e2e19dfbb45912fb8975fad75ea5ef2fcb6603f0ea84412e1cbbf0515958af9e23a7a9e34ecc2eefcfafdf0117270e566b4b9f88cdca7723b7795060bbb1484bb5c2a136d275bffd3b98b9e6fe14073d1fbcb31bf00b984dbc2b5835d953ed72551bf8249bb79d873c1d06e296e40a2b9a818bc99156ccfd403fc605299b6fda4de8f372f42da3f268d94e21717817bd63020003820741c5e301402efa10931ad58263623b77ea178607abd7c1e4ecf6f286e2c0e208fefe43be567d4d845d1a35ed6b3f74f52df3bc46037c95e06944f66c7732db210cb3fa8d4cbd3bbb79434dee4609796b828840f017c3d229715e6a76cb70fd4e10603300000000000000017796c600f697d8c4a11a172c989dd0726e0411b329dda4e1f67ee73edb3327f669a91cd3e3977c66de03a2785c4a864ac38363bba4d4f5c86ca5d965fac5e949b8a8b64d6a58ad4dc3981f5e053d76c1f6b90c910da6b1e5adc468e0fd8eb81c101d521808fcb5c21b386ea9997d3a5aa796331b14d078a3063c21a58ba58da1f80b9a9d31b0392954d8c85d11e629f38906068e46e71892454507ee2f00031c0380b6a2809fa8947187406d77533754a52aac97a109f290ce47ffe213c824e4a306e6aac2f6a832e7585b12e0c50d58628a7cc37e6b6dbc23b5b905dd5971880a6b01fb778eb1ebd00123a289251e77c6faa93690852cad09ac0f2d8fa5b482f3e07f8ec79afcb1ee68f676c69b9e22894f57807c71911f0e0124c3c2beb98e2edd743e5e62aaf7999026e6be54b5ca6d7e65c0439e13c0d16588e1185f6df325154d84a25e97cd1fec156d9c8c267cb5601dcde096353c63ba9fbf0454d18bd7d299ef22e7cfea546f4eb20320cf68e73537ebf701391573bbac7d9d74ad2ee7156a2ee9a04dbca6069fa7be0477e58e92b961005f3628da991a9f690ff774dc1d8a714ba594d4e4950f8e95e965df837af9b83ad745ae6c70b08aa96ee1528bfddec4edf627bf36bc3d78d119180dcef02fd635ed6576d136505003aa23a7602f34fdd02a4474a56b9c3c3057fa820962c25f4c8265e133a1cfb18ed803e1c6a12ca9f07145b92a7db233a47fde30990dc85618627ede45b345411333e9af3426edc4248ffc037694f4435f0a98f87f0bf74b8c0e9be583ed6e4817d3fb2aff0e6af55f6c6a700270d0eb504abfa9b840139b3023c38d047450e4646c3d9a588e6b2c02e1e9ad26be13fc96fe8e256579b7fc18ac9e3450966e6d70cc41dffd415fe7d9bb2e3397e91d3799a2e0a08fa1b42ea29d8a1476c6fc0f72600884dea3af729edb85823859a37ce82bca5ba4c0da6b34eae8e3814d5950d53da109731e83cc374c65a96d3b91491d0f89d99542afaccdff18e18a42cebea88894dee94977f88279af63fe9db3910116333dc070315cd6b068e6fc946fc0155a77ac8a5f134b64a285ac1fbcd87f410497ef6fd38425720688edcdce1b6f7ff799e9e65311bdd7a4c278e266dec5b582839752ec43307f9207a6beb16283f36746c63b5629eb0791c0afc1179845f4efb1c01d655064cab995d260636354b97c9ff69c1d3aca2badb65a6de564612c0f672362d0d7ff5e18352e9c009b60f419668bd3df480ab7a5f8b2e2bb950416701289052722ba205302e35e5b31527f00a623190b99e742f2be808fdc5cd87f7121121a8a21da5066a32055803aa6b9a91e0773efc94a86ca8b5535d1dbf80d1f595295b9750745975e870dbed07e0e6047c5a98d9281bf63f3b3a29212499e7c672917b66df39790d3d80e9219f22726eb410837449888eefcf64c37f7d97a849177f54692d93be39a9d9cae16c6b4be0444fb578855ac8e97e6aa6e07688572fe62abd6b2dc983ddb89920070cd1ac11d1b27d3aa7edf432d381a4608a845c04e5f25ce62985fef04734222fa4ac2d4919af9c1efac312e9296ddddff61ee57d2ae9b2da5287a9a50447d21d5c8850580a37402d9126c17daea16122328d2b013fc6dbc20e0848c2eedcbda70d6dc9b95ededb65e5baecab25bab57acccecde87bf08eba8ab72c80cd23721e921a01a653d075e6adaebe97fa3e21f6ca6774effdfc6edbeb2b76420a46f8fac9bf84ca7f1a19af5be59c4be42db1161f741c800d7bce42c7b3949a0172105ab158e79df0bc307c61f330604e6a33152707f5ba55c60f8f9730316c4959c710f5e21e2235105aa819345955543873f3d001e9742eec59d822419976025210509fe9e8e64e4ccc8e5ac5b8ddf0085cf0c07eefa03c269c9c6c919c1621de90e990efaa98874603b5c2f70b096ac2ec024d8f3a87b51bd3e65195af60253d8145e79c17d04bbe9a460e7cf9fa6771b7f0bd9cf7bc04d1513b5872bf3f62e3863c84c060703a920747e9aa8e87f16003dddb3802c7267490069f78a6822bbbec6bb01ec0c65180578beee51f97b4d381611a94d5804c91a4dd65d21b3429d70372746f36e7f77c3d029964e913db5af8c18ac532400c7e2ffb6006de7dc64d473d645bc47318db9c232d07bdf514fccffdd8ac7862c5f5f0d756d0f3924dfef6e9f98345974056212d258340bb5837ae9ea4503fb0fd3db8977f49e3b12f8e47b883b29139362b5589a18139ef9a2526d4ac0fd81e2f8a0e4b9f0265eec3f2471c9ff93ede0f26794720235357ccd806ee1288bf2e0f5ff77e8e210c5c3f90b038db1f6aab826008155171995aa51eb17289dd08a531ba7b069731d22be14cc3f613911ff75748e3ad4f467e9f7d9256e8a31c8a6e89a1f548d81f23e394fce754811ca6522c03b438f23c324fb22e88d5fc3314f87480b75ab7c95368c4e34aeb6983d02d07cc9f6f6849ffe37121349637d4358237fdd4db015c069b3d535ad534deeb4605f4c4a7b17a8a5d9f3b4ad8d4853a6d5b7ce61aa84d0141d3c0122f5579178067e5c98d12ecb737f2595d37838057de67c825c72e56c06cdf43d907b808ab8c8f3428ac60d37b33dba3937e5b4cace5f8fe87e45f12abc3cb74c2d89a167afed25cfc44cb74eeeb2c91903774a2859de55b0ae25e1428f7af8731d66aa6af0cceaf50bbbfb4c20297eb385b3d74d2cbc3f4c251b5cd8fc458fb824eb27304248e46c9b7ea15cae4eeb9e2615e29cf30f910b79f6899fddde79daf32740083328f48194e03d8ae2a15a7383deff1cd45479daac780140315cee4bcf017b1ecded8a976701f508379187768eaa6fb6b91e6d640a907cf1d9b0639b51783fd9dda7ba50a887b09219ab52f008cc80ca60bf291f2e8acb7c54c149d532c608fcd617ba94a2fa3d8c1088fc3ccf7115156f80e7317a5d539b86344a89e155c112f6519863e37ff9319133b09802d571e99e41b9be76400bfe5adb3977cf678652225459e408483781f23c9f67e3aa07c4824cf41be8befc9ad95fb50073bedf0ae34668e158190c25631edb2cdbf85c9660c896e1246d11afcc633babbcdb8e66f367c8f424ec06000427538afd14a1222e152cf1f0dead40ca554131654eb4e841f709950835c238a51ad8e43bf7b85fa2a2ef40750417213c61f6d7c0bef8646257c9d527a00bbbf2d1abbcdb662e26a58e6e149f6fe01bb113aaad643dfeaf9dc39338ee4481f82adabab672100d1ec19fb0b0367fce44b8ad4f0e186d470d569a8fdd76effaf4c1490bb144897ac5898bda12ce161de69db2e18293b65b1afb26cd211e7ee7bffb915663ab80f4a990fe74d49e0b222b675e78fde40b4224e62bc4c3d08dcdbcf5f7ff93cc687e847f15402447edd303ac802e72026db8d14e530d29fac06ab3100054ad578ac1aa0b1f3a6696dd28d280b8f6b8cd1399eedf377b1bba9dfbb1ff2c2664e2f7b83de7465d6ca3c2d1e9d33d9857b1819bdf2b99c88fb212e6755ae6990760ed8700c858f245c3e37cc5cb3ac338fdaec8ff8c38097b0038bc9a9222a5b26fab9de55919f8fc1bb96eaea294c6fc7becd37a7ecb8bee4a083c0f8c721cc01b79c3aec3f871360c610480664a4074a0144a6047d86fe35c2948e4a94eeaf443f47aee41df7da0852aa743523600c80b22cb4a011fd546534be4ff95d831f1e1ec5d4a425a94f3a34d35157604dbba6bdd569e51783fef17a522a05e1a7170caa0872267a933843a9afd1b8569d72aaff5fbb29f2b9bae7995b40f52d86d524eeaeed1f847bc42333f602730b71b715d15cec39f954cf86a1556ad3a73e874500b90ad63f7229d9026ff1643a3a490bd44f26639e017b3f3996776cd28f9ec980443166472d55986ee8a600236dc02cf0c5b9579158c7a8313f67794912577dd806d7420910620cb8693711f73e63056c9deed8678a14eaca1a5a6dd338231dd13199560d1c6152935a5e7d7ae067e20bf3f53c0174f2eae167f2110ffcf7e783f4b15e8a66db78336f177315e252534fd041a34a05f4390f8736bffcb92a9cefe45b569dd0a189838e3857981baeceba82b08dc212e6db4cf45150cc8ce9104e6209c20641ad7eb80bacd2dc674697fb0225d545b8fa50b7652a91ab819cee4f30328b352f4b69ee304f2318f58aa692b0dbe333561c11d0c5fca8f1030a6df6bcb5343cebf7d2da20eb4238b83da5b3b4df6b9af984597a95a9a09bf21af0bee4b0781d90bbd10b6bd4d0e061eb786acfb623e6e4d767d30e810740d888df523cf6c8f52c7b25b06619e7baa3fc510414cc19b33ccf080dc964f858cb359b62d9acdc0c07498668ab6567f70a72422736bb5aacc3cc01a1721610340b1cb6ff2e5d2bc9dbd34afd4f299e3a3603ec1e05f8a5961ccffa77c2fd974f3046bedc0c555ab58e61caf48426082e041363984ef8002730c9d4fa0f30c019d03856e874733cf89a6729822bb6bc2451fe93363eea57c3b89dfbd0b2b12144ab0736e271574a1a0c4adf31131da26df06b74b415c2cdf4b4120c845a03a211c5dcf1812632600f7dbbf2516537714ec439b1fc6f10a0d76221419d972356d3f023535040c0f99a8f8bec8b0e2bf7d8a205f1ff0b1a1891916ce8bb1832e9e407d740310f6c86be8ec87896f867dd1070fcf8ff8e75dfafea01cebe2fcedbacdb4465129062b8c816f0ac32520afe3bbdeaa9e7e18f2493d028ad4c20d135fb8f2f33d2431d2e94589ce92c6a3e7ae96657a3e2154e80c9fddb67c5eb3538ebceeaf25d893ab767f780a41d5c6e8503b15bbb1b4acff771ecf0a0c70dc40e7ae0db703aa64265a3e291ec61b407c2b0f97ddf08d3596bfd7b1e034cd320085e72ac6ae28dedfe05c086d3ec8a8bf05d970639687b23de5c66951e0083674466df4281360fb41e4b4fe229fed506cb85982a6034c4ea9da788cea969659c33297e4da63bb638fa5f87e0ecf733d7d747167a2e48bbeaefd738cfacf2e7e6e691166dea0070db65646eeab651224188e38f1ad9175cec9de24a804e4cc6210ece8a3f09eb6182a184cc8eed6cb927488106f574caf99dedcd89cb964396cb7842c346c99b9afe99138d416d751f39dbf4c222bbbca0cd39d591321e8417f5327755fac4c193c1e2adc1da79861d21c3f71d58251df8b823b0314e0e3c8faee7d7354ceca4fbb0b03e0899b2d1dae292cf8b6dec76404d0b9b0a521a272d98e77d7c9582c607ce71b960c0387587ade2170ce31e7214da35b6e4260ab1d89d11ead8df7379f874cfb73893120efd5cd18d05b50ea11332922c501b94eae971b388c701bb0d1d22358a6ceea2e15d1494e9227dc21e424fa14b1852329282fa5abeabbd3e331ff7f09f510322e33268e6e2d39a8856c726a735230027cf38fb3bd35f771d7dc0c41ffd3d7913e27f055956c10611653685c2626a0ad4f21e0117669d9c462f5cfc941c1950f1d990144bd30084dcfbc6bb415f9913915a1830f4ce99af13dc7c74ac30df4b386b20a7cbea46633186b2d46e83e25119b9a8a628fb856e13ebbcd648744e767866d6e8418c616ff18e798b530b2bdbf88f9e4bd400aa3cc8d46f31a763eaecf43affa3fd241540bc7af39aa147303dab8f4d639d16260a4d95e5401577bc6cd1ab497d99e092174a19c297af999733e5efcdcc0b92309be3a4c8a776c17841dc070ae7d977c3baf19523b691778387dc4ab5de4b462c7cacab9cc0a85580f8086c352226c5b71bd1986b87f5cbe856ebb315b169c9cf35cbf78cb8ed019c1e2873ed8e77f0543bdf085a41f1e00fb3c6a629c53371c8f0b4059bb0b337d2aa2e940c73d40ea1695a0272b24d178f18751374e63020003079b276e97ee1ca0a84fd5c462e014c0a9dd29d2f6f63020e904215b1c3369cec65c2d2e64551823dc3d4ed90db729ebfeaf6ac0747cfa818a75c57953866f3efea5d3d2e9eaac6640836be4d66a656203c4c59337894e252fe2a4343d88bab9fd4e1060330000000000000001e12f00004b0cd0b3a784062ba31c1181565aa2ddaebe8cd0732586fc1fc37f41ed8fc3467eac595c4314f7543de0d1eaab4dcc8a40292ae938d563f8e508eb89dacb5ca85d1a1921e20e898472a3cade302b2f0be6039f8f93fc5c66cd10fcedfa89522d2fa94657ee28261504645ce76ab786b69fe85c5743c6e769fd489f9f697837f7e7accf0556aa268e4af28757e7ebdc1eb3ea36765c3e98519705d2f11b0a41146beededfc7281be9f3bae0579a789b2e55def54a048b3c8eb49b3c2eeb3b5d3ac21fb87e3f431a654fd6b2fb74a0f3fa1f1b48bab5fcbbf6f2808560817c692f9ecb4e0ecc8ebf33385c89956622c8d1ef35984701c4dd5c21b22dc6be47ad22c7c9ae7a26034635745ec263ba61f9ee76e1d4279bc815359353ce4a776322527af1c67fc4eae1904ee716d93d1cd51d49fdf27f7200c7c1d89d65688abea94f538b43a2055343462e4b9627d4bc6c38356540d3467b203d4675c74695f04f4c818fe288fba8202a95c83f966a9dd98ff0729ce8a6caf51e6c3eddc1aa4354b7467e5d0fc7d72933a445bf32c093a607471ab039fac55f7114e4626ce22e75163c8501797141680733ceb83ac0c76892f07435117bc86171d230c2790cb326b5465de23a8e83acf61dd866b47632ad3dec8dbc25ffc5373d116398e34dbc69334581b6eea3c95364866a4cd184a69cffd1c1a87a391368cb6e7436b3f46c785380613c041ae296dce4dbb25f2cf3095979bda8b8a4d5ece7d374427bd0663efb00a215d7f48eb7544efe666c8d834d201a51bb80cc46f56722ba7cf34ae6c45e4aa6c30d4dace1cc227f6e5fc4c26257523e798cf014b7fd0867455276c5522e5a24d9e168aea61dfb7da4b1c85b0859cc5bfb9f461dd94b0e65b88ec882cc1c49c33faef4d923c15780baf38fb57dbb0df54d3ebd30f626aad51f28126e96df32244041c38e1abc7b82061d5942cb98d69a0330b9eeea31209f4e66a72ce4f6dd771a78969ecdc345e066349584c16adf43a07556cb84c0558b3691f6910205d8286a8e4ace8c3612c227decb05f794e245bbfd0a6105a879fc38e53d863feaa24d0a09e374884336afec64978c0ea5e1751f9d4895cea48da3cdbc8f1547df46a507df56663ee41e64e04004a30435be0cf0e637cc5f5d32ab78e0ba31bcb9759e98b4ae27b01b3468adea384ceefbd4596eec313602c87b07f82c76fb1322e650a41cd3dcd05c195b94f254cec7ff56f11f0e2ac62c0c6b683d36f08f4917241e4768db16f90969aeb6d102b3340d3a39473c1d06003bd1dd616c590413c9880f07dbf04b12e4d0b262b47f64b185422d5d4919f8d819fcd0b553660387e45c15c199ca9ebb2eac411f3fc2db4e0199268717655e84a1b07a2bb0d11215d9de2459b33469aee7139907c947227e9cb3263525919607fa45380a0110c454f298fb14b6db0dced3675a94792c0aec305a09a77c833ef4df873d01dba5e73a8b40f963a07ee8a76dcdc9c6f193a4d5f2e7d349e42d664ae463e8214d21d23b758cec7a1aa852ee4613807b84ed4bad9c5431c376e0070502f8b0facc44f48fbdce51e843f224b7e9bee7ed41c755157c55d0b39f72a047812d0349f60c16eac9b79eed5e3a33c539d4e8c9ff15ba7c93a0a51f6c12fd5813f3d2b98c807f85bb0b19f3cb8ff19ae237dd2ce271c9a2dab2f3a3c76fff88c6fba43dbc78f3f8e79961084891400d1f9786b005eea34890f05cb44456b812ba89fc69377ad51afd6ee45a77bfcf6fba92ee0ac090d32739973ab794d218e7d8f6d96a1716f07829ff336fdd4e58986e65e148989ee657b25a7131e3bda51a83d86efb57b9aa1589620846e56390317b9647bd093cd6c7c5c9083cf458ee053184d0561515c1ce0fbf0883a39499d6df945d1f9744d75427ce88084caaf71b22a014b107c0dbc4b0b113c7e4c3244eaa7ee0a12fa61b0c146e9a05083e2b8ecb47b3b49f279a468ca3e948b8b9d25e4d7d0845d96de44ee1d0cb91c498a2a0a96665950572d2c054896dbba8e5a309bc0d3f5bd145fcd008f805af09a2ab9588ac1b8f0aa20abec84daacce02595b66de7244dd191355a740af11c693dadd139f0094410c1cb9cdce2b02648773b16bae1dd8b37eb697ced9a6509a9555096d6aa1480ee3c387cc46431483a56274c19eaeb5302f3d9548c8291ddf2c208c8f30c6e8ddde77b37083dbe03cf3a5cb27c1d323909ddc6129e54ee5462c6afaff160a43e88bfec436aa705a3317c014cba9b872b88f8f05790a6e68ecc61fb1364f0e846b95562b973c430d862e2c7f55d22dd4c99110f62a42d6ba63f15f2f04e40f6eb3ece59c3a0b4b762aead6512206fa6f13e11eac821ac81393115ce40840f65130d6a79d7efe3c9e2edae0ea9175fb8afb79439b82b8d89c4d9bea5efdc8ab5ca2975410b312e4d8e6ee5760639d2b00c090efcf9bde300922f7bca39521b1dfc200827e4c7320d405f8ced70241335f2c10697c98f69ece6552b041a763e440db2909992aa47a6bf5ee3220b81e1525fe315c4183e0999be5ff29214f79f1c16f070cdb57b274748423a6864ac5600a35a3d2da12215b4f6082ec3e0a0ca93688497d83a5dc0b73aaffd62ab6e56c94cf288c91b816a8be5de7f530a01ed6bb4e303a88e0bb2211388790fafeb095de3f9745903a373ccca71a2fa539a748884ee016ce72b69aaaa5a53c77d95f05d68d836df15536b5d8fd4e8a0384887a88e294d5f799c5100af96da315ee78625105590ead6a7a968127dfe5115e532d3df13ad37ba65d1d15d28a2dd2e050b3ea89e58ec0dc1a6c2d44dea5c960dd47719a2036b293be485e6dcf80c869d4c0e7269c362da6f621aeb7b589e56db07b1885d1b6f2eaafb53de5f4c8455564bfd302237df70531c0f3199e3624669d01e052d1ceec69e6aefa9a15d2b774b8ff1f574868af69fc5b67ed99912f4d5a0a4eff593df4cb6efd03841ad5d8175d6676014892679bc34bfd5792fb062693be3377590b25ea7907e26cc66ca33e0022d60be5689bde2e939b7d7c5da9b3e9403d6846e286d5673f2aef467a9da9aaa0230f665ad8f1498f139d5a19e98526740cb4d0b2fe70a98a50ed05b488a41c8b51774150730bd22ec132018a009aa42e014cb5f94397a743dba9321e8c87fbbfeacd4ee76a08e533c200c4138101a4257bbd14aa3e4d8afbab972d19b813ade52337f637dae72a335df5152aa4f77e25f052631309ff9e0b683808cd801ff0047c43c115b698514ace481f5672b1459de7325e36fe2ede6ef7c964006ab8bf55d29c8ed93dd8990e0ba9e0af9466ddc56a01f753ed895f43efca8ef43778656cf703f7b7ffedf4132b00686ac90009e251f5483598d35e9bccccd4cf2763c358d8739c965997a91b1cac424d1b1c7256c71b4b7ebdda7d8ed5ee536719eb5b32dd7dd0b6a92f150cd7bc4b803963999e5ed9ee6fd18ef0039bb60b001403d999f3dd553bc5a7e289cfaf443d6d2c934feef72d913251082684d608bdffebe5beeabc05a3dc0dfbc030de609b60f60bb8527030685da95f7e90a9a712213999deadb057f63963145e71e0d826dd1a4bec793245735fd65763ecd3f2565732ed2b03dd888f774d5618bfe299ded333d058e51b7e9f8c4fc9967368a140298ff9299107b61dfd3a7169136c156ed006a020aa510ac87853f350b39baaf0e5563bac34a3717dbcc6c8750f84a054ec0eb947567fd36aa868d4fced08f104a65dbe3d42400b6d3785b759616269bb22b3d5b1d45cbcc92eea408a2020c422a309742da792a3aa2127b2bc6cc9a58c1c151bc4af219a6cd3cdf487300e58133c2fc6417ccdc6587e2da15edeb1624b99ffe1824a72dd41298d3684ab87acdebd25b5f093a17c18bcf445c5b72ddb170c6dd39c2f11c3db04925d030450995d1c3abba499a30f1b72f8fb30b63d1d8163815d993edacd43bddd3932d5b3aa6dab6e48735a9f06c856dc661d6cb4ce7766567d4ffd08b6f66956789a80a1f34b63f14469e2800e3a6e08478a69bbd1e800b02137cbfe2cf8e61750273d8552690532b963fc3ea581e68197cf46c084f57817880c40501d361ffb78cc1df369ff412e30c08d8e3ef07e7af46afbf6720ed010917f6f332bffbf1ab8e0d1d36f1b8b01a7b30d8b32d7c20f21e521ea294e060ea67388324fd86d681eb80d79e988f8e5e956413810fbd07a4bc53993cffd0ebaec3cfe8fde0e0f14405b457bcdbecc563ccb1416fbb7d7c9c60d522fedc8e2b619bbcf628434db5be673f474e12eece9477d213441b6f6f698ab8d31f914b5e4575ea6eadcc0f2a4a298450576a397eeb6554c7b93557ed9e371288dab6921a380ef240f3d9ef1c9f79594fd42ca0e4118880ad4ab24d324e9c88f5231cf301a2d5e890be61f3a74969d506b723c61f01c6363b55ac8a4650ed3f2b66e748296583831423a1ee33c4649e73926574ffa5ec7700a69fd7f4f1108fa496bc3ae377346380727a02cb4c0b962d3e33b4fe1a0d080617c335c726b1039ad1acd4f3be02d3d973a6e44b7d7cbfc6c373a0491c5eef45135350fb444dd34a87ae66879ea1eba9359c37c7979006888b4a3c26ea6b6d1241599ba1c6063d8df3354d663fee705c67643537ab37b947eb2b07b1e10d39b5a988807ef4a05cbe4a3638fd28223cb88a74eaeef493d7c543e0a6287b15e8530ca04216e4993ab30090cc4d569b7577d6ed95f7800e2f292e7e31e6a80a5fb2a2f4b335176a2c2fbc891f8af12d4e81f30317b58f614e759e5e1f04e9f804b929fa285fc86e843e3d1b2e66c84f268bc3a858ccbb93f55eebb787dcaa3ec7fe37b3aefd732bccce2579e7e30d1ab018b7d6d09ab8b90a838888c35638339e528438fd6fd6e536e55f8503b9311c2823ac53788099e8ed16b362b428dd34795cda00e5934eb6ebbc180e373ddb22fe99ac91380fb83e5947891cc34d20a1ccee479ea6300ab0b4431fb9454987beb69c0481052184e0ddbb2b8ba5cbda85ddab07d1451a06fe105a4f134427e6c30086a0f1cf1d78ca3828aa9f975cb94a7402cfb00ec7881c7c8db9643d4c3dcc3ab96bc6c0e1d0511b110be2c6d7522726f8e6864c526c166325e66f051dc58d54d3eadd3762bdccd3ebd246d2376487118a2a3053e80acf68952e68f61477a229c6d1ad13db1540849e7f2fffd69ae9300052c8c020aa062dc1bb10527cdbe3eb272330e92e3c5bf63553ef835eea4093155c436904c822c99fc2abece776f7235d9078a09ae138ca6eb1e60ba39ac35deac241971365a79b851004abba6b9273300fc80036035975ce354e073f39bec73c22736517f41b44b5a7cb2265cc572ae4dcc9e338c02354865c42f9064c5ae8be4e45f7e15821037bf255c05abf8fc39d3a248b2c79a2776080e8d2cd5f82e9f9fd0d5d925b7e5f27699fcffbe4a43dbe84f716c22dd6575fa86e66e69e5803e130b9fc0be9e77ac55cee10b6aff5886af50ac0eede460254f9b58157aa8fa482d55b9d55305e620fa6cdf8bb31d817f3af83e7c40205f33336d82af51cc7bdb0e1b743b31cc85d4beed2871bc5a0b8433f6867a5435bfeb0e7ca8168fadf31c6c1b389208b61dadd6ea0a4df113e171b4027133b546d7e3817cce5a40bae7874eec67611ddbd9e7270ee7b6ee277ef658625fc2ca08c71a380be0f510cc2b2ec203bac7094ccd39e57a5627783a0fb16049846ba96dd29c60082679927ff0c42016d545946e83fdc8b1980b19fce2853fb63d84be896fa9070f33f8e0096d8a614c4acc66300aadb1e98728391048b82cf1955364c2f7dbd069af15c7debde6944af35dc3ccf51e3bf2323dfe77797a6cba7c6ce7b630200037a6da6709d05feb844b14c5034649b02a4edec50a0cfa3cf71ddf17bd9f3fddd51e6702cb84de29ef462d2ade164d6d500129a0901e200a4618f95cfb2399e1710d6c075b271e8a7764d5a13ada46126c93c21602c6707a9576c2358c126828bfd4e1060330000000000000001659882011fe7eb88ee594460f92d27d859f795e28255f5cfb23010ac9338ce38a3670635c90a8e64a86f05e0f632e2a86c19516596fb6f25cc8995aed488a447f560a7c7344dbad76b1f4b69678683ff69e35130e19adf1cf5d76a6cba6611eed175b1b088f71e089e4a4d0f0d32b8051afb42449905d2165b06bf9957e1a57bb34a0bb0f640851a69311a81a5b8e4fb64ab90c4182eb3b3b150d528a76e319b3ec1bc2dcde1bf377228bcc61593847a32ebb14ef6ef326cad33e73902f7d7a25e0a060328981effbb0b4df686cbcaa97e403309a94ee23552a1c0a22d98439f34155d2c3f1551370b2973658f4a7c7bffaece164df7fd7cab2f34994fc0f05673e992cf5835e25d658bb10feb6e9fb68e2fe490a24437cf24e545de9b30d31d391ce3898e3e56c87648d335c41dfb88fc61ec84c9a1d40e18a5a4796ebf6269b5f2d948a17adf5d1cfe19bb23f2280048d9edba6dfca0c6e4c231c864fcbe56cddbf0330062f284fd9555f1d540c5b88c2cad8d037a0b2539d8ed973bbc3a0d1f830252c4b68f1b84dc81e44ad1913adc0d39cd504c6ffc435aa91b8fb187f26c75280558bd016f9706df8918b61f49744d7f40537f7c5cbf5404ef534a94cd339f434b62beb8d4c075dcb40c8dd91823785bb41e7c529c147eef84aee7ae50e57a8cebe361c441611fe5e7d3f1a16b3f3f6ff09e7f3988ca586cfe2d3748347124d986bd87825666ba2a9215350e32abaa5b619b96200209b457ce9541045804a933ec3700d240b971ba7379a5b3a0563631c7e5a8c4561746006284ab01f44d84fbf1b1d920837bbad1eb11c6810232727f43eb7227632666f9646e4f9bec6836b57a1b86a3044b8ae84ff3468c10421135300712d24ef33769105c73e0d88d0ec53e1865bec30f05f099aa8ca5c60b862005a5780b235f95d6d5a13ece773425c676711e3fe933797beea6f695b06f71650e045f187024e2d7dfc67e419c4e4df903b51ccad8ba47d523bf45644d124cf1ca948f508a322ecdd3af066f52b51463e1df2507376742960320e1e30b34a292b0b24aa59b32ab94cd0627147d8a62e7caa175510408da1444420e4e89aa26e42ea422e84528d7dba8784e5027a0aebb6697bf242143188366a044a329eee7ab8dd241d40d8fd0bbcace7bcf33cee3003ca343694f23f2319d2001266580b8b501c27258c1afdb2364b7bb6d8027736c73f1256bbc4d0ab6411edbf277c92d6cd0b2ef42b57df71177c3aed9c41bfba58cfd515a0135643b0f5858d5778efcea57fe40b1710ab447ed737c1a062fb9ac468ec35f7b348ed4ceddc953a50d4d031165f8ba043ab2db8144695119eaccc4d1d546b278f7a1a7a0c05144a783cfba0d6eef63d33eb2460a368d1e2c13a5b137f8ef4abaebf80564e71ff69beea19b289c2e791baf6ec3c615c46d6f84d8aaa36cd7897ae629f5051f35bb46875c049785378f3eaeace7cf87ad4849cb472735ebf8ce9360f48f715c4f74eb61d4fd53b4422294cd562240c50fa1310cf3fafb5a8ed2124c7a6ffb6acdbfc043b8794e3c2b0b4f8d7fd4895d34f307e109da51b192cdf4b93c43ceb3b97f0509deab52ab8d31c58894a47473c43b17e1455b7ef55e47dc130104253b22425b8e12008462943e8faad5907c991139f1c58be4a8a832c10414af575b45dfdfda8b0ad8fe2976f661ece7133939b9330a229d85ed9b1417b2621cb7e1389921148c61c3279401f894081d15797d0f06cd494e344d1fa0f587e052eee99565df04a89b43a43c5f0e2cd3539f0cdcfd68c3f1d682a3e4e0ef4947c39cbb2a469ad396e4181afa5426398a866cc1471d9f1689099dedc16499260558ba2fff82d5a67672ee642f6424ec15123a3306cbd16057f77b3eebd58d0320f8c3830cfd9d42791f7d298eaa4e6685f95c9a482627f980cb51c810649e6f32ec6d63a963b4b4a48ed37dfe5dc17f1b249bf87b6d38c1ca112f7520f21a525d93fa0c96c8f295cd974cb5167a03020fdc8f3c54389b9b2f1e00684db009122fe8d6426894ea7915a7950db91a1df09839feef4b02742f2569646ee8bf2913bb973de9c03c61e61db550f63f3c06964d0077963b9eaf107612eb554efb904cc7cb9fc6a83f9ef7a0c9ddc79e0c39c6f7f90c5d5d2a55ea506fdfc346e0401915ad0abaf8d5b818c058a54d324b03293f354ef83223ece678b3fc44735b5e679d8b15fe162872c2618634dd1a2071ba7dae8d7adec6930e3f31d0473633090f27ee07094d095222f0ca424116ba23bd13ba9b1b146c3b7d4c70812bb3fab93718944334b08cea55528ef6c9db5e6d8b520db63fbb5d088af9caa6bfa5a40239f87813c73e5110a0e0b7255fffa0ca79d589d216e09938a1bc083b19bd54aabd50d0c9e3dbd8b6a98611df57791b0901a173ae3ef4d206634a06cc181e0ffbf6ea756d1e97f7bb50a4991efa242bb5483ea6837954b22c4a981547c856833f4d4f1b2c6c69ecd2620d19d57bf1d10c052455de4a43a347c86726bffc1f98e8f15a50aa9484f0e3774b4bbfc4f3ec4540f8b81f9233112075b94599ed9a32a6a2dec9e6ab3f9c14cce2bdcf2552da0e8735d79642715488f7c6d846e1db4b23bb69fd1afe360e76da04c67d82383d2f9671c69568985eaf53637ce25e55560131a33647376e391efe71f9f58bd5b51e49039f66116e002799e507b4ded484edaa237f7fba40bba08dccd486193ee6bf3786d5ec223b0fd9562b8c7a415d6800a5ac2e5051d094ce1dd953524007284ba46bee751c447c441644abcbd79e737a501e5bb28a18c827814810ea14753f246f68c28ccf9f0b8dc757fb39429d6cd015d9575b89bf954fc3e1fd6f3d240857d4cdc256da8879b217e7f632007bd9ce7bc433452845178029375b6731c51f04ab79b0b457a00b65fc8fb9d9f9d17988cdbd003c1263da4259ad46587d5a631ca45b477bb89cedba9b830ec1f44bb7ddf8c9f3b51fc85dc445106ed8e6d6f0ba36cbb4d0792a1d4d614c6e2b13d91e32de63194a807a62b81e6196aa863adcafcae34cb8fb59609cf01d8432344e92073a036dfab37b8e3aaf2a9c5ccabb9205eb4a3d3b3199e430129df0b869fae519e16fc1baf3ff1a72761ca8b5e4b91de84359bf516dd6fd4f29351dd8bebbbb775fb43a5fbaba8ba0a28fb34b340ac8a534279c68fcd29f7e423276f0e50e11faa21145429016cad518a2e3d43173087234e21dacba49ae692b1c95389ae2b6087bdc189c8b4d9b2ee524557cd3c97e129ba8261a2df46bea9761ceac6a4041306b31b01d00da518e930c07fd290ac30bcda9e4fde0bbe8234e118f7ee477ef2f1ff9438b67bf51cc113d93332ed0dced933e9f1ca3bd5cd111aab4f3635c7de5c6430930cfa2bf6b0b439ab539377fd20a232827e237a38bbd8e843e343256dd50191ce94286c155f16c0375fd86612fd9eb4ad41997a25a1029ac7dfb21b2ae46e13944e7695c8334477060431867c3a05d30d0eb024db7a458b7a664e73a1e57789436ceadfb1f28ff77b3bd23aa833cecb4ef1010ec4ba2cbfe0e555e76c4532b68e7a8d14ee35acf05acc2bd290609ed53d4a2c7f089806346860d0c09cb37da3bff7316ae46bf58a92282fa1eac75f26b1b9554580a69e93f2ea6fe6e8322072c0ded1086244915c199ec69a9a3da4d6fe780bf5d0ced52058ffbc566b4062b4651733c231d3c57d143d884a93d0291695b03a1c30c21c698e17e30ea5f01a1a9ab770a6ef1af86b5e6b1ef74ad7dccb172d8119a2693ab133683e8f074eaaae6ac7ffe49360fc0eb2713467093aa8adb7561345623bb626aad1a674f2cd726faf709daae110f4a4646d8ace493fae8b3596c19efbb3472a91a3015c678775e65f5f204c7ef4a9194b5c62a0f51687a041ab651b2fbd8b8138debd51f38d1245cbeff5d5396b769d12fd845fb1fcf5b93daea9a3264e956a3f4a3993f1e9019b28ce2182638100dc13ba80b494d038217336b9123d14920ba74c216df74eaf4f5822e76ba6eef88633ae6992a44557cd066484f7fd35efc7f40e78a1f5a90fbb3499249baf0804222550064b03b0f01f12940adf156998880a36fbae58887bd63793d0987db932fbac40ffd932da6433170bac633e771df74f6a1a620ee7769c0faa946ba7d0f8e2aeb2186442315c25c64a45f57772e38fff0de702a77b011ceec1d4231f4707ba53034777d1f0d397918e068cf121837ceb3384547c16fb5c7bb1ac7a8d290092acf79729e531cd3d5360a7ede221f946c2ede002a9416053bf7f55376e6c535ce314b1f4d160444a4a2e7b30e45d13c4ac7f84e985c0f129b1c84ceddfc6e26cbb40a0d6d71272b41a107b85ce82cec1a9e6d2b3510c114d93fa8caeec71661122d6bd6dd72a10ef8da00b3209f9609d000040a7cd70e36f6bf6d9e539e44af156670304e178cd749489aedf85727825316ac5b79fe4831858725a91621760ebd50bc9c3fc994d4ddc854c1fc872f69ec717e6d36f845b70dfc72146e4271eff93cbcbdc0c99d11065c75b39ef71d95e251396c08aa0a41dfae344e000091102d17d09a27bf3234ec69fc91eab60bee6d5a8ce03d681ff78498c0bbaff23e917b7a96494cca11db086eb587e5dfecf58ffa5b0b1cff47337fab339fc2d418f55409b5fe23a0f05e9996bf8b92a5a42b09f0d3d60495cd176a397126e4a4f355117917df5d073a56a95f9ae5488ee53844cda9bf4585d923d750b07d18313f4b0b5d1cf8d8033ec97045e26aaba0113a3b8b1311479b07145551c8e5a9f1cd91ca640045d92f7732df705d9e4d1fcc14bff9004638de837fd21933e3182dc2ce380672ff4c91cf62f76300992275cf23a007b5104a240307ee372d60de8aba9ebc882860d6d81bb9e55f0afb6abb8a6e09576e0fe4f12f5b8ef85bccf6739d36886eca5df257656e5d09a7bd3548fba478bb4f873def7f2e2c8bdf02b29ba2dff6d62821c28323b7213d6332911b5fc8aa7ea66c595a4ede35c70322f3e8378837041c127d65d169aa4bf9e33078b60c22405912d547c1ec2fd6fd890d50f19a7df480dc8a0cdf15bb1ad32a1306a85ced602e404a09da90890e4ff23f59e9d4746e71ad9b0dd28871ed0536539a58e2529124ddcd4423c48aae4d9164cffa6c12978d7f3f4fdf3fed0b819ced888481f208d596586e70072bb0221ceca7f303944845a6aa3f69e3ca56a46e47c3c7a9e6d963780acee076c083cf9f27becbe5ad97aab82524dcc60ef36b82ecfc8671a2a06977897a8278cc8bbdbf920cfc8baf3dabc39e9d6467beaaea339c1a3f9dbfc425aa05d1249ce54e6352ff690948cab0f7ef45797396657fc091e22a372edd8034d743568bfeade805e9715fb0496352995a505fa42f5e730d370b4f86faecefdb0811c011a32e2aa947ba7e407de26d51b8957e63afcc7c8b9c8ff7d87f7a8e6d9ec535a3a4e93245b82bc51fb96a2c11fdcef4448428b72e94fd56f544e1ad2022fedbcf6b2bbe2a20f6d95deecc3306913540eee7e5a1e3897237bd377ac87cc241cac1cd86af7de9c8fd3c0ffe27e628bf843500c61499152420c20e2fcf86b9422338c28dfacc70e4ab3442afc38bcb76f6677555fba0d36d3c0a01d4cf17ed9f5fffcb2d20506f3cf8e4a0f425a9c2927d0246f0add5a5a5d3240ed8e15f74a8e16238a36ce665346d86a3d7c11d657877b64904e1889de421f6616a767b0e9bce01ba43908a743d1818dc19c79251b7086f3a09502774addd5277e13854f8a2ab16a61674209bc484824978e892ac92c13d1f658ebeaca43f70219986f4c5c8c9d42d0e0965efa64108527ed9701f3212a4294d14320cef916d1a68ba7f630200032108c2ec440116e3d14f20204a7c410c649c55897a69b182ffc0d339d5cbe281754f0e2f41d8e49ab2ada91f16f2d8dd7fc1cc4462f3d69745241ca38753c4e0c4b4263fb92b14dcd6572e29175ec14e77408bba41df4896b64ec9e01ccf9109fd4e1060330000000000000001cb4d7100a9654134fe39bca10bd73b695d12bf24577d1708716baf18073d73dcb0011d7ad1ea06a184de45b60887a47d9461b1c6318d833760cac85b9dee07b532d6ffa005343332b9b4fd0b4357e6a57330e6c0b354fb9196a18db446897bcea91bb91b4f382ada0d512936c98dd8bfbaa6473125cdfff34a2c9c2dcf7a7d307b6570468d38c5d734b518047894a24f7b6d074ca7f037fb8f7a53ff518c180a308de9ed7f037d4a913a31b1262ce2e71cfb092872196cd91e192af6ca3df98aa9a0303b7bb9d9d05ff8e6a1974a51752fb224b809e1a8339ff67009e5a84d7259c1929c254cbc927fcd83bd75c5e15f27f743be1d38947c6f1d7ae4eb9b8ee65e0a81c37d83f865c924641f8ae07025bb7329e62e63c14eb4861a8c10b55b2cf262f7918d17f143d13fbf387f254126948f41014a5a9324cdd76eb85b7966184d51740afa98ca61bb0dbe5c47bb3908628d17d3049ab01bfbba81c458d7ddc0afbf8878196c44aef743edb493a8b88e60f1fc79b4fd506f42c1b63fa9f70c1c715b6f8d7e5ff73aefe71f3e938282167702bb096136b26a3383e0e6c324b682f9443e0a2c2f9e717ae3b068522e9da1cddf4494215a9b2036006a48041903dc018ef7c8ecfcfe2cfcaadb6fc64ceec312395d1b20eef30328ba08e05da50ab9aadcd820f0d20308703613bdd48a72cfc200e02d524aadf4e3e84030dc37335f27a558f191d5f7d4fbc364bddc2907d66b22503c493541d52e600b14d87c98a2c5a4cc79c94b928b908bfb76a9a59b1252d83078f621278cdbb9d19b708d32231cc5e5c11496278e59c564fd951c7c8431599fac667c64abb7972fa27d22710eb7acc9d84ee10ebacab027044309ea52aa119155f0eae702c5499a3b6b375a3d3e7af8251b3bace39c7cb0a9d8369d43d1a7ffa7c6dce76149ecbba1ad28462edd1d794eed5ba4a875be57e3031d47ecad179b6ce0498981c4b84b3e99be2ef04fc6aeb9049c9366a15136c1c7732f38f1cb4bc9f677130d2e3830ea90ad68740459d3298927b5c5ac98fd8ee02c24519cb36c25fee86871d4d447d790f607308214e4acdb2a0c0ee572b128e0f6a884038bce3990edb5aac63b8e4adb34ffd831b3b1c26c908b6be079f2f75062c8d3b17e2fab5834d7174b1f6de1fdcea6a4ba30d9c2b38784c7a9ea294e24970aa4a77946c2fd3052a2b5985c3b566df78a0d97f296b5cec7bb503d35139ad5309a5e447a181de4333718f1c0fcf510331384ec708629e6f9f5da6238cf56b5228373287adbcbb4193191c4652a05af3b2281075afd2d474f4cf6e4db723410ecd581bf08b46e66235cdce48e1b7ca26f93245547410179e93591bdc37ee3a18fd5aefc0bc0d87d05f6290b470d58394e524ce02095727838788b3d8ac8ae2a907fbf6359cb64105ee067271b43b16e67a1665a9fb1be10910f0a28a088fb3f2d3bfc006c7e366ae78b8dad115adb0b06fcb261fe3cbc6a03aaec0e7b678e5896283751b2c570b1cab223b82d781f97e6ea926d6e8a4208d7298841e01178588f385be59b23095a66f0dde5f2526fd1f8d4f2584cd6c1bda55f916ec30bf9367d50bde632193e3875e6f393364e474ebd49d52f0c26bdb94a7b020c8edf007394e594e995619682bd8b6cf71936f0502a9b2eba3121a721d8bc91b230a7c2f6a2ac62e8bfc6b5f7bacf67c7b0bd161aded05e671e6e12b71c5b6a81d5fcc85fd5cc4462e5d3a3d165e9b3858a345d99e66c952978b4b513937094bbcdce408c221980cb8685f83b6214a303eebb99e834985bbde3bf3fbfd3a3327a0888e237df77c79e4ca9287a0e7fcb53c8ccbb2a33b6fd67173cf0c6681cec86b3dbd5d8bab6d989eaeb7bdad18996be9d6eb96908cf60aa5db4f4de062ece192f11d1fce85bae1d00eebb3c52f922a1a8508aef015692614a8a13d80ede3ed8e53926f885948e04a6a68f4779e869533e0ad888af19647ef9422c4d21cd32dc97d6cfa91743638c9b7ca9e46fb687a29fea8d64707b2f5287f9396b6b6b35ee2e7dc38ad27d2cb50652646122faec2060c56742de92ffb39aaab3c860bf188e3e2097081b0bd3f475ebefbb70b889727577fc4df7487e9671885327db8d88eec571f1c2b6996c83ae371de0bd9c53b661356f648a0f6cd1a30da1323f4b546bcabe767537d6507fdf692533e0d4e125636f6dc58c76d206a518fd9be789f8f8a86979be782bd3b4723070e4cc3beffc2c0fda67f0f15f767a2d9dd4d393a35e06a18ed382a383946f91e036ed15c1c0ad24e189c82697cf244c03cbb9f7fcce08b5b93d0774eb971b962fcf8aa041953600dcf91fe174dd915c50986b8c35d61ec580d83d0cf6eb220dc14a467642ddd54e5c746581241a9f22139fbd042d786e4ef5dd6dd15c2e2d9bed3b3eed825c69f800d51ee4cf06c184f23f3583e9c2850688907e0e3c1dfc2fd37b37a594f680a90947dedfb2fe9af36f801bc129227e8cdae62bcfdd4714fbb0f7ee40a6f44891c3bbf712ff763060daaa0d5feb67eae999d1c54b57addad3b2ac31127be42fb0a8491a4aeb43b339b45bb53c759198f6d7c13b0e257bb243a17f1d6acbb019799a57d56e9c8072a4b6241915b66ebbc47603e820dc53ab0381f2be1d9a5dfc553f6dae02dcd9bb78fbebcebafff8ff0f7e865d0e07d2720c79add0f2f97477903869dc8961085db63a809207d5ae853ba28cf89a30bfc7d362bb466fed81ab3ead1d66c7cb7f820fd65a99cef4d8cb843633ba03972e070349da81ae6ef6baa5b28abb24c3356ddcf0e1999b8b427ae623168ef07006bc74411caa175a6a9f11272ba0e11c8ea516da92ef1dd4ed11e647099bd59cbad77727b93cde07f6a3ba088a10a25d4d063821ca1abe81c62a7c7db0c682b1a32a30b5a28b7f12659a391119684715049307fe6c3a1b72eed0adec73043e78c20e8c3b657e75fbe28d9ad53a374dfd330d05e9cc5342bdc5db8268a8d736aabe9830e7384af76866da7334cf2f5bb779d559cabd2e4ef291ce3a9a8f488bb824f144eb608cc127da254c83112344b26b587221d8d8fb0eb7da1d4328a27b28499e9f1ace6151e55756397f729fd6e9d9232e0a677ef7a6dcc6dfba74855f7fedf78dfe1c7e8e1ac44040f42603037cb2dda6521ab50f135e0191f84faf48d08a68cec3e6b51932ecfdc1abc49fc9252868288963ddcdf7cfe24a07cdd98e1bbddcdd2b6936711a884e7bd930c505b6fc7ae283b8fc12d253f7a8082b9c71729804b1a173e813795d4c05f328a712a9bcbb35ab8c53e1a7bd9e89b8720fbb6a907ba1d277385ec082eb53f239c7f917ea9f4a8c1bbb0a3154d363603fc4b8ff26526725b5da0e56c634d926474691fa3683943a6a1067f467f73cab25caba3bc2a874696a4f8add24ef00a9ae028567587bb608d9efafa4515bc7c0d3521c25091b32cdfb2a136ca65f4d3f5ef512a145156b8d6c2046848b25dc1b28d9cbe4b23d403c577478494ad578c18dd62a39a3e606cb77b85c9440d5ba578e09acf9a1999d1a0f13d59dd46ee70e27c8d60aa86306f5a525aa6a45222d3076a75c1af1aef9d204322f83cd1f708f8fc55eaec01c54714048a794d56e55fa1e20e9f48b67bdbb43888b43742bc66de752f034047b44cc4e13961acf3dc30f3a9709c94889a960853ed47bed2b721c4943f35c88706b90e0a7c905124aca26830433124e9cc317fb0a4754d259dc3d8fc7518a2325f3882aa97cc8cd7581004e32ae02b20af90c48bb02ba7d0e29166987bca0d84268849c49a508b22f0c3cdcb87f826674861e03ba2df157688cf31e57b2406f1973cdf5994d77ed48d2d9270866c72312e5168fa0c31f7b7725662faba64ac1a8d7b3a318620b6d2dbb9cc22061bb121cb06576a1214894acb4db730380bf479ee3d8ec16f82b629acfaff124281f47bfe3487921ba73f6915b093c572c68739edb21c73e38a406c6669fa71f178c9755f0c8d0c6ba5be98eef2f7e27ba2100724e9c9f3079b79edc6ab4fb4f78631e26683ffc62ca8b846cd40016f35a808b23596f54aaf9d72c6638c87a405d98139cf7168ae7366bd2f440f00d9cbf5fddcab3a7029d386604de517f0c35b7156afd0e77d4851725699873399d5e2f535b0129e71ff8c41ba03ac5abf9759ee41a240231f978aaeb980ddf1c809b5bc2d2effb4b932a7794b26540a7770d7e170b1b0b81e10114a72db8713ee6c370af4c6ddd1d76c10e5b4cbd79fdc818a2e32f286c47efe55b540f59b5a555c43fbf673df3101b22e58b2e3c0f2fb83a586a5366d04e1739dc646e9efd0dd41b4391f2c6a45ba0cefe420b070e566555d7de61517f660cf95c1f2ebdfd71d9a2ac5ea84addf9c0f2d78e1876dfe80efc3b0bc2ee217249ccf82c3a06def9a3bb210d071bfcd604e223524fa986aa9aed206c4cdf037c7835cb27679ea43be4d8a07e08817358f9eea823812f56fd2a17b2455665fd55f09f8b0b0208eb6f38688a85ae7a3636cc2710259e2a6c19d23108bc81877e16829f26f25e878854f5b7bac0e2ca355ec50affa07b3330e29f93f134d2cdf291f9155797275ebb4ba96f68232f49f29b67c597e1f281a2321a21ae1ddb9961ecdca80711efa8c188e4819309c2d36ad1aed8a2f0abb17409145323328ecf482dfe60d6d6a3eb727d4194115e4cefb888a56c7711ff3b60ea969eddd7e96995646998bf25382557a2105543b4db2ffd5937b988a953a6ca167188688c50edaf44924b4b7fdd48391d537800c887d4944f2181c50434de8f3f87fc14f4b58fd1e4b990fd139f2243dc4046b9bae7729b0290e00a0e6623c2facddef9c88d0924eae647e48056857c45eb351d37f2ccd7f0c0c6b74db67f19a3da4d6569a45541c4954f99c119be3cf4f9d4a0f84ca549cd91220fa62f90debaac56c39fbb8ed5a53f428e6091a4bde41a430df18a4ce86bcef5537467e867562b53298938a7ff21f51d69c775aad8cd5a556e727ad330a08a7b3fb198cae003cf80740f107006311cab2adefae4b38ad0dd744b9e3b7cf3c9dd47a9f1545a62d664c987a4213b8be4eb33f4ce984b6b7ad8038b7c54bccbb031a82abf1192e26f507c3bd879ee42e1e74a560740ee173e23584e370e151f53f1ed32259549d70643fd1d24230904f2dd5cadb53ffdc768e4be9ee2cc528d04c4f8603d4a625fe267a36be37e319a374e35f1d20e6477eeaa03bc58cc9a6948b82678e0c3555b6589f72db2923cc5249fa1515bc8a56122529e8eae7543b3a01c4f180df51fff9c1e1b5465fdf5ecfae61c97dab19f706ee009c0dfb4aebefb15288f8320183ce0f3d0847d424279fcc9aadcd7d1ddffa12ac78e80e2bd345eb6624c8bdf82a5544bbaad8c500233f66bb9d781a6edb900dc373880db5e929ffdba840a024de032e02626402fb76e14e4681c39da2be9536b9499ab44163cf059ce6d73c40339dee4a4e79d0436c1167976287d4faa36b064d84bc4052091296a0405fc2535440d97428b7e5a5bb348e3e4ea4f13a6d7daf728942ef5a64104fd8ecdebcf60e8c76f39808cb18afb80b1d325b3ad2d1a1dec701a9a5cb8ace1268fd15b1b88d7dac5b89c93f28a3673ac4495f3c3965341d07e1138b848cd220c66a2ca44cc6d22e8038270f422045dc4265d605c3de060dd81d61b2bf95b9d9c9891ec9669e5f18dac332bbbeacc9a87c75f00aa02c4351356f8a2aa7ed2d869b52a7fa5e4bacdc3058a92ecb0f4db06f383e09f99aa541f4dcea30f830d6b721ca59f61a6f6989bbaca92f49c836dac0aed12d1b4f09a83ce40cb4af4f456d6becaefe5f8218379470e98e74520ae9900240a37c55d38f60e79e12b2d0000" - }, - { - "name": "3 tx test", - "numOfTx": "3", - "hex": "000000a03db40ab09a2c6840e2ecce95d8f23730eeb491699de4c306f1d57f236fc84632ec9a233ab87d37ce54f03270328678a7abdac37bec16e909654384ff6d4845638d045e607e000000012200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc332604a000000fbee9cea00d8efdc49cfbec328537e0d7032194de6ebf3cf42e5c05bb89a08b100010151030200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04017e0101ffffffff020125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a0100000000000016a80017a914a6aa6ef00bfd6f6714c8969601bcf491add40da9870125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000000000266a24aa21a9ed6294309b9d39c458965ca69bcd5a36b478b8ddf2b3a0ca0e8217ee0f1e0e4ac8000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000020000000101ab6d4aba721be5e32a5124c9351b4396d1984d4a69f04c874ecc20226a84e46b00000080171600144f731ca75248f7443951374c8ccbe943e98a6620fdffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000012767649f8000002011cc98074a33e14ccaba093c11b4b80d51bd64719d7cb20936f602493b75b806101000012767649f800001600143e77536b6a977c023d51327ff4774309655a08130125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000028a00007d00000000000247304402203c5a6dab11deca9035dfb850da4e02bea71e09bf0353289823225ddad2dfcf52022015964324c4c9e04ce87455a85e0d9c9052c15f3d0fae5a209af2158f4779e231012103f123b7ee6f68351b0209799456f6e98b66912b7a78ed9f11b54f8a9620e441630000000000020000000102435d740d05da8077191107c03adfea535c95b3d6f9723813e96ba519aee68c920100000000feffffff1627283d8c3b5d07a9af75388dcce0f2069c8b6fd74a5955424ae69f7cfd25940000000000feffffff030a1d2f54b647653eb0171cc07c7048a8d5c3966268fd8d2b097a5f6d757bf2c03d09a8f858bc15772945ed461f9e3930fcb7e23c4e966b43e469453dc55c832b3ec6022550a7ebdd03baa1f9eb331d2b0a51e00af9e1740cec5c96cf14c048e8ff6b911600142e64cc46b950ceb16b940bdb7f5723e1e85505d70aa8ee99c6d5203ca61ced2d716e9a760675ed98c933c45be289f7c0c820d381fd0835e91b30a0136c7ad587198ca7c6409f3036231588f017f5f87118b79cb615e1032591e84a2ff4bac678a0997a12df347da2cddb6a2cb801b4b2855c254593e6ab1600142302ad660434a6a1699d31097c461f6b99a3fce70125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000141e00007d00000000000247304402200d88aadce535fdaf67ce9e81981ef6bf0fdaabefcf250123e62d2ab1583071c102205f69685fa5f225a57e2f393c09f35c4b6df005111b29c223d4dba813686dcbae0121023a98b032df5ff3555354d6b361aa10185565d147370e37c8e20fdafe64cc15020000000247304402207d1ad20ded877504de9c4018c9a8322c92b080c65f24bc4921c394e35cb107cc0220651a6ad9b44cc2923f0bfaf08a5829648c3fd080f904eeed27af1c33cf1b6f86012103c8bcae0c89b6e9c332eca0154e9abf8e59f6f190192b627598689d9edaae94a900630200030d298a450dfa839aa83a4499ee4f61b3eed235c168115af97eb4270822b5bb36ce2b1da5cbf2aaea98704886ae9c53e6afb454da3fa99f99b2dbfc9b01763649508e9258280937a10bfb4c4777ebbbff4d54c1ec2cfd87d98104b72268df840efd4e10603300000000000000011f46a000b8558a1c0b4c5dc89306856ec01f9466ba0f97ec857ff809946118f36cf444c860560d8a2c93b33e2cc3f254861b3c01de248991e8a1bad77f6c851322a834df4146126ca12f4311d54dc6749ccc5a67f8c87bde4fcebe9e04667c9e446a8d4258210c81267d5ce1bdac50b147c6288b1d5dced4ce5c218bd4f408d4c2dbd9462de518cca997238be3c74871ad2a9bba5a48c54842ac28fbf0cce611b57d5e7ee1356c2fd2caaa7c2112cd0c1a291059023625c4990e78bd254aa9e158c8da23860ee7a82c8c665e86f9f55fb68426a8cc2247ffe4d7a13f5969549198975e1e130a174f225a0c56504f4818c3f2980ad4e81f6e90b7ff4544408c88b471395bc937079854f4f14735100412607b5e73558ba8004a4e02f71af2a537741c4c1617946d75b2594cbe66761068630d03d19ae62d7c86017423670ab63699c57bdab695f80a93e49c8a755ac08c361c614ad179e6647ee026bc72210d2d154e1dea98194c8804c9cf0226f12792db91cc316db74edeb65adbf33069be66ac16d1c34e270144fd982a8cd030e8aea7694f63a1f69c196be4f6c2bee28085072bd2c150b211739fcf947d72fff445cbdc762ef174d94a9b2be8e433096ccc0ce795f4eb230b88b31f64e0b210a0c55aea504b8b25245ef438aaab23ce915e1fc9ecaa17ffcce6da47fbc1270dba407a9a917f1085d70af32b38e73d1b99e79297acec21f7321475bc50286a2a6e76cb357f375b874552267a606345d2a00692c6d8a365cf80836d8fc7f440bafb839d053d6216aa14bce7d8e9391e761d59dbc3632037b77156484cc1a71f5d1869c1742cfd6d5ef4b755f067d1b201775ebc0714893e922547e506c0691797254c6d56a1fc0065ee27a44c7c6cd12508b7181f281f28ba76f6b4b87f3a2b15bef3e5d37afaa3addbbeff84157365abf7ed45c968eb804bc05fb49c4e338e3970267ec8744217f8952275057ff655ef4142226baac1516d75d4b11eb25f61e9ed89e7a12cbc2ba481666f5f8794a48d1ab63fef3ccb7fe179dd53a87977e15c43ba138608c0812cf6dbd7b67b2794c8ecc8c18278afb930234d0e362d926c15eab8a74a3a59e3f4c443130013c116643cdae821c10984a832153ab49802a9fcb54b6ffe46a9e8da7fa668fcbd91d169aa85df684095f1b5e8bbaddde4b354e4c6d65ae0b9770fdc79f8b3258a8650c4fc2a2d512dbaad0a588ef7ae4ca30b7ec6639680303919a28a7443d83d93d706ea7b349d85f9d07f54a1446cb0dabf9cd2c6bd0b32342a9dfb1a54f4e53e1af0577922f95ba3b7702223b1e02586c98d118c11c9f0c2ee194a36c3bb05421f409f5c3b02b0189b66f556cef5afe2663f863923a50f0d614cb044ffe533b3680f6bcc89bbcea6a97f10b4a201d1651dd8d50131f40885553a22b768ba342777bd9dfe373752c22c90cfd5a34edd40501e5687cf42d72517da4263984e5b4beedd2de48a12b804fe144796fe52304f55165e76fe6e4d9d1167f2cff488418295513e3b79e3294dda1108931239fb7969e5cd1c01c9b73aec18d4cdfee83cb76852730869687c80fb97f6cbfc62a292e94f4516efb472774acd55ae64e523b571d33eff0f37838c910ef04da3c546874400fa65155b4e98f2573f2ab3c70ddfd30ebbb2222aeb8095727dbea048d80d1b8cc10342be96025aa3ac20499bc9601ada20065128fb71617e85a51e337dbfae322e44e990defc01890317854053a1e9618000c4c0bbc6174fbc24effaa17c251330be9532ddbbf240d72c8f2f94df12bfc48d79421e322acc136c20a30ca4809e96ec75572d64330641684c99492ac1806baada87cdf8e8ac78ae1e58843ac0874b9c3b9c324d7accfba6a1490a2aff6c8cfeee6db9086035ac9404c82623c3c1191fe59bc27b8d68dad6a584e6ca82ed349f2ac9e9857c7116b028ee20dd674fe8511d22a8044abb44bde86403000f5fc8de66c957814daf6a651134eed3c3032eb9990aa4757135b85d5448ace020a621e8d6074afd28a856dbcc2d6a0b86123213644314fb7c0d1e4eaddbc25f63b7b507855df845791cba82405031cbaa81ac0a5d8b39e6f53c7e5eb883985e4b84218ffa363e3e36911cefbfd7a90c56ffa080fff7cc572a1875cc525f173f4f1c305926f150cb49e087e5ad3ab70f29dd847b7eb77c48a3214fb61b4d1266093ad3b78977759d9665e5624d161eda765a3e6f148876bb155e0ff8bc0cc448945c588d6a324d3304060ad7875fd1f4222350d528dd21831d137316bffe308b7852b46eb55e97ea52045476fc3ac43649a0731f9615ef51e97ed129849ef314182e57b10d20f82085d64363d837f28b9faaa011cbbfa346b52fd0967b32cf7e6702046f10c4003426e74f05bd1e03fc27ee596c645c073c7e51678966af73a0b18d541bef8226133b174a8e3c5963d69ab078cda14a0b44fd30f4995b7d66b551f7f772f0b9b9536f6f68905b1116797d57f4598dad8e58e0034e13afe7c3eab71a16d19ea98497b94748a4949dbc954dd7f89add0952c3073b19e4d9a52be723bb5b2a5cd50cec9c6cec7d1e9b46708d0b7f48f6a51d49324ff29b0940e970371f25608f37ed30641515f6c820075e7b1eea7e3fa548a0195899e14e001314cab985ae209d0b2d67d6afe45147b3642e16644972ae8cd004ac5227db3d40d75d16d7800b44ddbf19d2a15e98ddae30cb0ad4db009ac295d3c65b7fa31d296d226cad46dfcd12ba0581c1500aed0cb9d14848eff962037a013ad7149a8828c1925e7f0bcee4157099f252a957b4e45f736c461c10380b793d42e7d22efc783226aac4633c2a542c434b762040daa4a508c015f59a8d143742417392278a41804a992f00bacab5d598220b4edab565147891d7ff7a23cd1affd950433df0116a784913ec1619b498a2ca7150d8f00d5cccbe22396baf5e60caa8b93cab70ed34ed91c5cf3766694c723a90cc3a726d56f0e3cc0dd7a6c7787d35614fa4f9a23d0bb0155b5f23a723bfdecd0e1a512441b20637d4a7d856f2f553977f1ecd57045b2b95053993e1fc297d14145046e37c2ed74d4328c5705b7e671b7913e4bc9a9f5198e9f7529acc2f23bf9fb60c17cbd33fd10f44835a397ad25cf85de0626a6b1188775e1b61cb6f16cf7b9763b3e49410f2859436294ebfd19a28d2fdc06906f816c4ae05a2c7b70d2a9a9aedacd3b0c4b1498838364d858f1f299429036eeea1d66d9ccb5f3d5e9f0d6222e1456fe58a35acc4a286158425ab1baf5c097104ec8b732ee74a1fe63daa56861197ce0f8d4a5f248e9147558318d15d5a0c28ca26e62df17ead61fc4549413469278e79d500dd8e7c758e905eb3f03c7363b4e8894583e4b971623c8f5def9b9c38b3bbe57012b057a81bcc1f005b3339ba6cd6066231d502272cf54b8d6c64470d796668ada6f675e993260ab9b7bd99ec58d7b480459209dc0301531fef53846b7100887ba1f4ee04e4ab1dc8c1aa9656d04d14f78189ae3fdee92659c48915c0bfd5506830ab0decd8f747d6c06f359d78e9c183bdae20f2f76820d376ee6bd4fafc8bdb1d8aad87fe19df64d9faa92fcce6ddcd1267ca69515abe66c15848264132775c67d8d589a9e4c6da3746f257b45186b2991b62c16a8ea9563f6e49b58399953e643d9e9e35781733011a8f64989c6aac37013cfcdbc0ee932bcfdd96e37f2649fa378c7ace04996d73648ae4675c29fff84220dce78a9e943e14256bd73de395d85f3c9fa5eca9576c5c11716e764c4560180099d4b9210ade37ddcfe8db433b884948e4da8f27ae3ab42d3c41feec2e92dfba3610936b81c5cc5098363a03cf8dc2fd41861e0d5e058055c701d27219d019e39a82d0258cc2f011cd697f61abd168d346e91ac7d937d43a8a3f3765f2cbde3491bc444893da4a2a1a454dcc75f8a483462bac0b7399edb5507eb251656e46541657115cd326619e84e567fea6092015055d38ca7ec6707f61f52e132217b4119378b4f3c955fc6e7fe89287980ca49107ce972ad8dda987e066c9871d3e326e9d6feb51ae856d4f3cff85159168c9603bbd17caceeb385c4dcb46f44cc419b456ab694707913cf1f34dca0a2e0c6404ff9cd38a0b15e9ef0977e09eca1d035bf87d815d12c86d568a0b615cb4702dc146e30b0f5c45412cf8ec286029bf4ab70f62f34b7f8e9e67d171ee623d0106ae292caa12beb95d8cfb26c47b6a1387f3cfb8319aa34d7e2e88d275cb704db1f7089b019c99c93758b8c0c5ae30c9fc1bafab6b0c66f1740e7e17b522b3cb840b67fe3f8f08346736c9dcb15c32a7c0b27b107d669b93af00ccec64bbbd320933e3a65e1b9df0353aeddb87cbc2636d8b2f06b2ffea94eca4b587e26d783815ced73b48fbe92d6cbf56ce3428c09b4d6809cc9090e5ec6c2fe806e7521ae5e3440c09810d0749d13f46a0f52963c32b7165b785ff71de99302083e6eba62f5f5d308c3d614e1ce78096011903a310a8af37cbfed48c2d17b7840ecb89525e46ce21b74f7ea07456a1acdf5870ca484f046c81a584018da0f1bb7687f446b1547e2ca714be2f7074f448e5c12d53dfc388e18441fdfdca5fcf5c50aea67ed37caecb137ec14351c272a6b06c3c0687b1ecf912e74532e2393b8d5288263d7f864be271823bd286c7648aea29f77e33a94a6f86a6bd0fca63d1a3fbd39a2c22040c19105b2c38cbd19819fef64ab4b2b7c26dcad4850fbb1ff37f437a819ba5ed135553259f1c0ec1753bc78b0d4a74a412373d86eb1b59c541f3b18d24f3260cd379dfbf8ed389370b517a292a34e257e5e6a833ed472066fb69b5cad6189002b33f15643697891a09205b7b1da30b43378df867afbc734ab9e2257bd9bb692dd16b836e8dedf9b67e5578a8769debf2b4fc2e0015ef6aaac9af3a81a419b4cddc4a3968254e1b326adfd83697c49f69621f034fadddf123a9666295de20bb0c7f3382609d4984b017d2a06e76abc4ef934edb1d2ab595cd042715b3868f19113bad333e908f1ebd1b3b468818389e137749c1435c7a990304ea2b80aeb01d75313114d36e5ca94a0e26a955a49c8a6ee2a4968f8ee09874859c6182cb7e1971112d8284b94c370a7d6e682e92dada0a74d2b825312d7de1ccc8ba3bd5ec0939e7750c748d1fbf4f0c3d4452d5ba660c92742ae82845a8f45676e0c33a4bfc6df4bf04ec6dfd443372520d5d82adeb6cb4ad697572b30876cd87482d34e1dfeb0cae1ca21706ef12335dc129aa0e512698555801fbbe01a8e5d11bc7ed08414cbfb826f62cc14e9c65b2ecf1e882b094211e67181d62e3a823e13463249c4e171eb90de10eef05c1741d31d91a7dfb2297c95579a7b4ef13d217d7d285b97869b40c5f2f62df1e800f3b0e7fe45a896ba617c0083e8d55e6d1c61bb555c24678c4dd6c6683a24d652a399bf0621f05bfa62ee2c57d4b86c6c4541621345c358c33502b2367734a7a26ca27d1111039b4e38b35ec24ba1a13ce3efad7f89676e56423bd4035cd568b9427574c189afd3788918a9f0a7cf9b28ad17eac350e8c430718612b5b8237f7a2f8b4528dff99e01679bf6c868b9ea9ba7e66628ada0b1c29b179e541493de0952fc8ffbfdc819a884373d504959654a935533f11d8ea58fc8bae4cc7d89a4da456ea8b9b8438acae41a98b1a912505870414808f7ace1c5e0336e0946ae42d8cd665f94948f9e56062ccdc16d42c30be28c540fdf2a2ba7a447ede427122fee78ccb94c33c3477aa6a064d900a1077cb8cb6d65f72f48d6c0019a5d1dfd32a8e7145e6efd4bf4aebd798f1c2869dffdae91c23800abe9c399077c70fc68ad70844037f10a2cf36cf444363020003562f6ed6a76bc80fab62621e494e15867e4910a934e8564194321f9134356b02f58fa5e6e24642c7348116a8998972dd79faca3f79dc1df5accb64f295a742a757fe37063124c7b17a0645707d2ee1e214c228af2d9053c116cb443ba3ae8236fd4e1060330000000000000001b50fb201669d2446d2945cf115c3a27f0be33843907699e8409db5896c0f0fe89b8f4635e003dbaa0d59c240629cac1cbe764ba860f63daf87cf849ae66f3fb9bbe9c99b6ea8f5ea15bdac91077927f8723cfaa4b61a8be17a9bbb8af98101324d51261c0b538088b09926a58c1109c9d075f59d66ddaacdf139954757ebba40f66aef92da361e1001df9ee6cc3698b8d85519ea41fecb777097ec7639397e354a559f717d5d1d82264f26a1c8b1543b2eab074ca250f8f95ccb1b2f0951b3c653ac5611b76c21f6996936745e08513fff7fcfd96e1701ce42c2eee1ad00fbe42163cafa5743eeef6028b541bc0ebd202fddc5fc56344f6acf321f759d192177ee7a8b51f2782230e1d348fb88a8d3e6cc541be26f8264bc6dd097a184bfb53cd8bef59740f84e92d9a8a1ae76ea3e2df69823e16bcb7e878da607b4c9b60a99ab80f86b009000dd0cfaadc92f6739df63110c43f4aa0b5ed4be5bdff9cb1cdba2f5a8f82bff133b6673d7b2eb305078c648ec45a509074174626908e7092eff2281b1f48d53a3f82e9bbfd8a69ad0e969bbeb31f02274bea0be04604fc192cb93bac7b7ee8753c3429f4ea531377bb7340dc7d5d541cb6d39604a2725e150a131e5753a3e9263b6fa148aa3268824c4d55c450d406a3a94735702904f90732758b7a091da43d952b7d3c78978b9f1a6c2918e700da47462fc46df2e6546d744230cd8849dfd15dc2b7a89fedb5ce92df7c164b503e7ac9cccc0fc9969fe4f3344fc56111f35b08e4c6e075fecb631aa8f7a0129cb3f3e8fd29903287567d96ec4bef9fb84065e4f813bfad829a7242ec7efec4854c52b0b279a8040261e3b888ed400ecfbe038b4bb311612772b37bcd101a6fb4a8fe99dc64948101d584a22cb023dd16eecf2d6e6fc757ce44284df762fe8bf9a199024b39eb17dbde3e6399a483aed278b3705f4c4dee44083c0edc3794166c987abfdefc2692f61a5085b63cca88caf4fffb4046a340aa2e651cefb6c667cf4109bac4eaf7668d29bec2af835a32a7a7d5691505083d79aa7a5d39a09454edcfce3f49ebb2a6fb6fba398cf5838197d1f8d5ca7cb9138e24b03663ab6248e2a970304c931d578e632fe259fc67506c5de7fda0f2eb98311c64bb37f4e009215d2994e4b350fcaf2530b50e62651f485242f46cabe6156222a7c3313fb7492344824fc4dd7c8a8765f8cfd763fb6f207d0bc3b24df339fdd16980ae5e2f5c8a24cf9f1dc8c8b9ef6f2a6c6a3ff1f54130be7fe86d6d04ffe981d74f8b746f27fb573b495631329904a4d00da18abe6d4afdd66073f1bbdd033b842604d6b64f8753009db366b56b0b456b0af84339eb7f5d310c72991d4d1d3068d24e72a6388929ce807ad292da52bdca28cdaa32d756fb8ab535f06b9a4754ab92b393885a53187d538352a995eba6179cd4b5783765ba71063f6229cafb0b5b02cf57fcbb361021ba7f14e1e6f9fd93ab6e8d9c3ac3422b4628e647a37b9d47d63529e1d51d6d0a39153c3ded64c22b43dc943d37cd6c032bb43c4fa2f7c5768efa2b02d01b7f0252acc2d7454d43047f0202d37f429ae69194937c61496050b51714d1d5e8eb68b3f5bb20001ecdb18538c43b60d85de51eb05d5a419e39e1dc44db29c1c5937d44fe650dd30d53f5d3176037dfa5411c7c57786bd4af0cf3eb5d910a7c29a760937f7e6f4ffc123aa62728ac88aeca4cac26af763386f80ec6c78455e27a4ddf3176802d9390eaea4daaba0688cbbed0cbe834ef75ffe4ae703574ccc03d6a1bbb1c42b9e3082a933062cf4e5be68e5dc6571d81ddde656c1cd3e2e184ae90abda89f1e2a42ac0a4cfa41486bedf984044bbff3d2d3054a0c2c4eaaf70553756dfaf3704fed2bd46d6f81669ce4b9dd4b55e1f01c24505ec6020b022d0fab880ecb3809b4e6604c4b9bbdf3558a03fbd399761b8f14b8620b30ea7d0c71a25fe6019016c6a7523e9c99d66e425bdaf6c2270b24c428373c2723faadb891d64abe0a447369ad3f7e441bfca5c2a8e8f858a6bc6d284e71a7a657e9e7f40af96d780fb9c5263a30eb1cfe079042b3bce8b5b1019df58ed06e3aff02598d63d322db71263e2c4c0b58cd21969506b1f64459d0e8f3f41b0b9dd26b4d969a18e06eb9ad5f560f9944c25dcab63a8df4194865aafa4c9df84f68e9215f239b3fbb648cff09ddc1f860f2cba4fa2691cad71720a82ebf86c5cdf803dcec102e4bd0e520f69b8259181ddb8658859491cc6d28bd5ff8ba52a7cf384dc4a39f105e86db0da4c7c7a107e19b60f138a7b52de1a240df41b1ba3f56037f6b7c6bc7cdec387cc605ebcf01b1f2bdea84d2824afa8f74ded6c89d921b5b64b314c8bd382d3d9a29bb7c3bd31975eff68094acffe0dd13e375ea33086577da24f979d88ca046c950ab41f81666b86303a1a6e6a116c527ea11adec262eb91069206f7253e7bf45c553876dc6a196b8465e3db12c422bef82d9b9b7e6f075ad3695442c0733a0c7a35479c979750221a9f6647334f73698791fac2b73193ad5232be11203c5337fdc4a43152097e5a8ac4410ac2626747c280b3872a4b504950528cb079c71e928b760c807735754c2668ead7b9f8556fc07a390c3b56892ede6c95b75f17dc1d1b126fbe59e0147ef10ec0bb7c02012ddfd76a4fedec3bf29c6cfc914349fb25f2f42dbf1dd3587fac7dc6bcd53bcb72c8334096dcab33a5fe3db5332fb96cf760c7b6f1fa533e54d4eff05c6552d7e4065ff185047c841d07dbb7e18e256ebba4925879a825f5c750ae936c10ee298020e6eefe27369138d7fb2d743c8ac6688196e07c1406ab160c63956dbc06652b5e43d52c44e636ac11bf816019ef57492d364ab4a95cc60ca4c8905299faa5217dbeaf28d6626e6dd255e52d3a1ea45e60bc17fea3a109be33a281800f8f52341a94773954a723d305e0e4ea018ca410f89658ab1398ac5483eaa8ec7c8e33673473525891aa19e3a580c3c4b01a4b240344e8fa0b725bc1587e9ece58bf3c785eec39fd21e91087f41b691f1b53e9a334593d7698e2b41170c86a5f52580163919d5e82271427e289120a5d0cc56768c55e10ab48124fdea472070b76aa1edcc9dade90ea9876b15aeb39e03663b554e74808a137c5dca72e6939ee7414f3f5777ad1e75f61dde0014068ebf147891c789173d62902b051c840b307bd1ed876f104da5cac77e494d4fe45125027960e056b54b1373b8691993ecaf9357bf8b0c13d491bbe2d42b6f38b70320b84596f32cb2310baedad1c24a4334be143bb82ca8343044ea8dfe91056e617f16a17768916597c850774da68d315a1d2ffffcc6414ed4f8308c49926c9458bd10f7b5c84a898c1e2a6db23a7d19e6515d704577d3740f5fec65089a1375a2318dee12dbed836494baa7f12ae4d728b5e5117359d1cbb0d3d4938758e65967bc32c28a3dc1e9238319e646dadc22dde3e0c5f170f56d9d3cdf93d9d9fe95db4fab4a2409015e54c75ca268b450f24c8373d918640f59740588e114fa90af4b7549f90a79fec48ef9a43812b286b8ff60fd8e2029351539227ea9baa4419fcd6e541be3be0fb2166d6dc8f1452966c508ba8a9676fdbdae423dde5b372a0c1172c26d5cd26c6b4ee981b751b1f1e9e45f77cffda31170ae5e8849a7167b993d68b12a2ea25bb2366b47d8bb38b9fab31c8579ca7b7cf581fdb215b6a16579495ae86931eab248737aee7095dfd73581e96168470f6be2c728135b38db61f1ac0a949e803aacb1c97215984ea57f4837d8a905400b295673a0d96f4ace36028beeececa42f2b8c7e350be60e821b7c47425947c53632af9a4e7a36548cd6e45f43f5a0a122565a33532d4a533816974037ca882ae1aafcf840d311057d100d7f95a87c0701aaaf8777746b26ca7f3e55fb939747052ab3b2e9eb44d53b0a37b5d50930dae1bc6064712ec006a87d2ff396b3bdc85bb06d5b0250686c7e61c6ea524a766d45377f9479368ab266b32707b4f8d987b004dd847ae118f139fe8d35d12336fb6fc00ffe98fff8c21b11bfea8fc847d7f7c2f2b2991203ce377a9aad8dc7c4b459e4c93df3362206c28ffc26e285d8bbaf553e2cb0c2956ed4a9712a85a055dea18c8c6eddd1044d6bb7b53b4688bf29da0144af70da1ea15d8dee148d9373a7b3beca6572db03ab3be244d0e24a21bde7808dd930054f63695c0310ec66e4e2a6a09e63f1873f9b770a548bff8603e38d6cf498749a8546f224cd04cab7307585ef56a7e0202b41a44336bff9db610e37bdfdcf60ccc737996537174a89a54e38a2e22ddfad0227c65b84daade4cd4ff13e82d8308bd97aa1f7fa766a8476a2cf8dc7641c7288ecc4979eed04bda686985f7fc70768e129e5dbeef008a21a883dacd838ced6fc55289c913db9c3d60f5a762c1cf304b388870e8048c5a6592c37ce8014b7fc9089b193773450f27f0f33ec05ea85e355f0d84c1c4ad734c1b08adbe942ac93319beb4e6cd28db263e83e5b81dc75fc867c2e89d5d0c79287c77a6b07e781ae6d804551c39590f7684c5d7a30d2e126e5d57684ae8a78b600dd481092978767870a189cb5beebd1236047ab5646e46949c24a8608dea822cfd47d8943a4cebcbe6256ba063a14f6369cdb75af0eac7394239775fde0fb0f8216a8b7fe7ef5fb66259ee6d5fe68c2008f2ebbdbacef30a08247ae4f3d53c0f2d73e368255e38cd03d0ad384318a2fc55ea812aac8936e459387ac1117f448ef5130c8b428eec42f0c2a890b3fe20bfa1278fb1f8d8ffe9c617c05af3ee666226e30bc06c82a5d9e0d341ae8dcbc4194864e0206a44837c69a003cbf6fd354ed12ee9fc038bdd62e6e5c9dad4ba7e9bba236c68e0d4c3bc1f4cda978d5d533c8c855bfa62c85b4d7343fa7d9ef61fb39aec226f084a9555d374a408bba85cb874683fa1c80cfa4fd780d79acd1d1330d9ea861d94556790e5e73307f08e874cf8dacf50b13a3152261b7261297651766449eb7aac12bb3e649718c920279e7791c6d85bb505fcb94db0ba8f8de3a947b648475c4d36510113fd2d3d6808e27c39d8f7b46327ab6f5e56b5e779528e34b54b087dca92bcbb0b39253b7127f0d5250c364057275a74837e487d030a685d29b4c607e15e091fa8a7afb2d74741d4248aca3c92b2d6640511aff60d2e45669af2483574c674e2da413864be0230263678ced2421c17c187d82c69bc1021768038242152e9b3abf5d95cc29ff28f432368df1bced284d2372e1a48ca72e8760b9e01f505d9297762f4b21108f08942950ce9004af6c6df1d5203322534b14543be8e89af31a85314b2ae96dfe0e53da37e58db402eb315619b961ae2bfd71cc85debff6d3740ef46cf1a3803640358abd8a7cd454903b43601a192732e23025a927dd3f231c5bcc9293041d08569a95807cc97ea0c996d95f16f9b08d34686aca58c5ff6c8841f22217cdc60744d4d1f59074e6dfc973a99a3f215dde080141feb19db447c5ed8913b80d65ec0d86467db83f432f3f609436d1b5c94a7d155de412a4c29705e5eb1e9e56465cec4c2cc23bd5970ce05d26264676f1168208d2b97d59a9983c380a19e2813a1fde5ed38e8acc31efc1528f92245175fd51bd7bdc1841a30fff7a19bcf7469c1835e1af4b9b4fcb5cbb194f1795d6258761ceab09e44b7fe8bd08a6742ea3aac62358e1294a3f651af767a56bc0a162eb3bbef36f5186eea43b4262bb0a9585b65bb5032314336fcedbaa0e07fc403c27f3d38b94e5bbe04f19b3eba49027e7012b8282f8a4ef294765413a29f9820562a758d273ce15a2522b3eecc34e547972e0ed93964d5a60727275aff958e4d71bf70000" - } + { + "name": "proof - no dynamic federations", + "numOfTx": 1, + "hex": "0000002069de100c1bae40e1cf8819bd18282e4ca370f62123c8ea2c60836984ba052270ee0cb6e5458591ac157ad414a111db4d34cedffc22e096291f7b4b3c8de3f69f8d53815b03000000695221031c25c60ef342990d9bf75425c1dc2392b5e206268d9d35044b731735db230c38210319c5a32a8ae698aaf1246784f54231d8d20f81b91c31353214538b827d718c8d210399d55e0a7fb30281da074dfbbb2654cacc2d03289ba79feae702ad6dbb542aab53ae9000463044022029bbe179c2f0d8e6d1576869cea19ef439d0e52373f7efab77cd6ccb551b29f6022042baa3c17fccfb265ee878059b6cb85d40b976a30495c6ca14b7ffe6d1d87572473045022100da88bb6fa1ecf3060ad7c8347eaa1a7ef8c9ae27a8b0136cff909994ca409f9e022068ddf3090bde1e04deda04f762eb35858d7dfc17e156bfc1c8131ca07a349dda010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03530101ffffffff02018dc25a055e773e7e91d4678053ebc702cce47f07b29f3ebd7c4b34cd30fb240201000000000000000000016a018dc25a055e773e7e91d4678053ebc702cce47f07b29f3ebd7c4b34cd30fb240201000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "name": "dynamic federation - compact current and null proposed", + "numOfTx": 1, + "signBlockWitnessLimit": 258, + "hash": "4961df970cf12d789383974e6ab439f780d956b5a50162ca9d281362e46c605a", + "version": "0x20000000", + "hex": "000000a0da9d569617d1d65c3390a01c18c4fa7c4d0f4738b6fc2b5c5faf2e8a463abbaa46eb9123808e1e2ff75e9472fa0f0589b53b7518a69d3d6fcb9228ed345734ea06b9c45d070000000122002057c555a91edf9552282d88624d1473c275e64b7218870eb8fb0335b442976b8d02010000fbee9cea00d8efdc49cfbec328537e0d7032194de6ebf3cf42e5c05bb89a08b100040047304402206f55bc871387a9840489d47624b02995e774e3b70fed56d1eb43a9a53d4fd3e102201e1cbfbbd1079f5bea3bc216882d3fefbf6f27aa761820d3a88f12e5a5ea7ff001483045022100c072816f6561e73ee6c0ae32d55c3eec4da73b035425e4eb05ab50772591b4360220311bf295010094a489d9b280d9dafb724d776a1d99b9ede31c4b59bc2095c5c30169522103cadff18e928133df2e670a3715c4e7a81d357de36ddaa5016628e70a3e6a452f21021f0d8638c413ef7769cd711ce84c8f192f5a85f0fd6d8e63ddb4d2cf6740b23b210296db75c11ea3a292a372f6c94f5013eaeb379f701857a702f3b83f88da21be6f53ae010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff03570101ffffffff020137c495f58d698979ff9124e8c7455fe79b13ddb96afa25c45894eb059868a8c001000000000000000000016a0137c495f58d698979ff9124e8c7455fe79b13ddb96afa25c45894eb059868a8c001000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "name": "dynamic federation - full current and proposed", + "numOfTx": 1, + "hash": "e9a5176b1690a448f76fb691ab4d516e60e13a6e7a49454c62dbf0d611ffcce7", + "hex": "000000a01ecf88cda4d9e6339109c685417c526e8316fe0d3ea058765634dcbb205d3081bd83073b1f1793154ab820c70a1fda32a0d45bb0e1f40c0c61ae03507f49c293debcc45d1400000002220020a6794de47a1612cc94c1b978d5bd1b25873f4cab0b1a76260b0b8af9ad954dc74b0100002200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260015100022200204cb40d59d6e1bbe963f3a63021b0d7d5474b87206978a1129fbffc4d1c1cf7e44b0100002200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc332600151000500483045022100ab2203a8a68d7eca8a3a0fac91e7c7802656d937535800da82f7102e1c06f7b80220576f004cb14b95178c71e36bf947bfea496b00b66c18d2eb8febf46362d50e2b0147304402202d4630887d661a50b76b7b32555fd76906ad298ce24483df42310ffbf62d451802200e0d64069e58047c271c1b4051c1ff3d1cba7d32e56abb0d8b8bc30e1bed075b01483045022100c6b196967c661c4543802a895ae731af44862e75d9e3c65b8efdd668727a34af022041ff4d67029052eb6305d25d0fc4813d21a939ff5316a12562d0c9038976f8e1016953210296db75c11ea3a292a372f6c94f5013eaeb379f701857a702f3b83f88da21be6f21021f0d8638c413ef7769cd711ce84c8f192f5a85f0fd6d8e63ddb4d2cf6740b23b2103cadff18e928133df2e670a3715c4e7a81d357de36ddaa5016628e70a3e6a452f53ae010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401140101ffffffff020137c495f58d698979ff9124e8c7455fe79b13ddb96afa25c45894eb059868a8c001000000000000000000016a0137c495f58d698979ff9124e8c7455fe79b13ddb96afa25c45894eb059868a8c001000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + { + "name": "dynamic federation - compact", + "numOfTx": 2, + "hex": "000000a0a8d446ef9d78103b06e6a3af54a4c28c792c6fb3035dc428cb03c373cb7daf7c3ed98edf8f484508ea0f2853fd8b0204cbab1c6fc0cba260d8ea3828a4c3b3a793045e607f000000012200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc332604a000000fbee9cea00d8efdc49cfbec328537e0d7032194de6ebf3cf42e5c05bb89a08b100010151020200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04017f0101ffffffff020125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000000f0d0017a9143a5839b8f943a149f3bb3b03a5ee81a3da9fca52870125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000000000266a24aa21a9ed0d9aa5a0cab48746412b08c038ba2fa11065bb86031be7d018dade387366c289000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000020000000102435d740d05da8077191107c03adfea535c95b3d6f9723813e96ba519aee68c920000000000ffffffff02ec45f4fdf659ff3defd76eb9d47b10d1b55b126614f1dea1afd0c9a2ac62e00100000000ffffffff170bd2f4da4c56e93100816db5e309f043dfb674c2eb9197e93b8440d041809ced6b0991011c1577b61d45cda9a10e3f35cc73bee52a5d4f6791201a1868d7adf06a2002cd0fa7673a7c958611faee00167e10c19b4e6756486dfee4ae7b7bb0d7460186160014e87366855e73431f607fb0867150d44102c3cdb50b583dd77eb44d72ecf9292fc294fa5660314d3f3978ef4ecab47b157814cccca109aa48db7728b7fba6b771ada23b76707ae72f461d4fa9aa81f13382312f620f5c0365433e24f6f88d6a036f762b26f273521d2754af4c18243d2c5049d65b9e064c160014cabb8f8c29d62408ceb5ad6ed7f2051d7a37c5300b51e5d811890efd807ccb15075ca91f7afd53411d27834550715c8158e65baa9e09e7968e0c0cf4739001d38fa469e944b337be5dd198e1723b8d81aafcd45ef09c03c2d3af137ac29c1a149421e94a267c98bdc082ff3327aef4086f1c71f48da0a816001481ed44d4ee30a87f6a9058ad046c73fc9c800bd50bce9c8cba7372ca98f44387280e9ff6e9caffd42853d582f7d6d96b78eaac310f094c43eaea4e9ca53b67391b4233a88c5497c3f1d83f639d9b00ac7f36fe2c8bf603cd31fbf01b8b643ccd9103719fe5db1e83be1d899d24e5d064cf84b572a63c40160014a862422b441bc4183aa3ffa944e0c0be5f76c2500b36656df4793d0dcae42c0b973fd8093a0ae7ec60900b153c8728347f4eb4f44709c7a73335c9a7954d487fb067b4cd3f3ff357cc951b1dacb37eb547e4927de8b703036ca99b670049d5e60f13091781ebbde5eca3b0703edcc206ca8d7b3dcdb0eb160014c6d033db90b4cb4c422559b7373a6190d07317020a8790fc61cf11930ebd67d1ece1d2b07c96c73265a2917fbcaa18a352ae0113d008c98b3655400f77657c312aab8e9b73ae6e1fe5a8436c4f5f8bdf0c30825447ce03da6d763207deecbdfee6ac28bbbdf8b404d44b9d45509ba03dfc5cce0d9ae34516001413161df67b184053336f8cea99f1b1b3e947da6b0bf630ea2f07681eed44cc8a95d314e79c0382a629421bf002978e4b7d29999cae0890eb5d6bb65f41c8568c7b3769cb8544ca90a65fa680b88dd7f2b55adf6e862603d533562c5c239439c1e6c53fe5fe7e134cae8f2eef065fe5715736d8c43f008d16001431530ed6ec2440bc556937c0ac6d34518b47c1fc0a86381e5ef3d6376c71f8eeac777b0624b7413361e7b4b6ae70dfed9de5ea859f0937d1e95b2a2287aec9aecfe6391b1452995f5182383587403712054e4334ff810275c725bf416b8ac06b61f37ef9e49570c5ce55b45ba96e8971275b927f747157160014c12fb400eb2ff9f348675d658204c2f62b53775c0ac7354935940c984cf55cbb7adfde655c07a867c2cb380f5493bde47deac6a8520873c0cb0cfca095c250ef70932b305d3ad4d97816b0066c60780f330bfc6d32f8021da100aa93d570f7b31a15d9fc71a934bb4132cf70896a45f724bed6e607248b160014d87797df972e1d31a7c0787432804784ac717b760a776aedc329eaba2e5d5702f62692a6c3f15d5f77465bd399d00744be2a8cab590885ad2d2d4aa1b3ece05f41359a01ea3ec52829b704c68f121abefc4cb5d7bd8e02447cf7d1fe0991dbe094d39d9babd832d59bd9e21d980cb921a7deee64856b80160014f58475246ce667cc9295d0242cbd5068c7e2c0930aeaf518e3bc9f26f46d41d3e927d666e88b7d7c3e414430468b2d0318279762f90939f4aed1c77022d1c939311c4e17a8842199dba965b0c78e42da75f3a07eda0d02e829d7008faff605ef528d2b58aad449e9434329e137c12a1358038f3da49a5e160014d12f69541624478071ab05893bae4df548de27390a25dfaeb373dc501cfc9778f1cef14c0ec55564abfce6735ae0a41be88e300e4608c89ac781c84117543c8f705a7003ed8a93b25e56498a14250106b0a841827d09020b24a4065762f0407407358168af10fd74fb50d2deb2d741d1cf814e776699a9160014fa244d6e2a6b2e1508dfe241ad876a83f28d1ed90bdaa6ce1d45b50f985e569e88af94c0d0934f6db9a7e7c2c4ae67dcd460087aa40815fe8158138634ee342f40f88271b98ce1917640439c8799737cf1b3ceffe62d0370c4943847626cad849083ba85580cd12c4bb670d3ee7fec3191e3750915624516001483342e1e701df4f55957270572cad84392971a980bb029b057aa9e35c60493fb00b79bf3322255047d893082ed1172a5bd247d9d8d08e588ea93dea310a2ed345237acfc37809c4c21dfe6c3e849d5ff3b9aefaf204c025528d1f6b9682264f72066eb2e418f22da5461f12aa69d40fd8f0f04460835051600145d22dc1b9eec324a4253e018a4f0d61719c168230bee518a52be7469cf55deaf633e55a6cf071ccefa044df93c4f43d42b4c52795809375aede1fdfc85295fe6e39bd5897ecec8d586d8a28afe63238966624590300703fa0450521beea79f61727b3df7d575eb3b961af9bbb774d6afa243d3a324ebe9160014f4d1159c2886f0fa764b15a6a75874606b585aa30a82975a0005958cc493c0dfd89706fb12790111da04ee50450d91f4dc49446717089af8fb9451722119067e166dff4c808d966508d22a54bdf90944de1d727adbfc03d718d3cc4d61de108adb2ba139e2a7aeedfdffe9a5b343e0e585edeced73d3b6160014ab6cd763df0183f98957b361bccfcb15dd40a0930a5c82f223f4918e24201119f13c28bdd9a7e6d943bbfe6e009783ae05b0f8bcfe097acdac028779f2b61aaeab887e19e9550556a9b0455b5251f652e9437d58684903ce5bae43790cc39feb556869f6344ef160ecd1efaa587b6a126995267eb76703160014dfb2f3459b4c269248c2952728b9c343644e0d8f0a1eb16e831692222cc8adb883082858747e15bfd1a1cd8b9219360bf8849d0c2008a09d802d6c59d9cb323bec20fa1238a12311fc48ff256943e83bc0cd505c5f7602b57fb3f6232149a092a366eb191fe9b80ed1e79d0a3fda36e5c0feb1078557ce160014bfa4fbe3b29891ee0a6e94e9fc0c7b37cda266160b5e1006780f13ca2ab1769fc635770d64bcbe176417d2a8cab5b3e679b5067f010924826fdc539e012defe2f4e6e2ee6a667bc4bda7719fd329d1f5a8b2fa4cda4102896f8c6419f2ed3bb0bbf94a58535256805027bd51837ff8feddbfada6e74d85160014f85c21de703e15c876d68ee9a67d9ca419b5e96c0aab711436add19692214c677f090e1b8fdeed25b80d9197f58adf0f2b412c9b8a09d4dfe47cf3ac740e72e4251ff97536ef9e40550d61e038d329a608dc5094aa2f03ee31826e195e25a3c3f1118ba3a10a9ee3d09d236fe68d30d959547c160a31cd16001477ae933131eacec4fe0ba9958662b417922e6bd80b60c9271ad044d1aa9c359d18315e2e033955a4b56fc9ddfee3bf7b4776975e430833a7826f19733433b1c7aa9735cfaf30cd0dbe3224198309e03681f677b3643f025f3a3e991d3e9aa59f079840f7b1ba7909b4f863ad9a1e962aee01496667ca7c160014a36286c52af6bdff02be57dad8715cd5449fd7a00a755e63cd17960e49e02b46934ee3ee45733e4e23ecebf6923bdadd359c16993b084b81f2940c1ce0d93f95b575a8a16f5e718f0b863be09bfd007af521fa14409c039695ff3a30f6ac7dbd0718d2037997c526aa7ae198c67c3ed269c022c908872516001476a5b8d27ca4ee40cd14871281758d8e37d9930e0125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a010000000000000f0d000000000000000002473044022040c7b3da6d6ec5894ddafd85470f5e85b3dd02d4c393c431fe031f789e25c36c022078ab40ac50e4d7a7aeb994cf5bd7d06b325d8522924564ccc5527d8022df6b50012102af61fac2926ebba4cd36c4ea45d331ef7fa4657535dccdfaeec5cc390519be760000000247304402203cb4746196b5b7c60cec2f0a5ab49c6ed0560fc4a0edb4157fbf8f3dcade58cb02203d50220fa7b2c5ba2e89a0a17c605b33f843db5a320390b542e964439b4effd5012102af61fac2926ebba4cd36c4ea45d331ef7fa4657535dccdfaeec5cc390519be760063020003bba889c0e60d2325d6d0397dc7a9c54b8a0a0f8ff83feced8d751af47f99f52d2c7b345204abb4367568db7c0fdf03278d7a5b82189075d407034cac01c38669e3e4a851a2d70f77c2b405ad2884276b3a000463225343dba3c606a38c9322b3fd4e106033000000000000000193bc460154014f71a82b5580857a427077812dbc4e3b34fce7341e4fac57cfccb52ced248d69d082ad16bec684efbd100e783639648d23cf18def64032ca0b6d328a4c0f88a6881adc8f6c09e57c760b252ab8dafc97fdcd8671b971649c5075db84769fd049fb0f5e64cc3795ec52bb0dc8f5bcb30407fafaedb21908f5a0bc70f443f1af9ffe2f5c342d6a746e72c4edf1e5098d06caf941166e9bf86ca5307bfbbed1202cd4b99725d57dbf154f5feff0ae58d4a3cf89dc64edfb429da48d3a274fc85d78277340653f597ab1a00280dab98a58987d7203efff3fe7e395aeb870998297fcc75b119ef07eb033208637175ce6368f56d58825a60f6eb7a105a6a04bde2fa1251759870257b1e63a3e7911f761fbe5d1a9a9066bacdb59f208cd9e888e84afd1c4533615cd9a41b6337ba89391c4d5e93d0a7917952d879a9812d2bc329a81a826bd38824e66760e75599fb9f3ece2d5ccc47da6784c3f325b4faa31bf57efb3d304dbc6414f5d284a120471894e76a013eb9fd815e436b6bced0ee6faba3303d453794b3766ae672e0ee830e27b0492b61af735474bfa67311327b0a1815470882c11cf0e66f48f9f793e28f3239ee1932eb6c75b6c55a26cc9bba9f09362bd9b2030a1214ca18ac38e0e495912a4d22a3f12a45fffd6040c846d3831ead56090b16fec7662deee6ef6d3f5834234a28576b46947e3031a483b67ffe71c9f3501f6eb781940080d10d68dba086b054cb55d62641f44fa66f9aedd633d69eee14b6d76e35e0111b3fa4840f273ebafe6a5642b62ed76c7e02837771c9e444d5d8b3fe0b7fc86a56ae871600b67010f026cf4ffc64f311b798a4bb2441b1b40bc3234c8b967cabfced42c26ab45dd4b1656dc76e1edcad389457f893d3a10f22069f65a8597256ae0508483dd3ce1e61ad0957be3a1018fdee6d30fa5d89a5fd6f6de7f332d3756f9945cfcefc010ef7db2810324d7f3aba83b6f7d97d773957978a861b3fb4ead6a1d0b1f493c68188840152c8739075185aa37400b7f2a0b0b621ab8717a5b4593ac0ee087525aa8babec8667db69d082c48f64191dd343d259334b29941a399e293c829cdd77bbaeccd9d4d73c9306ddb831a582a933de1d16743de7ca5bc504072fd117f88c37da8f0b7ba606d35027f839ba5b62ac1141d271098a6a58258741e4ff834b82d7e95e7e6c324bbd6d1e2ba3ceba989918d8be280c3f39bbc1553f8bad4b37fb9fd1e29c556703c46e872a747e0705d505c7b0b2a3b57f38a65850529d31fc1df6d2a6f99511fee18b816bdfdde485d06301b85929b0e0d412704827ff3af7e9464bf1ab5a3de312f5c9a69f484710b0da7d9054bf47c556524f0e3f21a5ae6211a0b1166df9390f4a48da52bdbb2f4ace85481663ea5e615eda4c014d32c8dd5bd1cca6447c0d0a76af7a25ff320060be5141c626fc50a556dadb5a3f93067746a89525fd0a07b058887360cfab4a7f055766cc2fae9d6b59506ef854910ab79ed6626ad198b416c13ce16f42ce1ddddca653433d70e80b0af02176b37f2af0ad1c5cb23f284ac99598088af50e6ca22ac45f65ceb06aadf11aab3a41788ef5499626b249041b49bd5ca71b0b17006d7df17a3e1ce14abc863acd1219d6642bedbc375866f94417c0072ec31b7697659a61e6601fe206248aa660b60a02368bf421fb938d2642f86cb927d5a0dfedc4d99cd42e1c439f7cf21d15f93538cd2ae1df7d06cbe2fafccf134a9d3e12afc70b35424d67aae7918b39e6f0807f4046c579f1d5c1cbea6ad7719e8a62094d12a3c5fb4515c8dace908813f042f34fd7da1a7b6bcca9f8222691e784ac84b20441044cd73ca3435ae641a359cc58b3f243b6af452cbaf27be20e27b2e2233a5fe7a29f7636d66942ccb787d40867a8f4d1e6d86e120da2fa23cd0cdd96bb771ee71d572039bd1ea4b8eed49c0e445dbc0b592de2872b49b35dc94e0f158f5368e236f9a7fdf147aa8fd8e1b043079de997fc61e8ef1e6304a3f95fbe620ddbe87bf9e9dbfdba48f74c13c32da8759192651acdc46e7dc36a5dfd4d0b12c4006271bef80fae4a47d737e3dcabe83ee5331353c9a7ce8b2327cb27b6bd46e52ef70c3bf53571ca0947a668d56fb5583bb8c0f2e19906bea1c3902816902f50dd785d5b86d35829c197538bd485905f9c330c4b14ddc9af439d4ffa759a89504ef3cf49223b5c975bcb1ca3762f2349c831b76560406d636e8ac520607fea2c32e6fe47ca8811259ec5245d79851320a9de582bcb4f22c33f30e038568f7b5b6addb0242245f1da5af0889c756b47d3abe7f7ee2082359bcc7cc555449bc7c748bcfc3ceaca67cc74fd314ad91d11440e89d0501b3daf74425efcc43e51f1dbe87efb75b221900836e8cc246f5b46ede2098f35f933ea7455b6550e912cbb97263636c24913782071e60c22c4afaf0773b1f2ba7dc59abaa9a729931e9965bab9e2ec8c42424105e249e30eb890d98342a6a2750badd42111ee891d12770ca25ab900796d476eb6db6d544aaf2affb14b0b095c59558f196d20ac3bd6822efe840fe6a357fa00bdb9050b3c9fa373f3b6c5c50ba09b1054306c94cbcc97a285d0c527b801a87e90cac1bc35585f3a9442a80d467da739207e2a6049626ab91e1226cec6c80485aca844a7a28bbb5cd98e039c09dfe905735827454f0aeabdced00ec7c5cc6018c1a3c0c9744064de0493f00270e5f38b9f56522aeae54ac412345c870f30d3525168b7d1079e72d7fdc3f0ca813d1b6c96fdb56f995b8bf6f1fa525d9f6426f56452933d8902b6ff30852653414099143acd792d1cc45eb51ef8569b9260075014496d08406372cb13c146ebefce392439a8c76976c537cc5b37de42b2246696105fdfc1c9ee50206ab266c306fb9cf4577e6b481e426510fe9f66ee5c59543ffbf05f2c25387df2bc2be4940b007595f43272647e7335c32799908dd21330e45248aa870acc03bf6c4b96162917c24d816a0ed3bdd6b7588ee1ef7df35dc8bc34fb7593b18e8fd1f194dcc3df48824b4a14bba4e1c7b09fcfac005fe69408073ef60a2fd939f364e4bca50bf2a05f17a3dc3c95cc5d50a329e63fcee7918a3c6d09de75133e0aaa3d2e6ca1dcdbd470fe1c6c9c80835c86566ef0bbbdb506d3aaff97f82f98bf4b24e1bb0a6a33cb02b160eb660c048c095ca62f3c58aa15675ef3e890ac78524aacaa6c391218d684d8f013219729788562bf3310c2076b24722dab9e19e1d32c0ac33e7609775754348931db472d033cddbed270200996bb9e19cdd23408041012577113be742d1cbe55a5cea503453511689fe6ba9295dc382d72971aaeba98beb1b92f3fb7c980a72883cbc478ffcd3f36bcabc4568be77bc2bcd206d4b6f3ae3b09418f76b1267a120d67d834a94e4d0640720ea806439959701197f5f55f14c6d016420d117d778588bf46f4faf79d3ebd328c5593e20f453674b19dc476de4c1fcd68e6569bce4cb98da9fac37c9c86781d3abc76be8ec55e0dcd3efdc028027cddedabbeb22ae7a6c5490967a22fc9c2094661bd783cb23bc24903a8eba9d938425f05956a673282766854e8fdf899be5710516b234198be979ce66b6f875113f303a0374a7b2b406cbb946c65ef2fd92a3fd39a971c1b32714378190c0816abf7b9167e6232c7225d9312d5997840923c5fd32a89459617092c6f736cdade30a3fafa0558a45df1fa18131547b3a9a169d7263797a4b676a58ef2476dc30999478fcbfa97f53ead4a68dad788036d3f363de336f286a649e55acff939df54424183ea296bba152644f853320313e042231c9729c393da9d434e56f60421fd84ec962ac18e19e28163481265d98a60ab4b80fdbbe9e902d12d99769c10f45d8caa6c4ff0975e85dca036d1ba9bb39e48fc6044665cd9cc05b033e4974bd39db091ab9206224bc0dcca391780b3e9bb7c156a1ecfe016e857ed224fc6240737c161b08db2e43a819aca0d5a3d2a7fd01a4e8bc65a8e4bfc7027b603ff83f92fee62b2e91e3188b2f6e955e4c97bd305312c1559228ee9f4760071b32d2379abb95370e2158f11a44c68fc100aff2a3ea8d9b754b178c9b57424d54d285c9e85a3764e42e5d343ac3fac1038e1df7eeedaf649a9febd0d2ec04d01346d6cd2cb1d7943da9c9a27d88331665dd5483bdd469e814abc1677ea7841bdf0712fe40121c30581b482f0c939673038f14509955cefca32f6c9372bdd53beac180ee0a13622ca1a58eb18eb7e40fcd34ee9649451874dd928177016c7b0bfc6ec2436dac8e73f76f09814e280ccc7fb6f1da0cfbfecc7edfbef24694b5285c94bd750e806e1181237295f84f0ebc1e5b86bdf1e17e8f528e0253fe85c7547743125a6eeeb93e99174efc133d359b4fcf94ff121e804a08b565ed36a2cc2dbd3ada4744bb239e5d812e9286a4c130ba45c9226a0adf567de058d3a86c0c3fba1affa16ee05a4b772991ab24f80780bae20ff17b827b758e13a08d3324f244eed723b9551140dbc38218d686014399bea0e36471f59c70af1004ea4d7aaa1b49933f8f22ddd583a6d6bfef572c1993f5a0629ddea7a3cce5c7b0f232133eb929f96f48161c259438e22cd94fd3c97aef4a1bb64937cb86263f3db8691a82eea7aecb9995c0cddfe7fa5c756de22ce8f0c92f9f8bf54b398e99c0d9c8d1fe16c1a8d07be6b5b9d50f557d9cf269b14e2621e6719612678f497d2bebb267bd178aa3005a7cd6baa7cc22f65c512f2beb9564cbbdd192da490127598b4856348508592dd4152dd45fa258b323692c0782db079d693ad6bf78109735d83623d0cb5a1b4d16b9bdf69c82f67256da4042a3294a1ea0520dd4ee0135aea6be49a654f92498fc3fd9691a5c51fcf977df372c4b42c3d673afaf541b8260211af8a7303d09fc3f12057f46d098e62dd73ec48a81ea2c91efc7ead0212cf58817d9a1b24ca6b9e2c44c74dae79650018e4ffefd7f40cdb6d8d2e61024ca593159fa7cdccdcc0aa0cca2a3aee744e36d41c9850baa247e7c4f0eed69930a16a5b4e5a9eaa7b5484475a43b156f32a392c01dcce86dd9294bb3e0dff789086854836bc409548ce7c6682316531dad1cbb2dc07ffca94ddaaa928ed23e4bd7d82ed47e0f3941d0d2f48f642bab95bd64d7ced8e32b632e30c149b6539fac6052b287de43ed93517f069a821fe3d45e3968778f8b290ca88dcceb73e780931a05fb8f4d79b3a43dc5aec5c8e49dc1a4a6b9f96147f3d0883878dfe3339f79588c2edd8661c4a5b2e6f3760349fd050ec07a8ed80f5147ebba3b195abcd1032a38af87dea5dd5b56d7c7e16296e8f2194a37546356faa35c63730dbee54dee93bb2c80062b2844e0098cc2331c81ceae6318651b8ed771412f91a911b1c2433f00f828d3e348b98c6d38a587e6315d26630e093d81e7dabcdfbeb067e5c4f59b35d8a414c6e82c46d38c11e6b308be3fbd90762143c0b51f4e70b493e5a1207eb17b321a2540abdac8736ac0156d982c01a272529c3dc7f995d1a65c50f14c47dc96795ffb9953bb4f83c0b129aac0846cc818b83a330f695b085eddbb6126a6e6cc34643ba753647c9038388964cedc7848288f308aecc8a97010c225c8ee0bd01303c3a7688c375a5e5554f091bccf253b9b840a3a1f97bdeaf6c116a29a6a22341cd7c8ec0ea6d97f0f23cc0259214f8314d53d026329403db82706fd93713d8b939fde26e522a438efc4b51460f3c9f0037c768a8b0184203bf898ca4868058d5e7b16d5333d61dc796aa5eba4411a8c37a7426383eed51f5fac1267f681bbeb47c3e090b9527b1693357d9799d62958e99cb195637961b541d163020003ac32943f81b2fc8ac10a3dd14b5fda4888db17b3596469d594bfb1276b3d532ee124e760ab1b15d0e97eecea75489ec44d6a359c88b0cf47fad1bf57ab5f3cad9d1e40431e9e2ea9f0460a398beffe1983d3b0a16a4934b3da454e23b7580eebfd4e1060330000000000000001c66d3d0111274fd376ec11eef8dcc5f563889b47c3681570f0ef0d5922f2c01e6d4c3340906030c00cce6a6ac942ca8b4849bda4c1878ec252e4c1a0b20fbc52f4a6a054ab164d760bc6670d0596f7b5cc17ba4cd8fa3121a8f7b60722ab47bfe6809de08a7c7d2926b85c3c8e23b9340f22961147641bdacb6b84c3234a4c2e2dc7b9f79daa15db3b16b31c509aa4d1cdddf9d91f59e6f56f1bf9c94b0bfe7dd69216875d0869aa1e20f39ee282ec8b35f2dd39d7715eab2898b069d268a4b8142d225322cf5e728670d8a5e72da28b866526a972e9f611a6477e3b2b82b679e432f995e32a2fbf334d0dc84a19f3869f3c9da8ece3add129e54f6dd97f4c1750c855ff5c9f888e3a1eda42b4612a2cdd5fc155f9d8053cdf14571248f958f7abb859d7c7dccb793349a31d715479a178d5420c536f5f0d8f0b8982a2192f3fe1948843595471aa8dbdc42cf0a5b4a590f01e6b7375e993fdd62d1ea8099a22280462a9a948e0a84cc39c6ce267dae30047ad54d14f0652263500b659743692acda715c9c66e8d2e302236a5062f110d88465f2ae1f7a38099f022396636c68511561eccb9c3422a1386b664c89576c0a5c115e28745630d95c2ecaf27b9e144802cec6297234a02a837985a093a583d96fc519916c6d670aa60809b5a486ca069a448051134ba9eaa6c3f56a8313e8fd31ec0e6b6753b1b58f4a374ecb79f228d953b29460ff398efa5eb5c1edb8d466be4ba784c213e1100a47df4d51ee8497a20cb05c7af7c18e881d11339c884675b8f5018487ea430871a55e3c42945282190df7a0b71bc671623a13cb233a5f66b451f945ba6200436486ed47c9a948ceccf47a9ce7f8783188ca57256a001f544dc02ef9873c53735dc5c92ca08189021424df9fffbef41b37c714554846d9898e0595c7ecdca8e9397c0638638cf0200fb38479d8f34355a1877f67ca89dfd0203647ebe195eaff1c05706fab30a83f9836e3c86825b0aeb2886fa95072c4087acddff3b5cbe824047c3761c394856694dbe96a700d709d8c6ae97ef63f1c37002fd5d9e1e1000ab94461705c96039eaa00faefda4d79586822b105022eb04d1a7aa74fbe77f567542711cda977d26181c4a7ca056fb6fd52ff54f3b179c52c58c367407d07b0efc5f30ed73254fca7acd7720ef2b7c4282d11cb585e90f8e83b1da45666162e9dc5cd49e6d7221b99f9ab6de7104b3c5688d16bcd2cc01211bc19176a50e420e60d4c4899fb0f995770a730c1b1e0ac67bd748f0511eb63a8e104f2d90afff4e607f1902496d5fbbae0583719b75c3d40af055bd5048a6751dc8bf931d459e34da1b6bad87b1c461e23357321546e9c6f41fe5acaee56fcf2dc2bdfc6af8164956107c856b9c85927e8d237cfcade765481772291c213a73cf12957a61418566eb6efc77e82f6881a7997c8f44eae0883dc7cde0dd5a9f9086020c65d867e423ecc6e9bbef0f32de4e479c45d298c74c4f4abf5bf67e09cdce539ec4dcf4890100fe699f1cd45a38305e7aa60fd2ce83df8a1d8f66c55fa82a4e7673ccfdbc52c4b913e830116876992c5a5929332978c7fb6ee7dddf0af24f774bda960c039ac3f9b9a703cbec4e96212c48947c2ca54b5b2806f1bbae0451d31250def5c3ec9137c8412c0f1ad09f99412401e0fd2c6f0f03f9d5388bf4345b0dd42159c934ff3b3b7055d7dffd521dcf7049dd49c0051f590cf7433496449ab2a20f8b41265f94f442bb5560fd21f9724dd5cd7d78d6624fd3812971bbd3b7fa788f3c18b1ac9c06263ede3761894e1350b64c838591639bddf627e250de63c471012180800c2015dd81ded85a0ca3db5e0d65f61901e130dc10823376dcf371609ba3a60825b620388c1d394fa82ee68c578cfde16d144ee90a068cbef6508f781c65af1df65639c372afd1539f78b471abf2812015f1dfc4f99b4e098ebd11ad6eb9f168f21c7d93e7ede29f449ab2358ab3f68de72bb2bfde5ffb3a820d1dc41d7ed5f413ffe82d510202732080f4bd19ab862b9dc8dffb418565cbe252ddbc28adcdc266177e15b1d296b2b84ffedd4bdb9e0d8332ea0105b00dd6041b72348c91053509dcf21112bada3fdc94bef2c754e5fd4d0ce3ba358898ee1dfaa71f7597764472b4f2f5f052097f6ee4b5f2545db1cc0a7e1b8c53b2f8cf8006e9e29ffa862987eb3d0ea9c9b78960f5969d76fca62d082a2995fab29a9232aae464441583607fb59adbd3240fb097a5e72275b800bd069542135d7dfce3bd60c6563f9e295492fc63f2bba61897317cada9f24190dc4ccb0e59ec4274737530727d2dcd8abb1929363f0914666ddb1f6e65a99e39499e3c6254a9b409d6ad2b89f8657a36cc74a2ed78f1b73832f979c95cfd56376a54f3d4746e886b04df7e70f41bc2f06f42b75ee19a2956ed2b4564a71dcf5f1a313a2590d3ad695975f5c66f73caa8facb58ad8c8d7685717c40034b262656e561574041950b192d6983a7ff8d1da4e516e7b48887a4e6a1431c52cf76af438413eefa3ce8ffec357c1ef6c1714bcfcb9e923d0bdf4ba960fb44bbd30fa2148cbc42ce8f5b1d37b37b7fa3a7ac4119432412a00f3dfef0a49f8cf3a17fa006355ccd6fcf4e0df1e811119e5979c6865f4442557b1850aadd58ef905aaaea5519b6753d3ce6338442be33f9c6680df7001e20ab3b65e3d460e50c1a3aff0590391ff3ab9b143d4c7a9e1c99995778217b1b2ebb76e040577ef6dbd000ae38084bc8f75475f64a8e558f291238d3504a938d09887c06fad820d82201d2571b34cafff6e57e82cd7b7867ad6fbb006a2afa025b5694e01c565d8e8d22fea15a255031c8ef4ee2a97bcd8877e2aa6ea1edcac5d812a48e203181be1aec3342c39a31b7bbc09441deae93f4e13b1165f8950161b0c57b0fcb76642c45b333bf1a341321b8ad8af6789de71e7b45979b2e4a9053d8c5d5ab086e648b3ddee35a366b0b990d401deb443f4709a216dad79a1925b37323722a6ade4e72d29ba7472545f5dc851ec33ad978b28cb60dae56628022f2b69604f7acf7cbc74e4e07086554ef444c57dc0cbbca184cfccfbfdb0edfcd509052b858a457867732b647e540d565cc08170c3197026186b264c8cd874c6d813b6a1d74528540e5e44e06af65f989a0d64c7891fe0e9c728cce771f1e30979fd69a88ab11648cf90832a70daff1c8209fc823d613eb7ce43c96b68798a2d7e078f6506a6ccf622ac2f14e6a4eee5434b0e31357b231f44006de219d087eae489fcc8cccc95e70d4eacad3448ea47d67ed6170278325cc9849609a91dadda53de2b95695a95b594d41f98858f6f33d6957052852a7b0da4e6fd26efe7b7cc76222d65f95590b43c9678e74f9a8d85678f4a02726f1adcd2ffebb05c75b451f7b05d278a67a9b1b94b3e577d103d2b6175f448ecba8458f4e8c044eab8f28cace16e5eaf873ba39aec52c04888b027167a373fd87fd4b115a4f9454836c3baa5e42fb1dad784e3b122009c051d6963daf16777833f3a3f7093d2a5aa43b0ff3d451e7bbca6c46a142a0155ab9c1eda07536d56c74663811e79e1b3bd91dcdc5f7aa4a9594ffcce9c80d0b76db487c42b6cfa8fb166b712c7ed6e30340e4c641bef2f8e2a30c3faa66c751705938cbf31807bc975e6cf3383f5b60b0df510714b465467f2bf91e524cbb3362d3b226630fc6c00786659f4079fb1012dee75b67dc7133b68d951457fd9057a392fce27024ad9468ed48fa4ed48ad2dca22327915406570f5f43476befd9b16dcc167351034845dd10a0a33ea8d41358fbfce653bf52f48fb21e40a7a58640faecd90660f6cc60ebf238de847475ed5c49260265bab1fa29a7f54f635eb4aee9d062875831a4e6cc327a3c48bc0c24647cec3b1358d7b90e86ff8979726866b08f397554e213d219ca8237c8b628b956e33aedf62abd009e1242b1b10e72d06c6f4e5b85c33eb544d91d3645535b73e072d69443c906928902626d485bdbe536cfceb8e097cd6eae91b03c0d62204d819a9dc63e8418a49a41e2d987241852582adf66b66591b38b71cbe49b065cd828f7fa04ad80cc060df55470ac1d1eccda5f6a71950f76ee80ba76f5a1916547b67e0be8eb73eb2376a9a49acb25c6609b8c9365baba545d5fce0e553ed5b9a51b9b0c0764a51869b2dd5d81b895d047a5b4645a75ff48431bcb80f315cbee6e1ef8029f1ea2ce545e28328d1172cf63d13b88e92e4f5dd4e4d464ca5eee9e02ac32ff4b3b84573a77f815f209fa0406ebac822aa42abd7649e69b58ca01aa7295a09facdd19c97bef6d0b55684a962043f7d8f3ea09d4f0dca08cf2812056645d38d3360b3b69a8b1a96aa10cb3bebc694a76c8c8bf16a2ac38191192c2b53ab0b39ab722857643ef53a049ac17cc5d4128070ef9a47b6b87d47447aa4545b377ac3fe864ec5175d0befd2c1bd1587d7722cc2928cd19bc8c1ba8d23de5e41d8eea5a52e5b268b8e206040a7ef21a412270dede1d2a50ed7ab42992dc577ad0e1c0a1d143fbde2d8bf526ba0adb34f57aeb6a9101541b67b9477716bf78523e538bc241077501fc1ea35e350881ce4fffc7792ba2c2d4ec7fc6238ff9a293344019fcb1f880d2540fbb50624c29920ac905c0915213c07160f6d319e1eb8c9c6665327a2de5c8874f3d0e91aa1642c8aa91c3be9b71fedafc58466b4d939bdac0655590e97462a231f659f86a34bdd169c8a7c46eb48460cee6dc625e660b01637f66e03d7ad3fecb7b72843821fbda1aeeb7489584dce6e8eb0c4963502b6788f286dd46aff3289f008f7df69b3d83239b5da6fc99b733936a58dfe5df73b9193863b54cc3c363431bae1c0b95d5531c8a42426d1d4001c202451dde8adebb4abbf418a6120be46d5e3f73f2070629c27970606b7a223636770b8b194732e71896a0d4ba75ece1d7298dca7aef9394c951f94501e0ef4dc5c7626c87240b68c0eb82e040f45184d186f6fea8cb23ddd73ccff5687a08099fc84d5508f4f50f479a1ace06d72946c5752923e48e1de519c541ac00a353b07735ffec6c1923339f293f1bd2c22f889dbf9ee8dcabe7f7450d5b19fd759744ebc0cd6cd7bf4ce37b4b4562933137bd904faa4dbcde0142485457a0ef41a26428b9ecab3297416c1710b05f3f3780e4f5c8353f7ecb7425f38e5a9c202e3056f74e4bc1eb6921a2f3805f20dbbaa9808d7a1477a23b0b437e14a94798c3ae6c08e30c32c4a188ac09ac73781d657ca097a20887e58e4a9b3f12f38b3117561062929c4877ce67aab6e7c1bb539a419857b339ab31581559419450cfbfdd4648a0cdd2216788ea52c91bbe3d1f8d32c80e31af44abcd0fde7fcdbd134749780ee0da07a96cf17664abed925262f20e1824d84519841d4bcbdd1bea3178e4a881c3e3e05acf35af56cd41a1d845901d84cae3cb1b4a14f8e44c0cb3aab5bd6144724b15152d9e05bf7f5600405dc2e23d9056bbc5ee29d32c7f6d618bde1eaab90b7afbd63e3e52a9a6a0641f8acb1a208bfaf12a3cec0d6eef9e3d3ec179314c4fed56e3b6175851d8795c1858f5682705439345a70a89abc4bd9e19281624f19b2f3715765cdad77e10d42c1ceda826af0a92f26b7f362832047057b2e57f9fa53023d579c5f82450dafb259e04b22cbb55b9f7ff723a0371cb70cc8133337702c1f2ece56a00c8fd09717dda257c8d433de1e3d3d3d2537c56f05a5dd43b522d3759b1aa274a86c4a46a68e4a2b1c9d7f4f77a76418f912e0899d4726e2d734d31d149e49669d918940e227848c2ce5fa6c8c641a3d5a7bae74a69747ddd7f9ee4553c6fc2068b99c9860ad0bd48807d2567ee48cf1d3e5630200033b5728bed13de31673130b64fa6a6bbc1ff2b058e62e2a966719e665651e9eed9a6906db67ed59e6f84dd1470861bbf7aaa21ea13b7bdd14b0e815d0077e7add41bcddb2399bb40dc7e484aba9412c092f69f00f3a7ec97370f76bf37948a372fd4e1060330000000000000001ca760101364ffa054aea209053d33058f50f08a00c8106360630835f7582e26c2ceb7b5b7646107b2e6597600e743b20bae6be9651fd5a01399e83d5e1a1e90777ab37500ef34fa6424e8f810b36b19b07bbf20063ce75af11544dbe037d5406654732443e96c9beb1c9140110df0277202d98f85594511a3a42205834ef9cccd25d38cdd2f0a262e7f091851ee1233f7b790aca2674dd83d36ce5ced85f312981c43a02a436666c7862b9b0116116e6bdd325ef1397f25f96b915ac270ec322db5477b296d8c781b29f397f4e28294ff74933fe024d5f020de4e137f049523ed7248c734be169aa92e0e0eb5b508bab58a314f428279b310688f02459abd39c733bcd7d71e423938c39de72a9836c3e408fd04ce1996a9552eaa0b3d7572bfcf0b5b0e4d24d56b68c858434e87ede8ebbff2d82ed71b0f7b608161fc613efcb6fe79ff3110676122db523511bef3afa041d4d5c389a1a135f813b24ca90ac38e187c4b69901271e074ee961e050f48bec12c7edf531c49366f3cab6e50b9559662d093cf100f0b99b9527fbd95d93790e81723c255495008f01f1cdd10642e97239ef2d44961f766648f751f814df34fef0e2504225d4df08d50c96576d5bfcc1fdf301131ff74811dd385c4fb376034b2fc0ac7efea1aa9a51129b62520a14d29f08b3355685c5e28adc5ae20d5049e5794c2f633c8c7c494be059312f75a1f94b664899f3c2a3aa41104e31b547cd73a1a277ff85c229fa3ca91e455742189ff897de2291dfd6ff0cdc22dbb1de322bbf1836597a7c98d52358391a25c085be0c781c768114615537a17e50e347641ef865359ea2c8906c5cfa3459284813f137857c2cb757d2c42439cc69a774c73c78d52e5d039b3fe29ce0b46231956e72365360c39596e3384aa0045cd5bce628eae416471a79a033867dcbcd8112ee2cf4072e2bf3fea00dc0b53ea8e9475315d0d76de96fd25f405fe92f5f5fb5267b608ee32e4eafc4162daf6dbe85f4ff93f740fbd644cf15545e7c55115bbe152666f59ba6ae6ec7d1ccb669efd2a23282f00f660b24c75205edc802950e9157f9690c3989a4890e8bbdd886499d2a402ac07e865b250f22a6e629ca37e698b6e0b7056ab2a9963246c70d62d001c220fac5319876397d7ea37cf3e01386ac588c7878153ee5c5f38246536479426c0b3a6f24102c7519df3772de7df50f766778411cd8289e0b87f3e4f0562e3a740f14ee1eda42f27c4b8ce82bb3eae2a7be7e88672a0dca5468dafac8b1fd86e314a2e72973597b1b11005a032e15bd788491ad10e805ba99dbc19d8002d54e21bfc50972ce55eb4a54d17d89e09669e6e66922287930ae14e96941d21ef9b0f15b4c42988b6d3b47263494dddc0ab29496acad6312b8ed7db788983ba2e28383027bc67aa0ece0ab64cf36b6e26b7d62f9e157ac47a6f318b6baa553cd5f09634c5fc0bbd0b4684a7ebd85b5847037797a983dcdb07beaadb3ea1b9688be60bdae5bbb2f9f356167ce61164882a032d22204a56b38509a7241ea0d51efe3690c7cfabd0aa424c56ac7fd6c284befe349eb52d09a0e779bf16ea9ca2827d94ffc4d612b62c68a77ddd563fd08fc96bce82879772b4e1466024bb9e6c9219286ca4692be3b8e0e16a15f42fd65446e552c1a9781e9bf04fdbd788763af2aa532ece25516ec2e5176932b3eb3e36e264c5268db6e1521cdbd1125968bc5b25776dc64058f16b9290e5495efbb360cc105b57fc1f910c9795c808dd792f99e852d7e40166b09a5a776a6763861a0de0113ad54413015d68519d6b57190ba1494d8614cc44959ed35dd6d55705421a6f61844317dbe7cdcc9575ad8f251b5acc2c35237fa3b9efd3e5148ec385a7701426d3633e375d744a25f30c52a8e330e6042a7c79633b4549edd29ba81da3d889e00abc8b1aca93ef6135436eb200ffb8af3ce86d1f9389261d06e80672ce3330228070a7c8c61a9a4ab89bfb83772bb159aacd3857262dcad74d1ca129f7bc441f2b0357f693f2fe7d2ac2cb767bae0e25821c3f8c9c5babfd1b98123de712b5645af6ed7f0d3b1472f78360ef6f725e403dcb64bc9d9364b026ed0cbf7f5802fbc04e0bb656469cc69a6dff2da86720f9362fa399b350c6b736754602e8295f78fee67c3c981679d4974f2097b45bef54e3e3b009bf7c82284631ab9d672802d76ccf895b43bad8b163e6b372a9e2475a85733bdbdd4c7aa35a4ec761475914498c55cc25327ffd8a373d0caccde9eb4a357beac4671d588ac70d0ea57aae2a90681975d2270539f7dac796bcf33783cdb9211084d917ae0a08a5c29c45b355c1fc3c05c05dae13ba9a5e30a07dc7550e6e8c41d0dfeac3e768b898332739af056b615541021610a4721380abe6eb546919fcca7764c8fd18c341dcb06a6611c1b204ab2443e1531b35be395d4df6650b1d36c6ede69f482ff8685de4a1a16d1d0b42a0ac2e482138a568f3e093820f5d78afae4d8f66e83023bfab450ae579c5a478879e3db64be18ef8b60e4d2c7e5a1cee0176e549f53807d1fe471ed4598ad37a07ba11eff41a4d1ae3d96965ddc8660a9bd353fbba8e7b46789da2e8ab6e279113db33586fdd863f8f3021fd211a38fdd1a4fefaa66e66db4f5dda705ab4d8ab4e2c7819a9f4bcbfd53cc01966080febdffa375eb6da3744ac8956881c6a08b8d0420202ba7fdd7e8cada76573b5583323875ecc1d98529778a330c37274c50d26a99c4a4a127571716c4da1aa797f2e413a05f50030d509110d7501d7adae15b13e6b13ca979c693f6b1a1dad65ff05236b91800b7bbd30095da60e11a837ba7aac4a4b432bd797d3a8a832b69c09cefb3430b3a308ea4c4cff8582df8230d658def78a919228eac502e6b1b7d4f1201bb2259bf265712d8c21a9bbd12db467f42adca53c0d219a2083bfc012f8a12027e08b7925782e7d9f8e2e0b3817933956b6570b1853eed4ed59869576dff07e568383e80f5f18b0d07313c6bad67590f1768c95ef16d60e46076848be46b79add5f042c153d58d424545f24158e0e27d8d2cd5fce6b54d1e2b70a8db60c7321dfe698ff2353a3f3f400da28611a758b1ffb7308800791f6f01d1b7cb94b5ee6d04b4cfd6c84a1186139f60cc6436decbe86273bacbcb1ea132646843adf235018d1eba28360f6dc276f01f6f480e1a77ac975e8298e02f100b566224e4973c630836972f22f5977dbe6d5a2797962f75a92f83d7e489b9040cd10372921ed2b7a20bfc094b9613601c43895dd5abe32e9970dbbf7ff77923aa2f70e5b58f685845222b4791556d75f9123431a2b80d2d96d53299dabc42a9e0a38856a5ff64a4a5ef045a822e9945a10657297fdf680b0d52ca976e793397068a32a469b6ed2781655742c9c5716e1bc7f7a9aeb272411260a654f575c8a423d0ecde676df5d23090422ac0ea762bfdf62357b6ecaf7b73f93621457890ff51539b8bf36a21348e488901a9a1eb620dcda3906c0b7c9a8b194fd0d841b19c112ab52766ec88f3458e55c6a80c7a6e4de8d3d036d2e03cfdb59040a9d9e151199df4f1a2682c6b36f29d9222cd61a1b925c463b71d59a8c8e5debb22cb161ae7e3303319f3ec26cc68c821a95e671bca9fa6dcc68b541f1c02e07627ace08d34dab13a991ddfbfa26ec907780d9d1faa13b60972db886e6d33f011c7159d9902cfdb01da06088c67f5983793bbab51124f7eb44e4fd56106e9a72e1c424bfabe940a8421d694418b2943b5103416ad497e5df89445cf391393cedc393c1424e5322643139044a47371e6dc38d15a032fa0d27c2e7587b946c24aeb1882cde431b26bd409f191722177eb59329b276666a7c48796cf62c5acd7ab3442249c0d72d2ade6934cdb68b879a505a3b2f6166bb7b34b2b319587413d22a26109ae16afa6e4f690ba1763cc2797f38d2c96f6d73e6e5b4a780a4a0b24dcf8972b6c7581ce31b61b9c8a59b6e7b256af854a85c4e93b4014eeb3ed58988fa19252784b3c34bd818c6db5f42340ab99c9e4359ed5d8f7f1ae1be788c49faeed87666a9b159a09ecd776c44172195d178c98ba267d8be773e5c343ea13174d481a3a045c69b90af87bb842974f913eef7a11a0f55a63e42915420738d02cab0a4ef31e9a8af998a5ce949c45aa998c3acc91d9943ae0957275b8431bc16ad2e01e5eeefa254916b7d55fb727b561fb928a1d630d08de61c0a6d7d981370ce3e12afd35a93658fff453a9b7a16c0a83b3af152aaac3e5a928630d7e985e6c8026934a088f9a948ff442cfb0d6a136d9e2374519d38f6958784fc4dc48b224a4000d09a2153c3a0fe83803756cd5cdd97c01739e88bc1de0878f51e3364456c78cfe335a96414dfb48acda7332d641981ee55fbadfb0e47d3247d0a473c4d6a05a6f940ef2226c3cf98314abe7e261c4b1e5ae742946cc1a15779e4bbe35eb4e0c4626a9570626d618de48f7c80a729ffea702603ca198e584a832107585889b387d4a533a0585a114ddda42ae025a4a9b071700b914e85e0c26a2bfe2c2abb05e7f4f1d8e755a10daad59cdc175cf328e1856a233090589f2fd0b0826ac16b9a0243888da972cfe3c682dc08b1df47776026b099ab3690eb329dee348b81cda7204144c25e78f8d1b05dc96e94d912879df3442c647d3f9d2eeb5678c45da7e63397a31b8b562a1a3046a19dd81387c517178debd255f7a034b629336227fef6df56efebbb40e08138c053991fe62b52ce56bf51e5c2d7543188142305520e8d1b998929ddced2b71d7c2d481d0b0b6a53588955d2418c430768161bd9b883c882a1c3cf67c4d43924cca68d9d34b642f79b98bb03f96c62cfee3502aae5b216497bb9e5ee590ab5edb1401eab11265ab3e0edf4c44c6c2343d2e14172d8f0d5d4851cbf7b01092855c72e4c05dc6ff02679e4b01c325dfe12a4c0799655f59d46066b85dcf0c9cac313e10dec2b01ea3b9f53cf2bfa0101bd4c268ef58c925901c15a423eb798d7d20c652c0a62fa376902cc1c2959f6ea007a7d64407f5ca107a5205e6632e3b10cfea9cbf62c9757b96041119243050e57c7e5b964beaa74f1133e8b55500b15d8abc3ba31b963acb1bd9a7349c60db65d1449b949909f39020eac8259b0d2367d1c23c2ad29b9760c40efe1f116e0c0aee243eccab041ba3ca8d1edb36b831fc91d9029315cb6b3f9413ce2c376eb12cf507c734688146261ba78d4f001dc7c3ce3f5d7abebfd0c8e07b8c074861d8024824bf260e748580519a63397b76c4c1d61f338baa53a2b26ef49c023c36f70eaf7e6f886063b7b740b8b9db89120a553033c84a6aeccf423346757d67a91fcadcd19fd236c9f10946d163f3c838e516c9cb2818826c847e3b5899adea1d19f3d2ff3781347750220e0af12e3337bb89ae7401a91ae277c0d207be983451597a22f4233ba49975951d2bb66a5ccf4b02eb6041871d7e5e0f4385e5f4dc071203f32d3d0ad6bac14ee2860831dba2167f60945e09f381d9eea12f5046d896c88c93a0eed3db3c72185a4b9e2e49961c6511ec2d5a134e044fe57c25fac235bbfa5e2088ac6fe00c1e96febe67d1fed23494e121c78aba3ef50652bb17ca8bd92a2b05e8fb42c700206796d493bc07a92b2a5c98d80fba0799acd8ed2bb9c8c01c84c439158550cb415ab15920759a987cf47a4502bbf6da594087fb5e1031a2e37891faddbf99f2b0d621062e57f0b732614e64e4f66b3af2ee495392af93ff31ce2d606d577cc1eaa0cab834756ca286e6c1bf5a9b056b6fb425943db545b9923b1704c911bf8bdac81d02d65d2be5c6bf15037bc3bf23b4996b58b1bd8005c25412b91793a65b3a630200032bd37a0a6f2ff38470fe3d57e0ca3cbcfab9f2051325614695a62e76fed856f6482aa89c5496df94376f8ab32df34c1e953cca8d31674fe54fd87340226ae1258136999ccb814192c9429c497c49487b3273364fc80c430b525607cafd8aa10bfd4e106033000000000000000198073d01f6a37f774c29777793be0f2047d2497fadb9598333221384e9c20b90d40f4d554a96451462fab351c5efe0d383684ba200317c64c4fb6e68af11b1ea99c7a613cad205296917e5db49ffcef33ed9f837ed932686bf513bd75ed10f0b4f21f877ba8e7516ca8f005e9335fb4534ddbd80933ac2b45e1afc813ee9711ff78dff2e5270ac148e1bfb49f082663122d32dc37da06ded58f5c1c40f9a1c5eb5efa415a282fb12d9b8a79979695231ad6fd2d9e2bdb2bd1fbdcc20bb535734010e3e373875ac5796ea14a2bb60bc96d79f8913047e69301993e615b2d7db14f1cf68ef23d0f5a3dc651b13655c4f53b6b4b32471f333113df25c0563af6e81ac25ee54edde60e9de2c5182dca97cfb5f82240946d592ea34a733db513dc324c7d4bb36128e9272da71ab75f63602290a23038ddd71e50634f4cdc65fdc008c732f452ce0f66b543ef3707218d3bd42ea8a6d30ed61632330efc5f5d33e693b3c3685914110430edaf86c216cb204a8c5c32e00f5fdb4b78117e98d9909001f3ef91d7774927fcdda88237d3e21d124efc6b5e9061b65ae0577bc0e76548ed4440503012031a623029f6bc5241753d5e669a252c3ffa683719e246f9b3cd0e91b5fb7c438eb94410de040990d7a715d92ababcd3f940e2b3e5b754ea2d4e22dc535e899dd8347d886d0c23079e9675942a60b70bfed853053bf42341ad39f753786669693caacce1a4b09e0e99e92fce4dda16b5b494690bd3b4d06ff5627bc2e937eae4a9189214fdbc4a5fa2ef3a2d4b4c1b84118a8b11daa429672e316709a9ab90794cf3fb3657bba49b58c6228dae8dc12a8ada905650e2d19bed62df022d58bace41ae2bac6b803ba8b9de0ae793bb4ffd403024d51d4ca3020f094105fa85018643e1989eaaa6ae20beb60d20b459e2622458cc5e115afc695e3ca6d44be8c86c5bf6b742c8c587e64aa3b11bbbe34a3e74057ddba5531bc776a8a14a7688c82ca391f86478e8035ebc25a1abe1f61b3101a4fb25df3e7bebd1d013df6e305e15932d3e3ec789569bf8003256db34dab3e40a1cc603519f9e32fb07fe6ae448127750b0747729c194140273874d377e2c5d2018aa4b79729163f6602ae6dba3944c253861d8827038a23f87390644fb202a4e4ea95425df065d847723019e2c9df0b9ccaacf7a2e09843c196bfc4577113d85774b99f6d22dcecde92c5dad8abbc8512078a425070fc957d707ded416c0e24b4e0d19d436cd57241eb7975bc1077e2fb000d1e383f397cdeae7aa9098830f2132b5212e3af71377dae78b9bd01dd2a8f6ad52bdafdc4ea156371d58fd751b1a15f048425c073288e08cc0603a8abb8cf97e4c3a17ee0430866d2ec6890710b77b02ce14e2610ed27dcc96d123ac39d6d766ea9be303e22192bcd972ef566a93a0e76ecceeef0af07f4a52256543889a47733e29b39df1822633d0d6d7df94a69d9413e01d03f56cb72a5fd9b979f7773185b088f3f9c8e3a32a425943d8fb298aa8ec9c267dd0f54e84bac7d8a5973510beaf3f5ebb7a04148d5be5a94c9983c709382672280199ed8a981390bbc7e44a78ca7704116463035f96b3cd0575427e90a7fb1adb60b288ae17fc7532901ddacd61b589488a46268d7ab6cd35ce3384309ec9166eed9bf1c2c615d1a5c85f457cc77d8eb4cd0d62c76eb5468dacb57667312edd79b70f372a8888fed6bc938e2e03e00a2b98f1cce756d51dd1bb9a58cc9342b6a6152e5914d1fe8ed7a89f105c85a2bcc68232c37b6bb8011e48736162448f7063ffa516260792ac3dbba23680874236189d8452da864eef8670a54aad3d0e1c7639a1e2660df5ebb2d558115d7019459398400bd946fc94c55915e22050e708830127bed1a325ab42a2534cb6eb6b9caf35b3256a663b5b2e5094022ec79b69119ed05f79e21fcc0ae56b68fc96c3f44f9e25415c84ba5ca2a8ca1dae3fa5341aaa8b8654f00ee02a488e1fcbac573f728ddbe9ab52fd551b1e7c70d9031c3c83474df4a96c2ea407ae1040846f60ef067cf944fac0a02740722dacdf6bc498bdfe853a02058e18d2e9bb3cbb7dddbd5195b2d570e4b4dbdea3488d6e19f5fe4603a720c5afc73be9cd3ff627cba81e836fe460f8c3c09538898ac928c4dbcec037b6cbf55288050d35d39f998118c28954af70c13e8dd763655d94f3b21aa56fb35626cf3ec69592c3f8848356a0e074d603ebda5c6ed2d669b5bdb01f01354e614f5d847bc7abc34e32f52129481206658fb6062291c975ac1bbd82f316a3ba94d7e7361a1b950d92f7dc7ce209806450e223ccbbf2a13822abe9b1148d1b0dcc646df726f64b25b209b9ebd93fab723fa52c3bf4c35fb90ce659fb7adccc27ee12988fc818ee0e902001c5558874a17d1ec0599d93a4a3463349990589535bceeb36c79f959258e3aad63dc6682542ce6139aebec5704a3d8a706f2a727754aedd879706fc981561f6fe52172083d168735b2b06c2c8d4f502e637160db5706f7f0186ded68215840bf1e2c2340ed0fad731c522d39dfa3aa8f3ee8719b602bddb4dc7817b125474ef739f5260334ad79e9c3d0cf0fd4f4b6d3a9c3f16c0381af67378f7d51125bfc537d025d3bc04dabe91155bd7dfbaa022bbb88513cfb834ba7dcea665fc934cb5b388c6f7f671bb24abb35819f91edf4e929004629d8bc550ca4f6926d8bd5d614b9e484f5fcd74173e8ea31d773e2a01025d4f8d7fba1791d03c832a40614a75b6cf8c95f531e9f0a2eb95a5305d271cd30fc351d36ce3c032982ea4db16ab47eee42202ff28f12925be83192e5e75e61f73ecbcd41d71e3146f9cde8ebd0731d61742d17d4e01e3733d92b5d4897b2344db30030bef93c7b99a2ae0ff5755b1043e80f99534c485871c420e0d23d0a8ddfad00e55c41029de953abb18b186b85a2bd73d6b20a76562ac289fac09837a799244131dde02736112e391b2a59662cf6f86b4cd2d23b43512cb422917f3021d5079318051bccf77fa9d3061cc76cf0616aad67b15bfd16eb0f7bf4f0a41ccb97ce124009e78b94bb6130cbdc422ca7bb9a9883c66167eea8aa0f7392c792dc7dda06b956cb65fe3ae31f8def297ce0d9e1eef735b85e61253e0eb365b115fcb956218dbb09511bb396081c95e680edccc2b4a04d54283fd43291bb6d393454fa7339f7b029c67e76947d96634e4396cdadbe84730ece93ee3bf5f0eff4ac5b17c480436abc241ff7a3437a3769c2a6b3eaec08175e3c5729e541e05d2d1e1e3d6c04e95351159674fb25e7f01a57457fd0b5277f2fe7a34afb5fc5903d4828016eaf7e29cea0dc1d21926b2fbe45e32e9839a7023ecb6aaf46ee2ecbd2d76673a9531b611a7b460f2d564b4b384009268fe78178f0115def0815fc5a546f7ce326f726b33f3d53dc9fff010bbbe1b15469d4b1c0974365f9b241f8fb9d38894e4cdb2aa535cf23ffa064bf88e0b9369c5f34687ad5665d571e72671268268c5ceb688e03a1bee1c0036c3ae6a665198ef459002c2cb9cfc30e526fa7b4e2e682c1b62a43da37320fab1f630d7be8c5b35022942223c6dbeaf12738d23025d2b439e420b0967ec88fdded39727a814f83b9404d322790a361ea3b980038cae607792b9651da67a737c226371120535c2a2e2b98ba4268fbe9ccbd7f944a9d43417e409c7ad6d79440265836eb101cababf82c0f4d2362e64800a349f9d029ce969ecb341dcc3b4da07aa68941b96fbe18437a16e721a396c807aa76c1f998dc622f3dc1bac66784222dbeed23252f6299aca326516b5005853ca30473bca5eb5fd9f52750fb0e4c1ee5242b677e8fd87cf4bd9bcf0f5f719faf35490a7a8d9c7e87d4847a533470951570d500ef66b750f0adbed19b6c5fcd425edc7db7910de8f92c8e72235c3018128eb27b6d8259fc119f3227ea1e2ab7ca24aaa93d77bd5ecfff16e723cbefbfde958a35f597e4157e685fe7789c92dd4755b84103affcb4d60420b9a154de5379b344415399b183bd422cbc68e3f2a65e61759412d31ae2e4391098e64860dee3a81381226e87fda7a617dbed0a7473844d7c01fc1a3e23d13a163cde988f0a8cfd41dbd065027e4bc1037b3b6b6dd1e4e91a02b97ac6be137425e647c94d49a8f6384ec0ae89d56ace0a48a5f012758451a3b9dc1b722f1f0401db7cb91fc8b5a30d9b63fc24263533c71ff23488e823884994212e6d2cd16e4ae4d4f91c459351981bd859337dc8953c1f00702f4cfe94967bc62581d35e4622937f835ee66f971ee5a454546eaff9d90cc3de3bef208a7846aaf7b85e44f474ef51973f7881046403a87c84855fb85e37e7c243c3674b5ed5f5bcb59f80ef154c191206c893142b4ec0b87380cc9d9526a9e1940be7f7925f41babb8341308ac2d4bbfb34c893c936584d3bd2a17d3848712598d91ee5f43143e8a1715f46fef826d71c1df73891567c5d476cabee0f0dd191046562b652564a4fbb104fc128adda0a9060b5e195445f04594f9d352c61e9c7ad73d6f67078eea9d31cb3391c0c72296209acf91b10aee4ae0d7b49c40597e7c8ca9fa36da17f94d87fcf60f8857a889640b40d8682bb27ce2920718c593e3b12116f0ef50c4e7a522d41218dca1877012b4c7786f4f1381db68a28e8a1f6daf7ffe285ff40fc2b00664569f5d837cf34cd3df3bab71651e493a18539fcd83b79bbc4b61efea1fb43f6499c1314e68d09a572e84c6812094933465363afc2af22bb66dd0fa412f2ecbc1982f073896cf0c26ececc13d864b07519b214c38ea00b55253f35190e263402bd6544e224ca4613a9111787c7f88d74fb40795d132d63fce857c2ccfcd12330a1b7d3dd76a8488efb4e39b2686c1c07a5b776a1496f10de7417fb4a78bc895433ce25a0f4f05acc5e6e3b65ad5b075d8c13a7524bd7faf179c92bc2322574bb01b7b71ce1bc86be62ee1e6c2960593f131c078a153dbf64fae93b47db3c85fb57507e645ce96389f1ec38cf4df460170b1c6391d1dc79ecc0bd653d5d86d10c06aa091d43f78595ff434f8e89a2e8998c0788b6c01c4003416a6a835d452aba7dd95311a19fd925ded197ba6f19a58979b32bff5fb2f83848f1c7c0c303f2df6335a9ce1899e5a62a879d2cfdb8be1d5b0aded6eb213c32b8ec7dff63f8697316dc66610b4d25b8a789954d8c4d7a610a0f8abc83b318e679af81e46f009b3de26edda1f40164c7645a1321440f9fbbaae7f39d179d156acc0be37270a9dcd44c7bfb739d645ae6efea863e79563b01d4897b48628b5558f62850ffa0a2cdcdc074196e6f0fd124662afb87526da17f2abd9f13e4735ab359f9e47803987394c1717016f724c323df369d0348ba7e31a0c1495ed1f6fbe83ec48e35ceb3fc1b9558c497c294d7ac5bbaf4167c287f5c8246807633b17ce639562744c58d3342d86c89824dfd216ba493cc25b7ed068dc0102141b114d92d1a86810d8add066786a7dc9dfe70f473fddd0e6ade0005d52ca1cf99f28f2757db164de0bb833b63800d643196884e7cbf581a419970fe2933cba4f78e0e427e1b53be214e7ad8a513d574f012868d9ee01c483c6a4368c9c79009ff1f32ca55551ed94c56eb6963a7e2bdb059b0b4be93e801ca703726a4262ebdd57160380f881fa2a2ac5e52e5d8b446cd2aaa18e275954e8bad1ff3074abba975223668df251062fc10537d1e4feed5d8b8ae6ccab3ef33047cbe313b5a43c8879dd6965aa1c26d3d69001e21c249af6c30f66e18053bd7aae86b7785b3903228163e65d84ce96afec60cb2ce00de9a79b23d095231ba933d4106e8ba9b99d6cf8acf8cf345a2ad31b8aaac06591320bfa9a2d3be6943494f630200037be7736591b2ba8ca8471fde566706a0109b2f742d4f2c347f87fd601229f181a8df30e72c640ff779f01936e968b17cf140e3a1fced8dc69456580ded3a28e010ce0d49a80714ceb55b748eec5e31ea1a0f302b8a34bbcb60085baf562643ddfd4e10603300000000000000012f98a00000bd5d41c0d353354784d6799c94be5e1e294f8b119c8e813bca0b8aa2ce663c8ff86bbab583ea25a99c425dd50198e5122e2ee933e3122d8ce975c357acc282f64f433009fa09e000689bfb06ac837e6ec4f441984d2960a0a36a23510ba3f3b925f3da8b36b0e270dadc8c18e3ca81fe4cd782a99087d34ff3dc4b89dba133915f7626d1bf1ddc2e9ab4dcc93dbfa43658e20badbab6d2ed8f46ff667ea549c46b5b15e244b513466c361d511f612c45486d5c0a80c3190c39c27b5f42782063c0eae70f0009d0e181a603343c858381e7bdabd4ba4a56fb8a0b9c8942663eee02072b09f59eef3d6ce60acaec61ec14c604875210035d4e4251e99ade701706f3521e25ed9c5c601d8e2206ebc35c8bd89ded58a3874253962a3302b2fb1b23e8d73f3c0666d43ed5c2486cafd01327ad97c36301bca5f2d0e4953dd146991c2fdf69b6d8499c2755070118529ff56974daf3718d2f630027d4f76a2cfd5fb0f2204986abf2d3b5ea823a276db1a20a00345631c014bba065c49dba21348ce9992faee928530027425e4da60e57b91fa0219af3d40e08976f83121d0704dc100ac3c4add8449c88fdc77e5d3aa787e8f156856299e935fd77744b083ecb6ee8b799c4a979f44e131a2266ab7b4210aa79145d71d5709c2ee2d016266e4d5eb6105d36bdd80b896142383a9f58bb1b6d64ff57810bab9f49ad3f3ea42ef114f55826ca6f506bafef836b781ada2465820734ba3096561d26658e95a028ce320224fa03c97fde62d6281d078cf1c6f492f5526d3b3e4dff3904c0665af2bebea47bc337a16da08c827f28e4e1b9193fe37318f17c4f941c5fc7c0ff624236398438334d3855faf876ebe81f22e5f45652bd582053daaed6ed77cf3fcbd20a13c9ba0184f56ea01fc454a679058b1125fb11068bf0066da17f1cabe7ca107af08f7c35f0244f4309a52f5506fb72e8c3b2d19131ddd63cb723588dc2588a572d1c64ab6db9e5e5143136a98749c8dc996c08502e8ed92990f5bfbe22c08e3dbb04c2000ce147c2908c5e7ed1fbab86a3c0d6efff499ac6052fa8bb038c0601c609df9daa6202c1a6a1b99ce3b149d0a6210eda32ab45aeccc8c070833f1dc0aa7bb282edbf92e1dde26c401820d5c1f0876d83507fd8f3013ad3cee46cc3c7ee8553678be58aa1593131c08d68dc8e74f8a0c2f859d40f7f1f0b61172ac32c7ef7d1c99d6110e3f36e651acdd5f5e1762dcc611c713b41212f34a746fe980506676f4c0214bcab019fee3ed65ba433ae3e1be6ccf95b507ff7ae8e2f1a3294a4a7950b77a51acd51b0ca33ae452ae5ce1f7a8b6ae096af849a0db06f645f42a110e67a0acaccf1def8732f719ad57609bd04eaa4183497be5d3b3ea16969a5d141a8676ae1e61080a406590c8b8afec30adb798a932852208e897c25c636048ca5d7016b5ea0837d005bc6ca2e740063a7f10bc8b58574e8107a14ff61684b9384e00417df16b9573eab2158b2c47e421d0743dd6b0f2ad35176c5ac2b2dac6f4fffaa24e167d7c4ace840b9657bfedcb635fa850947e0042254ca005da25a46b1b98f976a81ca9fb6c999c65e490dc425e7b255575202910477f29bd9c34d7fc364f449949d0cdd860d62bf38a255966b4b567b6e37ec02c8b2e5592e2f681e0fe9dec7f4ab1bef82e328a79824f15d1a721b5540bed11d80fe5c278cda6a69bb13e1e9f086cae88b3e529171613bd5c7d1e4ffedef6d21f523a2d9bd2983d811b71d8d4ef8f07131acace120c74ce4a1ad3f925a90817a3591dcf996f18434b189a86eae0409ee815a676bd5644395d695e76939d8df803a3a34df7288e5752898adeb42b12781387e3319bdccea75a969b6bbb2a6628a0f5c0eb67970570984e9f066aa46d19f9a56783e4656d419696c162946cd5f921b904408517faa1eeafc8928f79c0455e518292d671a38a7ddb8961999b74aca5c6f6409b54c1712bee9832b7026c73a10c7d73b1de030caa04ee2baa2bf626b9eda1f92d1f224a2b8d810ccbe1fb24a5f2d87f427b99619a89d737fc7be1090d8aa06c52ca7b593d975a8921bd596fe211720556b1ba57d0372491453eff3676240ce16bd75c28bf54dc047c126df5374760effb3bbb5c38577c5944e09b0bf47730d044666112872f8f8675aee7b67d229f0b626fbfc7f5eca429fe9cc415f390abfb0b48e54468d55062b13ef0ba01ae055e79b653a0bd399362d0d8a0a49581397ae3cf5dff95f9066b94c6e7d2197e566f82f1194c748d0b221da36c35b4b727d44f9a772c662107f4aa6af69e9e841d54109d0e8a07bebe4ef0410d4488df446723562080d398667100028713152f00a67a00ba1116522fe98c3fe5bd453570fffc274429f69298f1adfb81c0a9809f7227e609108a0486056a71616b2a9f086bc6cb4e7ea0dccd490704ca34a709cf64137c323193638cc81a36b5ca770a29d2c9ca905e60a66e87b9388ec13ae355afec3ed6759061fd6a4547ecfb8c973c1649848bb24062ab0f95a7d827635911eb5a58c598ddad0c764d7e2bee73d2d16058b44ad07d954e22ee5ac27edfcdd73745053ddd94ba1c601e9684587fbf377a9952f0721f0bcc301c7200d4ccb7eadf9b7a536e8276830854e07cee8218921afb32fbf6ef595acaeeee67552303bec592888c24635d6da179b4f1a2e60aefe24b7daa000b81a4fde748e27dfe7812d267c5e903efc0e44020fecb277dd59f4b4b035e9c68347e4afa6bc65c3d90be7dd9602956331d79a07b4e968cce34c2821cacdc1d04f31c8df196a7f1c5778fa63dad97bd93c8c77ffe6237149311cb0857d282a0607d2e8c2a22afce9d8e58e7ffdbc4ba54aa5ada945ae93116c643adf6beedd4c75afea8f497427730d0639f0607cba7cc14252c105058073fd1a4aaa1d334400ab67e8000f2b98bf51cd21a76bbee4c5c646319dd66bee51c37cc26bc2b5176d4a1e7f3a773f814b2591b0792ab16992b8140374e787ca3362b231d9b01dfa9e719831c5e13fab6de0c52414257257cb9a98b936802ed197bdf32fd218fbd7cbbefa612f3a54e91e7dcc431d63b030236cd19a26b46c6939ba741cf02199502637d80bd8867bcad952e5fa1d060b6a224048b88bd848572dea4a16848d35d9e023ae0c7c23111730f5e67199f12c623013e2517dfb39f3ad49332e326299da54e8b6ba5b6f365adf6f7764cb7fe4d09eff2189e766da92cdf5a103830d8a7ff42779ec3d5ec30f5de61c756bd17e54413c6d93af2e1170b253a95bc62cdffaac6309896f7271ee4a52df82dcd0d09fbf1b8d3d3c1ad218dffc285e229efcbc6402aadb20399533d452058c4df0dca12bc6a86af33f01d68a1aa15637f8658bd84ba5cb794f4005eba3d41db92a4c376873e8409ae9b8206843a1b0d20717cd045d3ac262d9c66f4f8ff92ee57999a9de411e48ee6b979d738b897033092f873626966ced3285f90094296a4c4c9593233d6530743feff6b1f9737fcb2b3f183c5ec4d0807da9554bf839cc2dc950161d51a697ae6734b1a0a7accaaeba53a101bbbfe0a7516661a2bbd5fc7df0fc2df04d0ab1a0a4a0f0b0663180b66ed004828fdfd0ae711e3ddc6caf09828750f396b4fbe3d90c111e28ce84fa227c12295d96b15f56b70aea3bdd34027262eb7302d8087933a8c21c3d9fdcc7481bd1a27407f5e0789df2b3740ecd1c91b913ba160c2cb2a7e1f4775e931935904c46992d5f2167c0eee7dd78260337f5bce10fc6243a602d61bb557fcd6ce10f76fa7057318599cf29e3b439a8a3efe8cdc0bbc71cea9bef783c1e5614b3627ae60a663740dcbaf2e31e52ec713df9d248db2da8cd02a1dd27fde69d59ba23b4523c21bb5802ce28ac03f062f086c48a30518a8a42bd493ea2cbdb52b0a7c626371cb93e8d3ab754bf6357120427c454313926d6c38f69facc4ce6ee3c02e0933c3ee0a9d4e0048afbbc0a98037c48cee5cf37d0dd6a6229b16ee98e15362839ee0fcc6447fc67536135a95bd23d5718cc94e4446fa35abfc88849d9be5257f79e7fc1463f7b190ef9e8226ffa106618a0b3d51f5b98b7594cff74940d341cb110b4426ea29c05a1ec03df0b2707c096bc1ec9b1ffb00fea4b94f92ae485a404d737006fb90fad88adee8fce4759eae0bee9630c055679410f087b892e94f68867ad2dafc3e8a819eb0da6a2f8728f39490c25ea340536f5abadf0f34d0ee35db2ae0e381e88ea89c0f9cbe782b9a487a9ed6c58989799943a4c0b2cd32377393f92a976eb3ecfbac01897bd2a04962901970c11acf3546665dcfe07c478fda7fb5c031bf9fd27bbf8da6950e3a0a0e22e7b54139ff5ea2885e3edb25a93e3435ba52798c5b4ea6c3e9e1d4da373bfa26a1e378229f1671c16ea4a31b31c96c557e1c3290caca01c9b5af61bb9f5f5156cf6c5fc29c477c4401b2d5ec4347da0f97b1a5f29962c94810e4655f591f54b4272b6b966fb2050935af6d81771344beaad24aecd8fc7b587871992cc5fca42a5893c5b5c8cc725ff92d823cc82590e1805eeff2837d525c5cf5d8b5f2b1b8a86ad6115672d5c2887a1a77ace73fea0bc89045ad95e300b487d3f5d171b8e7cd807ed433491c7111fb15e7c27bb1f89e51ad90f28e3d874295d87a4087a71e4822e8edeab6b08487d40bbe4eccd29dad8c7a091cf5e018e64320f1281dba81be48e02610a3e7758244661d16b93671619372d1f7a60d41b4bd2d7b9290a0ecac1250f7c34a6f4b8733fac9c1c6cc1185286c1f1e0389e70fe44e4352c7d875541d582eb6ac056d07cbc2b6d4e6101506ebe9460c304a80aa52b61241243c2790ba214b12a3fe48373dc613ed898fa36e52d22edc787ee3e363b0c7bede14e7ec94902689c0f9dbc07c04fcfc20982c01ef01bf5554767269a6aef082cfa8e75404ca0529b9b228e8a46b5d759c698f7e05177149c06d2b35d2afeb3083f32185ec198af1c5f6e7d3ff383a00334c338f6ac5cf0d5ddb4d21f1c4045a0e99413f66f511dc029815f53828d510bef8492968d4d89cd527b2532f0cccf6760b67431b0753a5e5a892371b264ea5d85ebca309fd18e0f79e751d5248d223abf799efb2b3f73a95a6969fffe99938dca6587a79d87fd9cc9244a8b99f5cffbd34c369fe2b8b3f602edfdd79ef5553b65a812335b66700f6bbb4aba3cd74aa52f05bff763cb13771038b3f4e481a601182cc386750bf91ba6e120750b99319c7a600c18a90407b71da5d59f324605713ce9c60cbaa0bb197132a35b1df94fe052763d92b1ee191cb247db9f9b11b650d36ab963a55de1bbd30c8907cffa35cbcbc4d7f3023d05c36937892dfc8aa1748d1b0602477aebaae696b2ddd64ff5cb4ee9efc4dfd5db2d03bfc542f93613f034627c68c818bbfa9b11fd5652cf26ecaf51cddfb640bab63bd7f19d22e070a7fcf4a3df395eb5c41e4d45533dfdafbe907b1a320bc1833bd999fc9d0dc3e0e5afff0ca51d93d06c1dd27f8a115f01d3e8247fa9b6329edeaa51ad1b968b99da66c17e8b5adc46e2e20c3ef52c4fc345b21bc9a9883bcdf909572e243280210ce98b9b88e5232e832848a76de47dfa62184a85f7e519dfded8486591d97fc1ede7183d839c8fa62aa4b51f6627910e960dfeaa22eeeab9e4986bcf68c7e7c5ea303b63a1a12830975ae8c193b7168af521cd2276ee08751342a069bba903e9e2a2064338bfaf81649da05531a1fe89e8176fbeb845edb717399e8cd7f76d3cd92e3d75497bd9ee75254e1a8d58c40b2318f55c2f457997c0ee15ad24c787152e0b319247ee86a713831482e8b7371141ec18513c6a95f4a02676a0bda26dae13aea65344ffd52e630200036b03bd8105030ca50c875abd786ca17adc6b36da14ef7febd25e176e7e62fbc89083e17437d9903f83f66083dd47af0d8cdf905d570293bce4796a10a905b76808900121fd1a6c1c61d1eaa50725806a83a83b2830885f2aca8da225acf3b69bfd4e1060330000000000000001e0b5b701508907a0ac40deabcdf2bc8c7cba265ca6ea447ac57ea1b1569ef26120c8a218cc6126522fd9d2104b447f7a170f19155ed2096f8a5fde1741fcdb9525b9bb52fdfb971a0677bdcbfeb94c79eb5d2891a0a3ef3e6944dc1395a3cc23a61e4e7ae1b6eb022a2b7d80d2d676efdbea03f69b75c8b98a96c7a9256bf7c3d6bfe76e472e38e295c1857a0547e2b60725052ee393df384f033e5c74843d1e7c7765aeb3ad8f80ee48bf529353e1bca2b9b63295df3290d179cfd94e44dc0095237e1abcfc5de614b28f6d1b8262f96d3cb26a7fcdece97659b373cf7c6120fd1a0fa306e2911ba58aa1d9475f5ccd160eecb31268e5803899c141c6b99e54fc3060a4280503d651a94328c9d419e4bbd57590f04a36526831d012ab27053261d948df950536557a3ec147a16d32695afdb9499a055a591890d17af50cc7cfe3650d3238f2104c43068c3c8809492b2de38376fdd707c4624dff2edd658b186ce2b7c53c94e420cf532174836fe50484479bce740b01124794b319da9671a809b957b58c08b9beec59e764d994c464022a471496f7297a64d0fab8e95d9aea2391d024d49bc19c3fe449bc158066b3bb99a8c3f95bde4f10e3ce50cee386c0cf7f867c5da3ba50060646d9b4ec5676c6926a2822de598502c270b90a05c946d9dc15cb1919286cf54e271cc411dd7a6ef5c765104cec7f0c2d239ac8d577767224ade773bc6bee344a69b1783097f2305faffc5726ae85a1a641827ac581ebbcfb309faa59b206a1f14bf36ae8f6394f942858554072a620eb6c7dda109520acdc2749838fe0fe599ce0cc0eda9c08ae47107acba48eed023e0fadfb95225bcff5f1ad2cd122ddacdb7659977b03070bd5faf9eacd0f7ada7acb48cc90b48f1390a6f825c850f44d6cade8392bc59535ed2da5c6e67a05e787bdd312c075838d3d365786e82a20c934802ff3afae930002b5bf594fcd897b02d2da762e84bf8cde00fba138336a4053d87f86def2b78de56f7950ff1f07579a401d5fcb31117cb1d243d139fd5a1a6dbabc77ad27fd5facc6db62a29f32c274cb63e69549fcbf86597338a399ecf198ed779525eb8cb12e651bd50b8171b2033d6a35c7319afe1ed634db8258d43943f3807ea3c29c69db199e40c1dfbb930c851ddce91ff50e9ff3651b2ce05675b2ff5a5549386b337fa42477dc39b1e4dd8e987aeacced62547743c5b016aecd724cd67dc7748f541aa035da8ec2f90b7edacbba2476adfabca9a9c6e32ddec43e93cba36daa0bbed99e2757b0c10e3c1d542f55519cd211a9f51dbef6f1d9f72e999d95450a9109b55db8b1b6850238a8c81b56d565cdc356a8dd2018d560a93f5cb6cce46460e90b4f93be36fc5044faf090e429bc52c348c223dbf8863a22719a5592773593dcd2c26782ce82e6042e01f5d4498ea1622cc5f82eef9dac219f263393fd6d4e47ab702618f947cae95f2e9618209e65a36827572abf5630508ab7f87503790aa4349a7b624b4a5d5a3d806684664f27e0d24c9f56cbe10cde282ebec4e64a772f4ea89669177a7135c732a30e33633dda56143c32fdbffe34c7bbad4b4d9410c8e7a93bc26b83bf3f900a55a4e3de8fa3ec483aca29a7b711bfdbdf1580a12dac63e2361cf89d1283675ba01a7439b4f46eef56537946713fc3f854b458b6707ae2cc44680e3fb05c3c24e2dec5955253ce7750bdeb1af9df0de09fbba3151e91992a714375a9cfee361e5446221c101b47ab262a12ef30977f227ef5f7002233cf67dcdf34072dd826b9950091b75027608d5bdbfd774ee80f098b8823b2bca996a04266b87d7f72eb5ab272b4745dea8a5d7f226c2ef752761c49dbcb18f13b3916b1663ab8c933d1c8b20e337e66b512805446cbde46a62aacce46330be4ae1331ed7565890586d3ee2af5fb227fc71cf112088b16a841a136d9cda79c6c9f9dbca642ca84e1a7702f38f46aea59b82897e515925e7e58d59ddbbed65de6b9cb571b2dba044615d85a5e6fb53cc456b1e9bf73d275947246cd36b07fd8602c231a1464b5f5988f991bc872c15da9452e66b697cdb4b5f8fbcf597369def719eaa3a4e0305386773502fa8dd6b9773282c5dd1431f1881bb6a09d3a7d5a271b22858c02e600b9b00fedc11f0cfed98361851cd34e1894187f40828dc7ed8f9714b12e812eaee8d3d163c5cf0635737258f587b91aabb0911d89dc8d85e0369b12462aa64f57ccb1ca197ea2d13bbcfc2d7cc0d37fe45bd063e2ac243c66a80ea4a5c14838e33060686f79af868ddab218e6d60b546b54d200de9e3f6b7fe092036948ee30dadfe708d2cf94d276687a64c03c0591188da5382f3adf0815785788da538bb4c75c3020319a542fb22432c86eedf3644fc3dcdd720bb6c83bc8a136dd5201d2c1ff8d242e5bf685882f907c0ae25c37072e15a29ff87a211bf5d9717ad1a0709c6794ee60af50d25f5b0b26dfc089c3410493d6571250eb209190daf8fd27e028ed44ca27044a52a94f7d5e8638638efbf95bf80dcacd02645a3911451ac7c4a12444c7571cf3cab55e4d655d62cd4fab864d9439f17f9e60240c371dee07ff6a250c5667fb2f38f729a77f12ed74cb479f6ac165ca4f2197afe4ad52f55a1c5f9b009b15dd24adc682bc092cebc68fcf467be1aec45df556feb23b925498d9bdeb374282ee90c9887cc636f6b235eaa9c7cda84b5e1650f60019c0fe6a3e28eebbf3523f404dd61da36d3a7c3aa7ac434c9d24037ae5d96855003cb4d4a081321fc5a1f333befc62d6442e30ccd2dc2b6c2ecba060e7642dad07a4b165480a88fdf53df22a298278bdce1f72c0df4cd6ed717018ef1a10d843ade3cb265fad4f6547481c7db01b0c70dde73db94502d5a11e234e87b12fab35c212f7a06e4455550eb897b7ba465ac439a00a19c3691d6bc9506f50ce11948768ee00cf975fdeb59d15629cc51f35d74f875a4b8dcd7a7fd6b6b28da5dbce182dc9e34eaf6c054576b69773150fc4e60cdd59195f061270d8651e57ef60559c1b1e7194de405bf60f6380179a2394f0f778b63f7138d3a03e276e7409fc93d90ba071dc2369472145a393bc357a250291b69082ba3e0153eb394fb6723e500521e3e7048e53ae82bf37064396a43f182779e9ee33e47c88278bf0c425de4ecdddad2e031fcf903b348653afdb24f6c887c875c89f3d9cddfe16ee5b9611b5b6e864cc145707e0bc524a7806872a875db04c9094b927d03907fc2af080379613c60f1d7a7417c1f6fb77227db09ac2b31ddbddb0d8cbb454102d3e2e306dd199707d8d205e0367ebc11e3684ae045a168c7c6920ab69260ef65a74ca9feed7f4423eb2575dc50a0e441593ca0064c3df12e778ea4deeecedd1df6935a84fb07d34590f449fc45b6fbaee1b25be1431624d05770dfcf7758ad70c973befece8feeebdb4d44276aa7362953e3edffe7fa99e872aea2b79a1e9e2a97bdd9e7da218094f479467f9cf494c9173694092b4fc2e2606d542407f02b09bfdb00819d79133cd614f23a006e0d0e2d2ea82eb89726085ded17a0931637c3f0c2b420905eac345758f255262b4efcf6a765974bcdf1d3c0875044754d316c5e6b0fa6b0e815afe85be70bf2e0204d19bd5ff540377cea70c9ac39c7a246efdb06328e57036fe9ce5eb6e2c3e0cf7528d581d013c6503c57e6b1634a0fcb8efbdcf7be65fe33191a79b45d7c396b4ed15895428f5e962d2a55981c0d708792ca0a68b5eae3358fa847b41963600a701c26ae788d9d65332c57446179c335c74e5a9029ff715098946b62ea12237746cfbd51fec119fcd876bb3df05ef1efbcf1a6af929d1e9fcae8a675251e04df2c31342c31c6d867bb456811d68e05fb0450b030c37723db3c7c1a3c3f40add847048eff5f9e1c4153864b978446ab64b3e8613ca7c855302129c7c2b4657634ee66679125728153ea4a093b9949d6dfad8d0758ba11aa799d395cc556bc433cad4219b5d6f80699c9e19ae4818c5bd5826c01121d8da63d362a45693271f0dff2e86df220f56d5d3ddb0911558ae4afcfcbd65fb707a80c72389f529a2d419cc398926becc44e789ae21fef3f7321494f04cd9c424922be1d63b2ba0dbb3916d2fd7ab2621f7dc3e54f543c272e3d27056de045fc7e451d7a4445171993de5e1e1e9cfe7d4c364ad779a73a930eadfa431b6d65668e1441c8024cb409c93c442e305b3b29683eab4960884a4664bf96129c41bbb7a5434c1bdbf40a32a8791f8c8acdc98c65dc04f2aac2368348d69b037751fb390198dc0ea1d8b35637d978835c21d42279c8532a2240a429a587dc5a0fb53b6d17863c2288f70030e3f78364ca26aa0410ac0824f298af09c90c073f1428c725cc90325565dfd711d0b22fcc51c3a9e86485beafdf02f8c57d547bc0ac0f1d64b518088c8ff54e1f93bb2e9587d07a71458ddad81693db75c126166d048a6a26ef103848eda1b4efe82856bd382c013ec3342ca98a657a12c6d5313b87dfdf9d62a5e42a665e53a30fa3d8747fda106db58d8384fe16c5340cf05af3ca04c9c9b3d1595070d8d4265a409a0ca6a2469468f869bcba578e0b7313109e15dc3904404977f986d8b740a30e89a818d5b517a1577c5fb1817f71dcc239273fa8489ba06529e3c4bdb23dd451bf1bbf2f6486972d1b401111d360b68fe32db7a7a871811d8cc19ab93c93745f431f9402ba7685fd990dfd7f61e76cba87faefd86d99c5510ec197a7fca4cff237de79d9b8a9545429f3e17b54be06820f3d651dd0e1bbd0a19cc83f59d134ed65a4423a00348d49b205c3d5374b10edb19dbafed8edd7f6776bbc53891c7d33dc98bd8c568f004c3de719099ef370c87f99424adf255e7c0c70dcb353aa599cf79246a1114d80a78fd1b673f7932f5cfce1c9dff7695e3908cc1c303c64176784510073e94bf09f15c6153ffe86a4857eb568c61b48cf7c1fda0b5c53d39dabba10d72b51e152b052b87abdb0d270479983e90687ecfaa789461661ff4e51676ff6b92db9f8e2e0e418387cb375e3551a87f9d5e24017e76523e6485c877126933e20ea26c63c3b58da9f4fa2031110bc0d79cb82bd1695d8b41c32786d9d6c5d349697789f00a0734bcc1ecf0da15b5b52a7246479c686a18fa02309611078abdff25c0cdcb924a42b7e73f0c496dc73973854d8d40fa34173715487de2b2c3361c17b6a5e5b21fe69a8533969c203b5bf76a3a785531b9de2f2100238e9ec46bad2a65213bd2dc292757c325881348ee154f86bd71aeb99672a7c63b5ad521423a4be566bef28649c2f106469237bed78569dafcf99eb1445ceb598a9d5622075e4e6b5f058c24338546087b8e9abda739135e93d4ff124f5255eec0f2a776cdf2b768e77f511c178e4690a497d9af829cd7852cae40daf57d9b99cf6df190a9d1d4854eabcf4e1226521bbac0ed19260091ac016bc66a17be50df890e09faa7ec82e34a92cecc32d5f5b2e2462dfd547624196e25e25a9a9fc0e70459c985b91f637d3656c16c9224aa64a491cd8d7776a9ea645511fdb7af7c82820e45bb9e01afcf0d7ebee5b40ae2a7a499041160526c988dac88054962cc74232c28441cfc91bc1a82d457716f487dfe854fe028521a0db91343e40cedc32a11c108333d245340c02a70118953f559c63066a6d374123fb46062792dc602fa04101a543c0e36343e9f7f0736053904c7f3d63cea0fd2d812b71861c5c154c5788991377f615382fbc05113310fee5f418b50eafe05f267896fd4cba3a2d745a2bb4b413c68a98ccbd34e51d350513d0cb19ba06443c442eecd5cafad53f6d2a37a21cb48913bb50feb50167ab1e4ba630200031f5d1df9887703d6548b94e9d86a9684994bbdd35141b04f10b8793cc23f383ccc87e2d2785db1fe8d3a9a28babec48ff1e26c737add23d33f575ed0e11b539a7bfe7c9918acc55a9a3d80121dde15bc356de7362c7e5143c4f7f79feb02cf34fd4e1060330000000000000001eca413005d9de9561070c7ae6f3e6b20d5ce928b19a5b476fdb499725c64f6be18c8e2cc7fd516713aab398631640fb898580a492d9c1f0ad6ef18581cd21a74466fea19aefb65d4dd7da1ec0546b7b8a163e4132680f5731161b8d9c1152bfc10e5fdf6fd4064a3b56e4819da2325786f98ff80c4838fe2fa8e48bd600aa429cf59592d80dc78cf5102f001e7054339f6fa00c75fd0e7d02d8f8b351e3103fcb0a39a978cc257c9dcd6b6a8af82a04df71dc1cadc74b7f78df87aeb5a11b05065f444ae51629feb06cdd0cccff25b117fb60b56dab63397d7fa9511b8332585362997d3d5e349e95d51f3650cea33b7c6b9cbb7543c79fee1d69d5cabd52b74728c64dc5fb660d52dc443dc6451082cea91bf2ef4407fe780a8d91ee86cb45c94f37b0785c255ab9999430ebf1c7073e0729026f8b492f822346b028bc83395c98ce367f9c96ecc386c354a9ab1cfa425813de70f32d854d83a744c57ba8133c4f6af0f5e1a7c6bc20b2765c40635f0bb073e68f32d234818c5c46d54930d79530ae7752491c7e8d7e530f8b75ce6cec9bf0757e2f2f24a8cad3dd346cdaaf24d759d70aacae7f06fb66d48c8ad4c54418d160a6854bb62e28017e8498648b4fc17d77f6ef314d58d14d3375e830b6371865c770c55838521b7499e063f4c1f4003f2da808888ce14070b6869da749bb06530b6494b69dc0750a56014d4c3d7f1554d44c8749d8414f8e826747078d4709c69f573dbd30a3c5a2634ca7990e175e22b7a83209359a96a60f612035b94c1b0758595ee39d21b21b4ce1d6318eab37d50cdc1ab9c91e27300ea039360469ec8e1c352cad25da9f88aceebf5fd24a3b27d0068707df7bf9cef87e739e665272386b4b8893f7362e42ec026cb6fee730edd3cfd30020c97d972a984f77c4d571927d067e987c2d4040592c070e7512cfedef9c8bf31caff8b3167dda3ab5d4d5ba62317fa31acf1814b46b7bf69ca844fe31eb1a658a2661f471b5549848f505b7720e09655c6f6a6f7f0439a1dcf70e4f3f557d6e3666311776fe2773bbc191b3691d92a58ae41c9b15be29ccfbcc4c4e4fe0bd9f03d26e7c2770d23261375593105dd962094e82cf13450d30844192b6fb71a752ab76ba573ad57abb260c9ef039d18f45a65e4e6d94de5a48e44c4cfe9a0002626f5dfe33afd386a50560c78d35e836e3b7318e30da390d474fdd1ea6eabe98cf42b5caaffcf21b95ea2506e3a3d3e148685283b8dd00a8417edae4381521c38956fd0097fe3fbf31c741194151d3d4c04063ee8730fc2d313fb898b62b3a4f6e7ccd576be62621a7f5e0d738f8896ff213d101a5bd5547571d8220cd5f8c18f1de5415167fa1703c67c93510ef6483ff5773cd15b7e1ebd15976889fca65ee281c7930f94972efa72692615628da74373f051dae23342ba673e673720252dee86c4b50fae384b0348a60276ab938281fc7fb11c28571298abcf8d83de248fa3c64803797b1e4e2159e914cd654f897854663db80b36857e9a46d9779e7686486b721473f13369a705fcfc6ad651d9d0947f5c8f40eae0b8c03d5f72418aff1178b2c357843fc82d8784c31dcec38c66fc1ab2f4098ade5d8363bbd6e25f4435828822690463770e1946e992ebfe2d260efb65199963f81470e1c6234a4c40d50b2579097fe512142251fe8cc35427886d8abd05af79b32c816bb938c57eb875ab90764d1000ad975e16f4b53daa1fe450da5c5d051f844c658e424945738a13e1fb2459ac9ff9e22704f6caa2e25da13c4f6af074b5921347b936f721eefbf9a309349b35ed90af2528d0b5ff7657d8b3e32834adf35bf165e841124452e6d92f437936abdf1020bde6cf9b799877798e38e3bdb0140c50dfa6f7938b15e14689607a41155f4bab389cb370880f6842add409616bc323363ecf6442dcb6b0f85fc319c480a9a6290a2cc2574fcfb08d2fbf52e8fa947e883950bfe2493aa73e6bf155a89fcedc989a2b7525fb18dac1fcc2b38a828e3a2d6a4144f24bae1248678fd34ddfc4da52aa87ee3e76218d1802a217f5e28845da3b18b6a7f4a864c09cbeac2c96794ab8a072c8a7a5de6717ccb89d90205662938dc399e7c476955a6c64857fa69596b1e22e3643739e6ed77f538ddc2e4324aae970e84c23e249998bbc9777479b4ad60c8a663f09f5a6427bdd791fcaacf76bfe3cac99a1316306c0d2b440dd3390e5dfad6341ebaffc89863b95c2800489c7815f6139af92cdc435501ca4132344d4521d8a4543e20934d28058b8ee02942c1256ae055716cd62a4a7824fce19cb3bba365bccb177f58328795943723d5c28a9d84cef9b721a3b548d0352601c16f51cfbbc32fd9d283f3300bbdad8b62a2885c94684c722cf1b6539fccfb0a43c0197117a40b92448385baf3171e44f2af2eded03c67d0bbb5a8787764b8e04294f2f9403f8b2fc0cf66af162a5780d3860f0f4b99205bdd8a8e879299850ea4daef95869398c657f84c890baf9ddc60be0abb7d30f4a4451eb263bcbd7d3da2673e317ac7c57ac58e564bda3ab49b84a4f750e6687d46776b7be34fac5c8cbede97471462e2fa81eba870b83646d29d8e6a9b5bd66357f5b71d3923611e624efee55c8bcdbc9d5274fd61d70da158a707e1d9594dafe39601c12000cfb86cf8b5eaf8f76d972818e1e941a436f62b6a994caa6dd59e40c320fa945c00d3ca2ea5db8333d2c609b3ee4294bdd21b3f11ce815f9dea9840801713a2e762b260977141fc74e8201d70c466dfe0f8246b2166991b2868a4cb40af97d82334793b9fefe99f91557bb1b4d1b9f77314920fd2bfec31e5bdd5d9e5a71f32523fe237795b39e13e7f1fe76eb0f56f2ddfd99cbcba32b4d2e7a82cee13c5a03e716368102b18e8566c27f51b6d8859f56ef29c177da654aed948a1bb4985cb6e22c58cc5e7d1c1859152a028280a7feb6a78e9647aefc55d46d0c4db2fc5b0749005413d8dbdfd9a86900ca63ab8cabe3bb8bd85d035cf0f610870cfe0ac2a11016d5794eeaf765f8cbb949d41262ef38685152f72bb0deb2454c2e2b024f2ad600c41e802c7ebdc259ce4d56d5fbfe3df4380253d85cb17ee316a9f3ffb1aa1e98e167b29aa4afbebf324ca7a580d396de68cad97c89203bbfa937e98d695f67e9aca0d3fade39039edf319de02cdb14a4c4d448f26bc6fe4237698fa83081f6b001df752c02e7dbba8c601c2a21e604aafc182cc2ccbe94062dadc7b57f29fb70ca190611a801bca7c544622fdf90f75050fe1686b26edfab0e46d08aedcf20567693bf73762c8b3284d5fb8be661ec00b682691c67435ce00d055c834f18e989c6dea9cf6a8ac8d2690076e557335199f72145df0b5bca550afec7cd1d79bf38ddac9742779811893037e93875fb8b3db14f27f7e05a0e3f1bb0da4a87fab3405e74181dc8cdf30d57c43b68d70a826765c843e88af35f820a9176aafb3efd8dca04205872a046cf93cbd9096c1e09d7d8898de1806203bdf238cc62d846cb15f73fc7cc6067a7389b5319bcebf95eaa4c71cb2855f75767a822203112f0a4b23dee9257533933761f78041b5d0703accd8c1c89fbec95658584a5d9d89c221e1001a4a957f99d8e3ccd61a83f21c739ee52d13a73ee83b163cd32218fa412224ccfe87d9eb7f4ae02ac4a45fd0a5842c276d7cba2b9d467678a3be7f13e8818fc8c1eda0636f203b7dad104262fd8bca12dbe7f4af9ecc5f448cf71cf361dfa974912ba23ef754d33bd894ede9b721cc82d04f84d44eb6fe2ef45b0e6be77f2bca1d50c03b1e71ef359c151c7595fc1a5be422ab57b4d7fc671dc5a83cbc64a259ac0fcec5939f30f98db9272757f03b1a52f4979161ee9712911132dcaec9dc06b01ba299eaedba5627dd3e13f6ae270d4b6c8afa0dd58588ec4e4b94567d8566a877b215d68911b2fdc4ec662bff2a3110631aa003fd7e87ee8bd543f6efe706649a282beb97c18d5c16c69d23d08e01e99a5e2ba2b08712222ccbd0f39fbe177ece56f0baab123acf0a3e0bdca2974b80ed3669608f9c2648b014ce2b22a442a1c8f00cd5777f7013d3f234bec87f56b557e38756177492edb5ed6aa65b22ffa9dc86deae0d9a86a3453468de9e235499cb42186435fb61dce99d5db4533065c9784e2ec8c4cc95d92bdeb56c63426a6287047e2e112ba99660871f76595c77d6e3f1c3881a4d312257263dd3690374ec562e389ba99dbde719c0f5853c646cc621421f4d21a7daae208185d31977626cbcb6428f2dfb8aa27afa3321ba4add6525869ba874053f436eed7e3afa08b4f4897f84b768fb4763d726401608e5b67640e1512d14760ce92113368f2a9fe68c3990424e56c911f3d7e02a04ffe0a63facc91d64f080f89fe3512c86b863af4ad52abf4b413580dfa0ef640edea7c74c255b3fa2f00045f20ae19d1e85eaf24c358be61c301c246e71f638ea337cf75039d15efdeebd86c27c2832d11dfa1e214e21fb252a3ff63526dda3a0b93769ddec45e612d3eacd14c781fd2392d238c1e2a69814961a8453691c3e4b1a58154430aa2a0d9312f6cb435eb83f87532ea009bcfcc27601f355e5d201f82b8a9fdc5df07fc4c395408d13b39dad1a100d50ed232f27c63582993c7c0313358364ade6c2526b0bac726adeb5480fd80fa720b7cf91db6d45fde7e1b28a01e8a66962edceafdc414b997e634568145248b48fe114e92e4ea79c5da850975c5f46412bdd356033dd4f8c05bedf3db012fc3044e99fd68b77391eaff9e603d56db5e8d8d436f5c80dae926b195d9e49d38e02ee07105a3946b63830f4a6a1ad62d261ad0dd7560d261400d310f180594f0f7cc8db3f59ac27ff77624ff478adaeadf3d7aaea0b4d34b66879678c0df8aa7a16d5796d87d1c005748c094662ca4198732259dc28c4d894f02dbeb66cc56d63bd4fd66aced2ede5ecd6493a7e88c79775858e8540e85fc61bd00c432413753fc9a10c92ec80dbe34bdb90a53a66faddc43f1f76abbb5d68cb77537765c61034b86b68e03f6f86a3fb1d94e2803755818a81fbea72d17e89c5838c07c6c972824166c2973634f6bd099bab0c528fb0c46e80d15713682d3d21ce10aa8e2642178a543bc0e5535c52e632e6cf6fa08cec887cef36ff76b83dc81605ac4f3a27f020d59a03af64966e710369a89853d1b8cb99d4ff1afc26bb512dcaf59e0d3adffacf44fcb77c31baec44c36add7779024e3c6be4e1706098f43d56d8d083430e47dd592b5ce1fef3e0b440dcfbb739c04b62064e8e721d07ac22dbf26d3e9ffc332536d4c68b8ff3176cce95f915a9cb5c00f98a1b7c694e9a3ce5e33248731eb3231c6c3326a99cd6b296d4152904ca902b6d6237b300b679693b434b23765ce5003757a4781a06ea44962c00c1444615ad700487b66d705fb11dc24f240e5df8fbdc838ded790e80919bfc03d540dd0b63927acd5be553cd2ba89a565a78a4f71cc805107836f878b9969c64d95bd334b352024395e99c0e2c8663a0310e7f123aa15d25ddbb409d11d59633905d722ecf93e128b75e03a1387525194ce99e13b57f66c5aa2832b90f8137d985179168d7235111379b30e5436e1b6c46ee56b1168c5448e9590d89d46f5d81d9b834c3740d219831cbfd4755e37b8347a0773fdf1ce0ae1df17f9564d9e943f7b99a14d71f3e6290e48e32ec5c1d781235c2bcd68ec66d0beafeb80bd388dafc8a6385785e90f8fea99d9f4e076e2d4bbfae4a6921753af693773ed934c3611f53eca0491b279cc9f15e9a54315779ed5c4d5a2d0eb9e3b8abe8d18bdb92ffc5738ce88a06beda84696c15edb6b556028446ce2e029bdd139aa9afc6302000343b1663f248dc1d7bc9b9fe1d368d6bc11115d924dc225ff85833f2c51319a858a7c696e3b2b5cf4cb12b8d20653dbdda13d0b99578a24f3b35490452cd1836b4bf0b42d729bb22a651599ac321a2e2a41e6f03b16454a21453f83da86cf0267fd4e10603300000000000000013251fd01d1c901ee60aab31431ee76531fb3dd63bafa7f434559a1f86593e27cda53f3c05424c6d34be460f33226bae6ad10de5fbe33e488f914a5600b756aaab8d61981e480b5f6921416be364a3901e11512c40b2639dc53df4fcf851bc7ee6ad8a92c1159ac349be05a27d820247e262a0ed407d2d21a0323da2bfd16667bd810e0744de41dde43c7ba09d0c0c099ceb74764b79f999e12a8426fdbfd861d7e4378021d17a56383ae2b07b4a480208050cc51d2195979fe25ede6634fed3547fc8202deaea8abd8ea026a88f81f51f62d31f52bc28ca7604b94f4f6503b802f57d0656c46127daa76e838a2537776b9fae5575fcedd9a669b305c1b0e224327bf6a1a0a3b9e51c0ccce51efab89dcd87884f051e597df49056910fabc6b5439966952da6f165847d75a937dbb22985c4a4efb4570df1aaa8a71c9c123d195f0e2694283e016b747a3256164879ba3e3f2f537fc83d4ab5e2177b52865cac7811c0e8002a673e89cd3d4ea319ac403c88fba0c473c0583cca40635de46d3ccd95a876bb0211a94413539f8e9fbad7d900eeb72d45d53efcec55fba624149c1c681cd115f5954ebb4d623cf97be7a7f2e5189a5d53baba16e22bed742f9483b5bf2888fbee5570d6507434e91efcc4133c06d2b45ad97f9153bc9f15fcf4be31e2746388bb29abca0036be44bf55cbf12ace37e4ddfb97b4ec01027c18c3db4fb69aee2e8108e8bb5c4db7d8e22e1f7ec4b1426f15ef5eece2194409fe6e8e305e8251e2ae0ea54c4683a6998e64be48140a03de42b08c77ddb2321b100532317d9a3f8f8670921c6f0237183b7b0b20287f23052996a771583120d5d7a97671cbd489a0348ad36d6b604c5778e946926bd66b651aa2256768c8243ceaa5c814c4c71df63300f00f901dbeb94c68e8f8434f774a756f27f4c7ba1aada8eaee8af48e7e858b2bbe613f1a765445637b21849ccd61b7bd0edfaccbaea6a15f3f35d722ba931ba41f7865e7268fda53c302e08964988d051daf9ac5092db40592266a253a0697f877b6693c7f1afa5aa276a83834c7d5ce35b70b528a34fd61477159d648222218f68f3ed0291157b8ab4a1df945c955a5509287d630877b1bba9a045ba5f7d40b33cf3684655510ebf7adf9813cb2bbfc6377f4ea52d9ef09dae32b5e745aa7b776292c0613878d5a04d49f871cc7f9df3178becdf195e427be6249299f6ba874cf39d0270f8bcccc5a0ae407b675ba897d2bb9ea654ee02a2bc591a6380ca85b5f5955196ca8dc57ee78871b5f1fc2f3146b928205e4e2a6fe7212c77ad3adf876296167d807263765a6eef112ef290aa433d1d38c289d1856cffb67b90bd0d3a95d7c7f19cef352500daf2aab694f636f09aa063752f3280ceb58c8ea3238d19c45746dd4b44e38e75183d1442d70552b3e9b3e1a381ca57352d57b156e7aa09fe12061647f8efac92e5c587c5c3ea546dc5ebcb8fea52e62065d6a7b9ab31645a3746be8a3e8287eddec3fcf7b713fdcff243d199034ca35e8d9d8423d3a952b5d5707de1e166764a4708aaf0eb452f79435a01bdb9d3b1e76cbe7f8e49a02fbd287b276a863dfbb29114396fc6074434c7530fb1f3abc76518af4ef2b78982e0b308cd1888a2ac4d3962c4d298ad276a158c415e647a8222f501da82f60cf31fd0f9f2ad83bba0e43f7d09bf19ad655fb0dc5ad454cf295dadb20b7346c1bae02f2e05be4321966dae36830a730deb9bbbd25c80ed3f9a922a53b910179205b06466b00ee7e54c2d94802f64adb2dcf0d6d6e59b973a891842677b9fdca70f725562dc2fd7c543e212e78ea52f9dea384f14a3deb155ee408196f4d34980084753627dcf02b0fc6a87ab772cf4d91212ca8912d3258d5a41d7befa4ec9073c09f56c08d4a14cf4817b3ae18f2e235ed7744b29950765873df77ac6c283ec318c226b19566759f68979b6a8161ab937ce7bc6d6628b7ce7ffaffb2dcb4e94dd4e05371547f8a1a3892909ffa5f991710748301ff5a89d845ab11ab7c131ac19252d4cbf3d73f3d14a42f1f9e02f2476c51a214ef304306aab8d33500cdc27f4dcc8b6986a0d40d420409e3476156b7b2c713d2496a554c2c906970f17b0965971eace55176544c4441e2a04e237b7a95dae389033150c8c7549c0c17dbbd4c78af6c47311dad080dfde414f6fe1a3ddd5ebe8f747f140cfe5e57cce474b4bcabf73f9094a1670f84cb9f1198e15407dfe2090134c3d3459a2ac6f3616ddf4ab126e810cf4b0947eab9c0985263f76a5156de6477f8dd26061462933b7bff8b28e835cc9c4614888bcf6a6921e9a57203cc53313dab9c2a5804334915d48f588c7d6637aab73810016650ccf70898367361d11ae8918dce7d880e34242e5ccfc15c5e0c6335621d8014332a0d118409fedfa6f8c17fe7289e62cfc5a32bcf3c2a95a05187faf76a215ddc7cdae85eab332551db896950d38a96e9400dca16d0cf9182664b59f6db62924cccede98d79b63c73edb73c46884fc336fff21ba10bcc9f6f8d8213c6fa60e8abff165c71140459ba56533a0da89b9a92d737c90fe4ebd66aede92ed9e48d62076c0f68802ec96ace2f3707319a037056d44427dc880480ec8f688f189cfaea3b4916fc8cf5932fada94ff7232eba9e997fcaac39fd9555db96635d7a67d917689795a72ae94de0ef3a5de01c8fe9d7e880327f6ec0163bfb0e6e12f9b6c5370bd3e77c71fd3462436444b93cc1c69bea512b2a401d7ceaf8fcee829d77b5be78913479078633a44f8e9c4a2be908f327339f0871cb9e3f053218e7a25d4420ff084a29fa8b18c9ace45e881eb2f645cb9bc4a0a45974f09b5977888a2b3b04c41156de32e4728da4bc54b5f833109e226a205f9bde6426115b274db6f0d108c1a982693835f1f952278837aabd08bb7e8768f9a2299f29fb60facc7e80ea50413f7016f1cadd816d00cd8af0042cd74e3351633667d3b2af640e429234fa73a4a49f091dbaa418a7a599474b136e5deca51f585a52cbec3294fad3fbc45b33b70727f2228d2c021c7b06609d8a960766f81b0c68c37dc87c994781c60db2ac4875c02d906ebb5d9128192f7336d38f138bcba963d1414f8c757d84e8021b0df501e745602f4c53a6d11e04f0edcc8696c62ee534243cf178018e715296283ac7c6345b4347e70d6d18dd38cfa7fcd96fdb9363f48b0647ff49d31f5c5335e19ab8e4ae56659321c557901110b9fcf97344e9b52d60977323057051f37f14b2f2f7ed993e8c3b96575537b61cf27aef656719b60f632556464f3d4ec324a5d76cf6aa4497eee761020ce5e10dc5a341aaa63580a0d8ec6e48efd743f82280d5c23d2353eb3b06ad1b91c8d6880a22eb84946613dc44246403b9921f11e4d6684f66cd8de8e421490591bc2b193e998bf0f1a024736e08f65f0bdd04b9c2858db6286b5886ebf63e6d92fa9378d2655dca31b036b90026234fbeff9a61a804d24f7fd9a6c2bedb67cc238a4390e42a1efff4cf61da4d6a9af26ff6b1a3739da2d8ef59f953e5d754e481666dab68501220cf2a07582e3ebf1eb4ff7344b4f755af5b22a193786c135b3d70b24e2aed5b9027c6f70b71fbc3e94df1d17ed4aee4259f1e30674c8457f801568f71d8aeb3cbc1912dfde8393b349ad9c90cdf6a250b26171305457e019c291d00d85713863c378cb1076fc43bac35cc32def88a06daebd5d4788938df723d03b35480e4978433e87e177f87f9573011985b7bb4fd3a44275cfdb009baf1783ffa336fdea712ae21b9b1066e9df41a66f15a58148e479b15fcbc5934fa41764087a6b837da014c109f4f459a2facda78b318419d3054022d605b38f21dc9eaf70e2595e3d98b12dcd233723df476bf225e69a7fa3d3dcaf9700187f07f106626d55cedc83baf4d22225851a97cba5566880cb172ef9854d96f95b314c0b485e8c6f8f22a85729b1c84cb1c32e961c0894d0a45ff13e1bb997c0bb7e20d4c3e4453a368f301d46f65778955e88f00850b0977f834e1c56841e14037c1cccb6619807854b368ca12ec4826c82f7a1563ff765eec420e8502148a05419790daa55b985b3396053a282024793bcf409359433a1ff8f9cf9648d26916faa9ca0adbd88f0fa6378e8f1f926b5c9e5f0cc8aaf7f362803313cebc89e21ea7f9410384154cdacb1eea32aa6ed6425bf607f6788eafb507f4878bfd4b0b778f4062de110ba256596cf3eccbca47787734293703af1eb0ce831c6094cab79e15e108cd38a4695d5a7946c18007539c1ec3ebc326a8ed0963d3836cb23692848d654bb7901391b251f5a32bf00ad267578dd99e44567fa134e9562ec9b63cf79a1ffe88a129c61e6e7450ba76408be53a7a866d60186321c71789bcc9bd0f9e031abc80269f036768b4eaef61d931d692d187a2e9cacf6c5082c6aacd0352e76fb2620166eb72ec3ee396b5493beefaf12a45c770eae34b8bacdea144c371b09006cd5b2e7079e45fd47c53e3bfab172aed6fa69d3139a53a5b95e9aeb3bada5d4a0015d9b4d5ab200fc1575543fd418540459e85c36ba0604f7924bef43581052eac1f62a32816b48a51c2dcf6252c8869f3b75b37493f67fcf929133cae0e1c3cf8ac6b3d35ae4a0859fa6663773cdf5d5951a1696ddfde65fb31457dd58fed780f6b8c304fc3e6bcdfe80938a78c067a4af31d7d8796644d3a1190ebfffa6fe3e74c7b4c97bd0b1d8243698399aab9165135aa8638ab3338496fee59ab832a3ce01b511d91c1e1add4ee471123e9dcd7e64f4692a6fc4c76f4cf84fca51f102e340031173a3133c8d29c0520076e12f7bbd5a4a01a73d83a68296ca13bc0b7a7dd412e994314a9fe8877c4bce4b5fab5db80efdd03d2ef5c21c1e45e605b6e744f9aa5847ac6a94931ffe9b7b2de53686ea0262d3a0ac712cf34724dae8f5f569398f762bb399fc0d576f224b55d3b82fa78333f9704f00eafa8f38238887ebc67cea981a6091fe311c6a753a24de0eea7ddec1a74b267a6fed358adbd59ee4db45cf41d72f0b5004fa945c9303e40e1003bf40eae7646e453e5671a60f12aa489247ff12da8bc8abbdcc2eec1628273e3443b688e018c68697a81fd9e793dc0eb792f161cc8452caf7d568778d0b218eef619b270c5c3ee16ae293648e3781816916519f8d8cdffb7a899f337e6677ed8f6dc23dc24e84c9e750584599005f3dc7b53a3286707e67530726af4e0f075c47824b0d38f571c658ee4ec690b73e88994312a19563cde990f727838db9fe1f3bbbff15ccad169796d5b51815bbdde4f2a405222b1e79d907f178557a3979a5b8893055b14e921316bb29082b353a8610ccd4b406ba5055ff22518d55ef8a8c5f2f07829ef84009fe505ca0e721948d3d235999564d16a597b867df1605dd68340e535f87dfe8106b4f4b26bfd353149dac2ecb1c7867e07ea661a639bf976893f32a4902ba961e4b35630fe273a156300df089fdabcb904b99b9bc6fdd203e00fe5448c3aa75d4ba27a558e64c4c55c95522f81e91ea60495fdd3f66551fdfc9b6807e04e92b37265b7c5815604d993845b87bf0c9178b008aef9b2977e1f08569041c04ecb2ec36f6c8b00735ed2775fe9b7f97b5371268b4e1211e7d8fbb5cf592069cc0b5678b0708098403b07962508c17800458a9bc9947a9bc521c3e915a95a892f4fbba0524c37ef11a633b4fab270aa52615a1727fc819c01df2c52095f11fdae7beddc219943793b45c5e4ea5c585f20416daf6322c06c441532071b4229dba3f0967a94d0ecd2204c240c4206a9d838ee1c0250d7b0b15c537e77f1ab4de5233269ba0ddf2c5b8cb94ae7397fe4ca685927fbe5ed92c630200039ba8729f388d25a913319b920182c147150d2043c084819a4611e3bd4f8ed3bd3719def2fb0fa0572ca31ce13c45796b46b25592a3b0f24bcb304b3061161d378cdbe6c5db524063d8d1b965db5470776ac305d5c6bdc324abf49b1617ec355ffd4e106033000000000000000138fa5800a9aa2821029d76bb8807e76904f4e5e9b1036fe178379816d3f81861211cb68334ba2f650f95740f72ae75ea1d10ba9a2b5b97fc2734846eb109322142f9583b4721e5ce31b724aa7e30b6f44b9c56084302b3f36889901f2fe6164d4644b603f96b919fea4915e6f7e879e9a3e3535e9db76486633173a436b670d347f4bf63234a5189fe620c43722383f1279de3c3fa97393e0f4a5d7be1361ae4b93fcd371a7b48d8594abc508aed5e450568e01e4407d19270fdca60b7442946a4d6332ac3b10f27c0a0531226292deb2dbb7386d02b05d51dc59854071b5ed1e22253ffae87c04966c1f4e00ae1c9d244f243218c9fc1cbdb8f4f1ba847edf0708ec908a56e293eb4d632848be74b94ec7124cc63fa9439bc88a625928971ec5eba80bb865fddd85c94dcc132f7cc837cd6f28cfc3208171cc74ff53f4c3dd9d10324646fb609fa3ec15cdefe2384f11d44e2a533756bbc6d5eced5e74476486290fd45258f9b0e849bb43e2dff83e028a74f5e99794ae871d7f3d52e8a462346e7a74c581759970917bdef1ffcbfb71579aa0369defd4b040ee927bc09f26b01b2d7424244c75b46dee829ed87e9f2882776f1df41541290d4d64115e8491369393bb474abd03ab12a962f1f5a45e080a957f5da8a53ad476f526012d295e099a0b3614a15f144a893aac99577459f4f7e1b5530c0c72521fcd0df2b3f4d82a86ba41c9a42e1efbf775d52eec117653c7c39c3b108094255e13fc38c5167b056cf603826f5d567b581746e54993343ec4ad636c20ffa22fa5ef887ce2dcb5f2a7179bcf0a6543adfc15c2d37403d62b6d7b53684e035173b1a85bfae77721d271779475c8dd17ec8707ba0562e9799db1c7fa5c51caea0b9116ca5fa98877963c9f04ddb5ff1e00c06d973efd202320e7aecaa44ec2705cd160d0f70172807840f18181cfcd918973771e38357e7e3411714275baec57a1e211fb3b5c148c9909f69f628bcb61ff775a064e6dbbf9f3647ad7623eab0bb6814063135c35665c73ab119fe211e74ff5a354124d1a8287116d6b6010c4a6efca963c17c750af3d54ad5945aaecc6c53bc154790b43d0b2fa01c5923991f3e249832821977180d0522807a77d4d119677fcd09b42fc0d9df18c9f8e0572b4bbdefcea14e75e90010ee4138bcba28bb936543e812c4e20db2f525df7bb94ee92534a8ca67824051a115728229c409424c52298c151dd76fedd9095a1df7a4ca6b671aaacc664a720812cbc094ace3a575e0acb7a7bfc698a4055b4dba82998598789e3c1fcfa7feb1b6c8a14c42ba69b37a35d28186755505fa6acc9dca53275cdcf45908c5a9f998533ad368e3501e1eb62e2b66f9b6109c20e25c1514fc856df75fb72013cec318dbef7592774ee1433dec86616a4dd3103c147e49be4907211cbbe8b2ab59153b25e05fae35f749a10041761c280c68b2de31383509a46dc7362aeb550ea28981ecb323e3e75ff6a7167cd73b3bd4f797b11170f3c3befa705a7a245c83b9838086d3b80862870c6196923c5a0320eda60ea64c08f83e171317e97bb0a6cca9f0899e1cd97fab050f8a9a8709153cf8c5fab7b755e6e9840bba029cef1ae970047deb3156815b88ac15524b0d7466592756ceb4fd7d87ce612837540ab3b226d44dfe66a2651a72374b9d4096c6ba4c9cd5d6c5398f5497ad862523780f788f0b55da1400c8e038ad613753e3f1486a251156f1acce6a19844dcc021b4bc797c4b8de59bab5b4ce56dbc10a3098219884bc569dc105c60da4a50e064182f009623f8c13959d537f8e506066f16c0372f6e7a64ffcbf670ceba653f041a304085258369c082a8c94d17cb0be6e8abdc65a4e7e48ac17376a58a7598bb45e150616d5b7099eb4c5fc6f3e9074468c9425916f3333144d5b8faf6bcadd6837be952f938673fbd29f9be2ebf8e21ec379f4ea83e47549be53a3a51b8b4ae957072826d4e82a16a9ff20dd8c5e92f4b59a4706317aa13daffeefa36a87b9ace374067a03b4eae42c1a0c39be684ae196dfb3256c87962adc683182f1f6a2ddfd9d07634956719e87bb86294660d48a5a8bdf28871a0d4e8f65f5c38f404a40a7d09a6122d915863bd87fd5e1417557fa953c5628c53df63bf8d1b5fce04be5190823f17ebcac733f32b7513ace05b9a7dda60be86c079c25df7f5459e304a599b62952af80780b1330ee92b28fe690a16cb1ebd6b3dbc402d033a88baa353d42d6316ad8a439ce72fa57d02ec91baee848956ed54b416c1b7d16c681d1ac6daf1e0d3e3b33ab83ffc5201f063542461be7af7a4d3f8cde347c8e51252ed4e4d712959ea2c23813b6ab269f9adfd6918c28469149891943c661587dfe710fb2cc50b261be87d1b1ed05bdac8e830f683a60bb1d5ab1bda3e59ef0b3943a665575a5a428e0ecb9ec902ae04537602290db479e736cf8257fb73fcc02dab3f63847619ea1a76f1551ebd381bebf17f6985553d2a8bcc21f384a47d2355babe622ace2e70bb91561eb0002e0b94c9b31137792fbf8dc002465916ef8edfeb958bf927ddaf7f5703556f6eb547cfc3d99038a24430a609a9e443c1ffe581c00514e99cea3257ec4324e551d8f83af5b67bbd6e61ac3e3175b6117048088fc916f80ca948500f77707c4bc54c9799c68b72c381a3b14e477234ad787f4b23e1edc43d139fab5a5f8124a3fbe727ec68e4b0e8fa7e877b5712cd508773babae976dafc01ea98183a3a801ae51856870b4acfa0a20bb6ac10f207c39d9df40bae368533bdb997406b95fd1fed9c6f85bd8430c32453383a3b6b62036e2ad4ed910c941a783b612a8a77f7a4db0c3f75dc88884d06966710f56d71106d7d4124e24a8b5f42dd2a0502554729be7dd41ca38fc8df69c032d4cd4feec93d09d00a87a0014880c26d1f6418e070c7c1f25165f253792819a3b1634423d177474dc660a09188f13c8ae10ed5084100ba8030bbf47b47d9e262797138878372aaa20b6d866c12618e2065642cbb6ae2eae49e41f1896c89556250f8d7d9571c7dca62768cf59c3bfe82ab1a107ff8d619bf17b8833a61b51bfde48b85e705b1fea0c151230caff40f8a9c399086c1afd95ec33db92502e3bb3c7ac5d0396b545344ae9aa6416816e65f14bc2d11a07a0b1c8273fab2fa98238acf967d5f7886a11b49dd64e1efef50c5cceb15914ada42af346fca2f8018736a847d7043a6d52134b4090780cef79b20ef12f27de40c81b04c66a699160155f18317101f39a74b329fdf3ef4d274d1f3a5439fa5719eda1af6d319e460dd2f5f33442abf4c91b0ed53285883269438850f8281ffef5954248f9ed1b513c05ac48c1921ff2940a52408468141d91f87a647959f1975ab8ae5c54e0be59a2fe8d0261dbeaa2e4aa8f8bc1e46ed2428eb3775bc5f12edff7861316ca3abb5f3dd60921c0e51d7dc2e2d719cd4cbe6f64b83722fdbe0ec396a6107f810f77fc67d85d0d3ae5e01deded7927edcc00c625614a8039ac13a34139177c17146690ef7cf9b594f34b3fbee93bacd2b077a3f6ea045786b0ddd882b8c0bddc96cafaf6a82f2e96f4dae0d0f73488edfa8e3843c4310fc86db52476ac26c8505165bb28df08b2c87e39c74cd2df1c55aa8e70ce9008934f0dc54edcf0e532c17038cfce8c26921f0382d2c324aedaecb452c030ef875e768aeebeb6c7ff865201a065b275216996564a1b272d430ad4009d793173c485dfc013d78eba6a111bc2ebf972371a088d95b04f791e19f5bc55e684ad7d9796bd1f24dc00ba35e0e8379a999ddf682e14484ad513eb8d8d839307e2b764677ff7b628472e7253889d8e69317566ebba94231ee46874b20abcaf25a98b318e5699dcf934c8971ef6fbd427bfc4c1b66cc2983155eded779433ca233f4dcdd34d2524c6c3308f561560d6368ca58883b939f775eb06f8b6e2f27ec12eece4e0447f37f7091ff2254ab6a19a2ba57a694e317eb8e84a6b81e0a08533fd946b51187d2b22bd81a33891e1288b41043a41d6e59b0668ff669c8240282382c132e952faaa39e5482434a3acd2c04142d9cbb91436af617f4d3e5bcc7e77257545a0d790fd041fbd4feb3fa55f7e63e5d06e1dd95e5a665c0f6ec3dbe051c347864273f4f9f1916f927dff18dfedb4fdb0a503b0d8979affc6929ef95d2f589cb2c95d71cd9bbf18c942b9cdc761b22028fdbb0eceff131c5b142696631079bc478c14ed97373c8f8dc7562153349288d52e6355b41a3ce38ce66eb04089fd0142141827a78631b3785f656773d319dd12aa4c422594be7c282e65803c734dc4d6458919fac94b3c658a465b8af63e4f85635e5ef90ef720d230fb3d639845353cad740efef67aeb0ea8eb8b48fee33ad307651ba8c0724998ff791700586d055c7fad8e525a9f3f54305f0ea0ac0afb39b392eac48e2687bd7638127fbca3ebd532dd88ab15674f0263fcf42010a660954c187e9dabd55ed86ba1089a8b430b59406b13bf47179180842845accaa0c021e5e748b8995379e98bea88b85770a9163cd5dc319be3c1d28cdae53ac0364f8d9bf8665e8a2378d2ad1823417fa2522f08135c562d275a06afb6111e733560026781f06efff3d8f2fcd1c95bdf63964dcec607a0f55972d65713d0efb3423a050e56a9d3e2881119405d599bdfc951ed409147245c59476b31e1454c676a7dd79ff7f740c719b418df965bd9336c3fdd39e84f32843b89a416a2ec2e25261690ebb74370cda6e6acc7bde72549b0e68c546cbdce72829b7fa421da283e06de2b29b30d24489fd3cca6d925a225ef10cb1ee8409354d678bc70a87903f8a21120394f73d8b28c2b42fd75b48f3cc89f454b106e225d7eaa6127a8ed1948de4f467afe63708de7c6b52241ab24da3bf3c9dc48fd6d5c7f67ece6583234368e8428cea794ceed48eddd83d3f7d3ec2e47996a3c01e8e8617f10456b4ad4cf055e140db74cb675eabbb2f8557c23633c889739cc5df2796298a82dfa71b99ca57b97225acb10bf76d67a03209bb35766ab64e2e554528c4a9adedea0f83a24656be5f22e3c472c4e22f890a323e0cc88d3a30269b01e93f778c3816fcef8c614398e90c93b0b17ed04b8ed9ed23e8bb5eb4628a7219176e6a527c4537ee103d353748f5871c737aed34e84352ac80e577aeff1f9fb7ec25a0847f7801e7eb7e6e8130e089ddfec2f8b0f80782cfa66ffb292aa974d9346d85a04124f55016b0fdec6545b65f299fd216b9030b1083421895522a150ad97dccf5e52e780945bd88d60a85ae290d4393d06c3a57a2b44ee882a7f62457a9adafe1b1b60edeeba7f39ea7e37996fb0f71f29138d347c92508a7ed44323df620de985e7d7327fb0410f798f46250262ec9dc3d2012153d9ca022815895fcc38484436737f7c9c5acc4cc27a00cf060daa0a57b2498e8766d882636fc1f641a0e6587e0a67c57f153fde09f8d277e6dfa8e7f5b80eb0b1d04df6b38f3254975fa2d60715b23ad3524287b5bd2c3a69d00155f831f7a98862bf1fa3124ca883f23ee689d1df17a5c6b168a28b9de93581642355b9b1ad93c1e4b39dfe0711c56ecb77ea080bee112d830540a1d7502927f6b3a00b89b0210ac86540e8c102cf9bba8ec88d5e215667c1179c72e65875fc136f28b72306090a78f380cb6e70348d43a5d2d85f4cb67ad5fa1e0586af72be188985473ddd685e2e92352d9ab9cf6de22de371562c13eeb8702a6b99221d7e7893ca9c886c3ade1a2e576cb9b55e3fc0b1b7e13fe6c7766c49df4ce8d584f91b37b4eb748fa021f20ffe714312c23aae0bb5512f3dbe68ebab3619c17ba0e1c31b5edb06ab5bb3b67dbe8394380f6302000313f02e174b1d8966844f6c68b2c276b6d2d42a12676baee7ca1181f4edcebff06fa5fbb4192ecde9ce6df21bc0c8d7d633e03e337b54c51f0903f39a3c8ae197414ca7fae17b619f174a729cf96804f0c5eccdb1bb7387344a1c3c1ae08c42d1fd4e10603300000000000000013e6bbd00b3fcd03c5543971fe7d6d44efa71f8651aee0d76d219fc9df315ab30cd9a3ef5eba08aed12ee13df57477ef3ac9105c4acfd4b998abba12d216b7095eee5917ed7a8e2c1cbc879a382c0318e325aa248210791d0d8a146820e2a6ed4567df099eb74f3e30c4de06a923f63acadf9c984d7f6dd00efbd26b1e4393385636176b66900ec51287cf880617fcd834c5c0f3535ab4dd07f1fc900a79ed938df03d78d719e0eebb2e64efadc610f48cee7a67efd6137eabd5205d1e15c0918a2a0775d10c233184bc018c2efd0f0209bc586d50a25e4ac2a6f48551cf1639afcf96da9a38a75eda66cf5ac5705cb0002511291c65ce6d4c91a9afc29c1b7cb68d083eb890530a67ec5a3cefc0b6241b141489ac31590f89d0856ac940e17409d948908aa138af1a240bebdd9899a576bcbe9165f1f70bd81074fb1fe3d908a0e56efb43ef4dc4e362cf6e54f9798b0742607993bb79a670f60c5d5b5686bf8aa3dbd06786922bed7b1b876ec72b4f3d7b7e02ce8e76855d351cbc6ddcadf55fd3daa5b5a5594fb1a1eeace1b4e8fc7dfc97c47d1ddea3fa3ae0c7e6740f6f0ad65a22c873818f90408be12a2d8cd48ad6b8684a5490918bf531dc9e39009e72a4044a6d8e0b0499e0467ca916a4ee23c4c26367070f9d1614780df1ce534cb5d31407e1189c7853ea3b92b8efcd647b6011dd75f255441fb99ac11d0d2c66f4fde61b61f1f080eb65165df6611ccd5a4689dee566834a5a2eeaa00a61bc6c34b7bc131e4146cf7742bdb6e9ee3ddcb0196e2010baa1e21a9759d048b5f991608b330c0dc58eaa555989430ac237f0223ab993f3f60037cd1f691ad7dac9640f1e885cff5badcf7912a5c83816b2b3b75617e48274321112ff47b55a03b49344d42581a72820a6bbbd462d3bdfe7f682cbc094bf1d644cbe9ffdb72a527148f7784cbb5a49445f91bdb7eb78da4e4c2a8bcd3eb147ed8595dca2249052d7f1fc605f7043b10e759682fcd9b64a49948ca2065f3c375de69e110bc0b02874d8d07515be346005e71ae7bec22daa67c0ec08067912cc176a65903f282526a61b3a5396bdec4737c7a9a01048f7734af85d9490605063e8eb9c2cb72f8b8ebebb44b0595126fe59d447e0d2aa12bfded472b176e471f21d10b0108cc48905f5ea325f6dc0b74227ea431cb788811eb123b6e2ab5618ded7e8bb2d4595ef6843ca9a1fa08785aa9d3e5a46f64efad9c3fc0a87e9fdee09ed96feae0f0b318d2815bcb95e5f61bf30257f1c063a9d01eec84796154bdb3ff0d9f39cec67006bbf69b2611fd352afcfff6ac96c6cfecc4392e0e82be9603f27adc50f9f464079c6668166692c3a9bb96f2ec2f8874b2ea7b3b4df303d34d3f92a0ad76962bfb20a01265888a80d40af0c37ef3cfdda0b320d8f2e278c3de2847ab4a778a2778a134d50719a6eb274e53f4bc4826baba31ccfd498663c62324333a337ef9b9bc3c1d32931f3a69817b16ae4eb4e6bcfc812025c5e845c99fed64ba24a302d3e881cbfb9a03882a2e1a10202d673c5a4d169d715bb14f6974a4c55a64a783abd11e0de6bb15f7d90f63994431cfbd9b13318e341fe750585c32c43d18db89b5484800720796d92c9a106f4093d63318c0ce722c976749d3039f4a8555f93ecc221b0ac5bcd13037b0a6d34b4b7418e166c8465f33f89dcc678a22ff67a1e3e80d566a5ce4ab3a1cab12bbecea58b49352d74b11b2688fd326cb09dc9f602bdb70cabfcf99e2f14eaed3cd3b5141b5290b2ff5300fb678f431ee24d3484fe097d4e2db1b14a3be38ef6685334b9adb28f237e4e9b8de49ae60edbebb0c650f9b99ba242f59a53dac327497ff3d8f4dfcc087415aad18f0f903b7e88f67066b360b9b8a56ae3ac2762e9d2bb2da17f5e48a316ee86338be0d4bab4075e9e84485563b587d766e883ae3832231e9a67b7f591e3c8062f8f9cc5ee7b66c5853e50ecc6ec9f5ee4f3822755fb436001a7b4266361363e796557e1f5280b0f8a7519b62233250f4fe7c668a4a5a4b567f06cfb3dab497c9a2794a2306e747aed3eff5c6aee13c1f339c80895b7ff824878910d4b905a6bb1d27a2c933fbf203de1dcffda968c4109c62d9c6bd6330babd4e0ccc3ac90a70bf99aac6df777d70768d8f13f7ef73703ed7a93d780e55d1b3d219f30060df68544fe0bbad3a36b46cdcc3f5b5bdd927d649440a544761c06d5a29a08a5915a833e58f9b6210f7954b477363d11c3c6e530fc6c88bdcb2d66f511ff96658e7a579faff55ca29ffe5650906b710c53a751c811f318acf1db8620d676c4d1c95e162dfd88d32ba89928ec55b2e43772ca29340106cac2797ba93e3c2a0a73fc17a761125997e7f4d0fe6629bcb09a3a8cec45e6e74d9cfe846684fb1656e92bcd1209cafb905493967ac84b3733e4a0523335d6742e26894b8be9f327f7497ef0fa44cf58c4cced064b2f4ed34e0583f3870964b5752aa0d9f3ce78592cfc56482202530ff465368732f7f1fa3d716a17362598bd76dc1dc64dcad320b1f9abde2defb05e1a6dc4283f43c348628cdfe3d77069909fcf74848a196412b461f90f36906eb0b87df564eb8edfe1d847337da95ff429fb824836fcf402d8d969b258a385ef0654b5593ce1308b0f2bdd9ecda20923d811b522e64c28fc795e1c2b87f3b0fe49276b706b834a31b1d216ca4f0adf5acbd0c3ce407288552fa7f0c39f26ab32ab2317208965263d6617ac87b910c5f6f74bba5bdac0556a1dd8d115677f0b82da29ac9683c372d06616a097e354ac29a838932decd3fa19ff6a6c182d00e1d8fabef9f24124286cbd15570aff451fab3b4adfcc58705f4ef9d7615a24709903df188ef5b8265a06442667a6b6ed67ab3edb5c04d65ff5e2292cc6c3f23e3e25d1eadd8794c062062c6377ff15109d8fa233ee9104bb192222e9a8546388a304e0453ea699d944a77a93a8fa05a1e33038c6833dfdddbe1a26d00821e0ad6acd3cb252c648e1f5ef28726eaa4a261a07828583737c965f1f148f8f5e4a9f75ff2042e9c481197a679744d1e189cadf931ecb7a55165c32964541235f171205f95003411e0371e4b2bdf1af62b92dae419e02be72673b172869440138491a5d2772ff30ce2c5f2702cd2d3cd68a4f19f18a790b29c7623cc18a58a11a5835d7d71e025a354485c4999d4b3d48b70daf7f7d9f5f5ac00db0175335375f5279f5483bc4b9ab58c87fce062f34a61ae3970d891f9ee873a8688bdbb01b5e481bfd66534105c449badab2bd5102c2654b2078cce3e08b40d0a54834cd9ac45bb71fdf81e982540a9fd2f643f935cb4bfddba0ab7005663e2c8b180005415b51bde44c282adc38e79696d5a30fc321bb9230f2cb63d70a818e83f7587681521cfada9da95df1b6248cfd366bbf30a5b4a45fb33c9cd6963707bd9cc7868484ddc6f783958da0cd2bd417ea60c81cace2a715bfedc7dc39915b375d351c6dc27c3fe0972a14c3f0821830006efc61ae89b9ac04639f491418ccfd87bde10932b5b1ab4d3ef72cb47d53fe03ed671059d7ca5ea9a856dab67fa0a8e7d2f38819d82ea8e5b42257780f21b303ac26b5e01a77a68a1b9396250770fc71858e2a2105d1f32b43179a2b9de6c60550def39b6123b0414faf661a23b64bca3e421ae99799ec5bd824f812843d067fd167255976ddafdd63e6170859a9f4ab2c34c26ea2f2eb248350207de653bc7d3307553301ea0516aa8cd782dc23094eaa2790229fc706ded613a0451957662945dc5767396a027bae1274732834cce5dbcf4bba0088fec7155dd81ecb14d14fc8b08e1ba419027b937f569e653b48597d3b2682e67e9fc9f41105a0a6b6c9bb55f771ff747a4f3159320cbb37ae10ac8f18340fee83ac1a134bff14147003e3ec86db22916aeb0de7dfcf37f492ff3bab7cbd923682390c597beb3e20cd8173d50fc1a1f36e9c39ad96983a21b7d8c44a8cfd32f87db736df0b2ac38cbef5a3f888d25db819657f3d224aa6da92969133e5780951a9cb46ba730297744d53ca80e04563b5467f9ef87642ba54ab1d426258c30ebaaffb8fbc8f0dbff7f29adac40fe7e950bf43dfed05ef67db46a9483e3edd705fee5cdfffc8bdd818bc60c92c0b3c4d010772fadc5738ccfabed0418bf2449ddc4ad347e7f48491e3f4921a6b42b26e9ad3cb46469c1ae75c5aa9c75824322c835f7b69cd4b2270439f2f628b87399e954901260517bbf9af03f10ee7f963766e1ebaeeaa975b942e77acb6acda06b427ceaef1c02697fcb9b74d97d9326e8953e1c75e300b2f8aa32287ebcf6627631d1aefcc7ef5a0cb751bf8be3a8092e03ac494504e1ae9721da86f070824a8309532dd9aa88adbf54b8385a647e362928982217422c11181c46a4cb03b46cfbe7ca771bc12cf3f8ed9441a491245bd7cb6b57814df1432d030285c5d20ab2756da62d4ea40b71b5e356b88b9814eefd8f952da762c2c1dda56038fbb075fb8e73ef7b8022f52bd733e170601a98c72a6ba7334b1a23613c37ec9e01245b54462c3b05a53cef84a0cb814fafadbbcb49987688b15264898362743455e10415410e44da55dbd6d7b896239699499b840707fca75b3fd4abf882439324142e5cba81b694b5b03afbfcc71366626c971bb86095e8b9211d6c2e4ed4587b93ee4b407e20c585b549ea0a827bc94d53a122087c9c377f1f905b82f537da12d052339826e6dfaa62638777ca29d2b1db5a3ed2a7a017d22b3854a84b3818f529d5b63c634fbe13b15b95da67ebd9fa18f7bb512cc60b4083bbb8f167f7334eea1df32ae239ca542c2afa1e4cfb492485db974ce5b86bffa33f8fca963e1849393b9e789620560f1b63c15181f0c14a48a15d6cf6d805f8881afc43b8a288e3e5023d29876b7395c8a3fa14edfb774b84f1fd63ff1da7adca77baae833f7e453b9e645f7dbb71334b90baae3c29b24bd61c8eb6726fe860d96cfcb7218d4af70fcc18a1a761e05dafcdd4f5d7a9f7650ed5a17c88fa370d9eb75f629cc887740e4e101cb8f6bac35a395a7bd41b17edadf4453be0d6117e996be703d0280a5ded3f15b8639dc23ec85bcf5213620f535bc65e91efc376f13c99572c1c136ea70989ca3861738f081016ed94533b835b2436c88e905d3c09374fd1a1eab7232365f0bbc9ab786574cd0ea30aaebd452bd3204504a2f89e0cac36f41ff387b69ba1a7f1efa0371ff3a180f5caec19e5b3be68efaa244b9656c7370a00b8fb509bf1dc13bfb93f1135b3dd9c148a820ac0b1ce7d3b252f84eebd4a9ec9bbfbada54dbf38495a087ec641fb3d943b5b4a12d2289ef9404cf5abe839044d4b6b018db53feef61f6d62c88483b76d4ce5451b551a6a7902d3bb98b7d00681b6cc2409b40aa9e74cb745cc10f7b3b3e45bb81b007d6523e7436a745390c63aae4543ef30a043d42567a5ec437be0d916edadd6012128ae40d82dc332aad95ed8e84148d1f58640fcfb6a288b3d5b70e7f4a62562274f0474d75880f31da39337f91751d2c9143f54cdc055c7671e1e110a26ee70b35a8ea9fa6e5d9afb72648356220ea4260c5a52f44184cfe1d08d6a31494c83eacfdfb3e334cefd2986f23af97f508c08e6db9bd8df1b382d92aea99e7151a4e0879cf26628cfc28136f72c197eb3f6edf2b64cfa190f218b4cd8663d177c477eb618e2ca1f5daae819acf332e90bc44781aed902e73d41948b162cfcf33a4eb6935f29cc9b10ae3e48c149733f934782edab57e0768d5f23119b8b2e85066fcfbfa533236f9371b67d96e5e2d26d3ea5a87d19d8241077c4658d4d602bd314600bcf1f1b5bb856038a59668a93681f065b65e138ab6b4a630200030245277cfd3145e833f58516581106ec228263333d231e900be455367f2413b91cd4689ab49c3a4ebb2833db5d4f368cd82099d5837b05617ea035a55ebb716e975202ff49fb67c86454f04b2b5a903edcfbed720ff26584b6bdfe35f2b47f37fd4e1060330000000000000001d90fb30082cdc3d8bcc31e71003547e43c0fcb9d4f9ecee9b7ce283b035173f4d8ddc5e8b7577a9a5a8f31cbf37fe2a3ec739963a73d4559ead97fb1a9e8f5b63600d0e052fe0877262b0baffd22f96d1a26a2172665895aa79253839c89cf9753d5cab06d616b38bf143c0555ef907c404d0f43e3715876f61b3889afdf9e3bbfd73917ce3814a538fac53ce7becd9cfdeed8302530d4e66634036237b8d34c41d35e42adee673d499c65792738b50e45baff5f091a3c9e0a23170bbaa7626ef45b4284f1a34a80007decb996daa061c98582eff5067e02a4fafa5ab9695869df669c66a0a7b69c9c63cb218ab4ac4635e10aaa0256e0f0e12459df00237afc406b10784ee264d7c8108630f12948a7367852eb2a3682705b0623ef3bb9f46bf2a7ab202f6d1c4363788abe05fecc5c6ef67b37b4f69c8fdd287d66e46da6212a6c3b3cfaeebd0164505d854c9d892a61c74308a01e3886a483ef70894b3e584c8ec005920ba74642e7f69125d7372824e426c1656cce141e5b074b713eb4126adbd2847e9ef2401c48cce6740b86083d582c4dc3be3468f3cca53b7005b8ae6ae5ceee4a0e2169f7754fd3ede4015626422830ab42e4971b55e04943d3bef97899ea321bb97774054e8327a1296d3f13d46d60540818d9d010f5fb0e04814b7bbd7bb93db28c14d2cdf77576d8c706c7eca784e8c0827e45bea9fbd351e3b3b094c6b017a3df0dd13da6d226e43cc97d8ae00d289d61bcd10a7fbc2c0f6691a4693078a970a6a7db4990eccbeea4ef42b1289d3a0e93a415f9be06bc4fb9b87d78b53b099aa498e17d0530c30bb648a004df430aef4120d0ff4725d2dd57da7c7f85eea4733d51a16b082bedeec2f5824fab8d9ec80de220e7e97da626a85a421924a713c3de220e84ebf215c0685f9ea1f1a2ad9ac6731d0654be6b43e50e7b1de578eff78a27b96f13fd7d7e011783c89106ccd886a695a554e4cdfc62c60fb788071b992650f9c66a3e03dea51366f542a9b9cf3cc778281915e4501f9cca2813fea036d816ce968700d67f3079988f3b0c8994a40d86c81594051b7a08db7c270072b6c355073fb49e40112d97fe99d1781e93bf4872e1950b7d8b47edd12fc767daef314bcf9f8d5c0615ca1d06b29d76c0e7b95b78514b9d0c42415f6be5ec551fa2f88e97a0e4e458b7d52a9df72787a7e66d2122c64389196a1505d7125c03f88c7627271f140f1787f32e6181dce9cde6eba3ead205d46d7847dd592127efa370bab8dfbdb32a2476005957fdd31c92e29bd00a82ecd66896ab5e0d62ce98dd99a3a8e38b9d1cf491f62c5e526b85722bedeb6df7aafed873839bfd4a5e8c1b53f4e82f8bc2e11577827a5900925ec9760e0f148d1c88d2f2e5aadf3da38fd378d43135495900fbec06fcd2a899e2c1cbb88b381c9da19361b3f6a09c3fb7c27e9fa7f51acd533a04c8039fd22af83ab6e1a5f24ecbec7e73ec3754142ca88b9551bbd9ab8aa42282a8d98decc2eafb3b94964667b270fcd5a0d1e60778cdccfd07fcba114738d961cc11a58434ce7711f7c2ee18e480c921ee54d4813ab8ce76f41204dde145518eb797c4277b23757578f6e955739d7aa7ad705e24ec942d615da533b3cbef100ab05c453876a41f53fcb87e6525467800f2e527c264700cb58cb87adcb262f01f2c8a44fa506784e83cec9dd8a7e4f832a7789ca45e23d1d470e8ce8e6bcfc3d64ce96dc6b891864f715c90b821eff6436e19e052fd44ad9254e2b2e0292eee61452345f836a9570504027414b427255a891807fc4e2de1cdcb06ff346f985b5713e59da6c4043ff63b43672dc77319ec4efc67572b7aa77680d068494fffc2386c705dc1d4de592966627364cf3d27ecb1e2c917c801f080c9093a5cb623b308f6436dd7ec6321085ed697096a46fc86f4356ee193e13b3b982968f07a278ce6edee8d71b683d32646ec655f509d6f4e81163231fd414d5b53cfd5be3459a3310f67d3b3e4d7618c3ebe2b19d870a300e702491a80db684932af196002c7a0313f984b326d947e4d342b1c519804a8d1e98c643a9a2511c669a04bfdc8c8bd2abbd4312a4d7eb410948e5ccd491b2b587dab36e18a666dc19d705f4fc4474aba0474415dca00e4ab57ace0d177fd4b423e873bc62022e5cadb41f3f34f757c6b05f8462a493232222974bae1ae0e8f92b2e1292a3629c7ae03c372715efe25522d591a365c1e34c4434cd0e49d9e8079fba0d25c4ebac537489f652e4116bb1ea76dab8295797bf0fcf8362a20860ef0017817c85cf7339971ce8159a19069fb0e2cf10c71d826726bab6307e14c3ada75fb4fced4ad6f251ae5b0ca1262c1274a178f10925475a1bbc9834857bb84200f0bc9b7aeef8131436fe1ac2c2f4f59dc56e01740a172dde912bafa2dff55a7d7df20e4a3d050c4e5bd1dc17b8d35e1b431515fe78b8ef4b63730cfd62f01ccad9c789d5feaff2b976a290defdbb8ca7d9e756670fec75bceaca1fd4e3601a4a73c69eb7d04040c66da1111131fd67ffba3fed9974e640955e0e428315896eea243d29571bfbf912ebdcdd61482650570e1b51d086c167935d34af1d2b649454b9e5c554578164f28a5733cbeda33ab9b0e63d21c445da1ba1c4aa7900f304e70819281f654e753fba245c3f86e3df6184a6632b306a63ca5adf65a91e8a5499b72a31755ebfa33e939f29b4610d970008cd2e988052854f0b63a0ceacb1c78edf8a25ba79628770e0ef27cebacc42f518d39e5560cc16cb7ed5e864e33dec817e6401681ba1348686a99b1a56ba8a9d802a9d0a8b80998d6095e3e810a101aad45a7ba13d46857554645a0405523058d554e8bf0518de32d829a5b711edfc5ddf727a1b31b45a31d1860dbef29522ecbc44cc35b28fac20ec98930e6e00d968654ae73a88d89beee6014ac3287c73676c1e8160b001076dfc9d7fe56ce6a64860d0a40eb5de4d4658dc4c6e9d1beae578cda280a4a8ea8c31a811d0277606a34d0c9c06c504dbc8f4d936419bd11e6a5c0cbcaa4e099bf0162aaa6640f0be793d8436c3ccc1afe0eaaaa3af977a68163e4f43da722bebea72607e4c0b04356690cb6f2d14a079aa13d2c742e82caab8a0e0df28c3e567dca41900518eb665bec775a0948c2c728c7a4d4f943c3b376f054598e9ebb5b4ed2c368457535404cae940e421560fb238507d1b012783845f21f859e70ea7e67a445b21a3f8f2540cb1078717111253bcde10f6f6e2f20831aa58d2ce549fd0bf975fb680890fc45b8113fc47b721958bcb573ef506371ab2ff46a2be143437eec3817f1c2bdb1130d449e8c9618e32ac5f7e7e3827ec23860eb1c0b3e7e1082fe2e6d9aa033ba4940660dd3bc238bb2aae83db0eb525ee9d73cf55c156f6f318231ef8559b3b7a3252d424902a9200d992d81120ba16f869394b1905f28326c7feb0456ded3b7cc08395a951d3792ef0a5cca534d265c758cfde197638aa3dad8f3174e573223e04e0c454375cba007b476587220b1a9d970d271faf5ae30e9629265804ddfa72725f65bcc200454cb9d19dc6c7bf256f78adbc35d505830f35e1ee45ba935f5196fc3319797bf0aa10c6c6510a975263c33315da6fbb2e3bbd7be1b2ccea66f98feeaa1415ff695e4a8513b0c9f0d3b1e53ab18c647c819f14ce91c3c9908a5e084d90c3381fe4aabb2b2b91ed5298977aced4a581b2ebd57f27aee01ea2e2f7df5467fdd785b3f9c1565b5f53592a7e9f52a194cd8da97fb9ea13b89192e045cbc60dc6867e8b9629bf85ab7dd1b74255383890ba267857585223ef8cb7e548a5555e6254bb8b664013f8d55c8383f90fb581f0312f351721c759999bb4ec59e01aa8d31f85a95aaf68f09238529a34402231425dc6b1e750ad5001022d3b985cfc18db423776c943552381c6be6a4cdd27e06d0093855a1069dcb64973cab318939a9d8fb7b789ff7695541bc5971b234de1d67441e0b260c2e0fe9a149282f682b18118119d83902e3f76d25d7f4e258b1334398367912d6dec20dc55b988e16b8d64d9fb50f3d9c923b7c11a0e40b392fd0928a5e7c6a458b03f31151d07bc9fa32bd618fdaeff6bacc741e50a30b3828904ceb6bb36f45ef70850963b1c7c84a9c64adc12f3a7ec898a52909a66778b7a0c385b7ff7d1e4909604ca7e9c42bf7513573c4446b20a21fce73bf8e62d5611c0155b0c882790745e01ef963de994231c2a2b83ea1febc26b4b6b17cc60b93b3602acfaa04d6e32ab78f807aeb217ac9fb086ac66d4192003ea4fad837833375d4738047241d99357864a63b01528494246a98f6fe62038b7519d6c29bcc54ee9a9f7f822d6620fb0a53593d259dbfe3da4fc359e5ea9508ffedf3787190df4b4337c7400cd28140b4425f230f3e103601ff06100e7d554e394d44ee3244b9752ec26838650cd618aa8f90563fc3c1f80c11f7a5ab41e04c3fa857e8b61e3788ed36a9b13220791b2c9d78ff558e6a92e8ac3448d103bf30f7dcc63401eba46988c40a6500ab54e76d45f4cea3c51563c0808a47834694ac2ba9aa3a38066d13e8aba1f102a943e4b78b11436457054ba3aa43ef5220e2cd92731ad0a8628f1a50329947e18ca7068da5995b6644e4704c760897c032c2a8af615cbb147d9f5a55b2056094ab762955afba57307f60798bae88d50851503bbb09775123eb18ae85e02476da674e60704bfcc21e63b30be2583ad0f6dbb6ccd09c53010a01a490c6c38a11f70f6bc3a7b5913de871d331e7b5ad37261547335b0c08ef485710b02a972ca96d39a20f996b2de57012ba2c0286374ff27815bb3ff41339ae2532a91bf1a7e022a3d4a459e86946389b30f7ff4cf9e37d2ad81fe74c17999aedeb7e234682b78dc839bdc907dd55f86a756320db6de847e41851ff1292a5614db92ad1e30b3ccda9110d67b8547efd5a8335119ae48bd57737bb4497555c3343fb05143b48c574b539ea60fcedd412e48259a24f05c55d945585946374e55404a056cfdb3c34e977d0680f0075967bd80c284697aa1c92ce1d004397a4b7d83141a438cde88227b129c77284968def22019e1f6827038b06ea67ef84abb1017ff3e604ec4a86f223c2a14406b347d9f4b409e8684924c9a9fc5c6167cade54c5c45d6c3717acacd8f7033a5bcf2c5e9a360df4d927d40021c3139720a23b2c412e53569055fa8348febbdb6c8779617d1e394c295972f14bb4d43a79ba2c60fe5b097fa6c12efff38c0c3df18977a03982687c64772289110167e8b3aabe2bf9561f5175c665ce346ed19c5e026e9ff4cea0ebd45252724031e53885b6b5b72a1eb8e291bca5a3ef8f65173a6f69d75356d254cd2d5474c66880cee55db7a71b3053b4645c1fe8074e5f6f9ddb9db7eab1d17a7f97c918e65c2f649b656dc3569522cb633ad5ae65ea2b31b1a6174d74ea1b8dca8d91611fbab42f1ef4e5524bd687d9d61d531796ef6c715fe741e10b8244898fedcc56a0babc46ae82ce1bef1407d94a3cb072df6e6738b47ccabce0228e1a9dadafea96848fa34887898bb3b859b3b9ec9fe7a63b380fca32798d377d3ed7c93c713b83947604929495a04c37fda61a63d3faefe8388dc01bf5ba165f0a257b56cdc466817221f611c5c7e93694c715bf745058b5e3ad16a0f8eebd3abff381e2f062c020ade2c0da968231ae8992240818653162ad941df0da03a6c874ec144da06a6c9ccef17f3518123e97138db4d47c649bb87ea85b7ef77a1c9c387d013af53ca8651c37fba4434714ad3d9f30cfaf7184bc28a1e849fe40abe0c67f6e63f79f2463868b933ab3211d47b916913e1794ab599b66633f6521cd56cdc9945f6302000347a0a7978def0da3671593c64dbe50eef5e39d674199a525ed394f28bee17e4b039d652cfdc8c7eb4ab59f8ac70ecbc04a6ec60471c33a3b09b988fadbcee17ec163be43c0cdb5bbc779ce0f5002f16007f057bfab6cd38dadc529627305a678fd4e106033000000000000000178d32801a33df71838ad5df080777f3724cf6aa024849a5aa560f8d38b69c650c1de58cb919f6ae215229ee8802466d8c577103eb8986d0483e98c0898af2075a4ed0c4e5e019af4b52ea31e4c84cb4eadc5b1a448894e40cf363fa36476bba4c9e1d2cb0b7744cfe2a275cd32373c6656e2de0f3b7a3e8be08bc397f018ff9665df1aa10c9e966815eb4fae21d411a3100d4e6a506931d97c3662161c007bb028ce01ae737c7001cf23b476490e8654be568fc1ce3852784ee38859db69d84701865affd9be414c8c97cb197c82945e11040ff9b0306b2c28a1092c938bb61a06e2ca1e9ff27c764f0d998484fcbcd1bd59edf8a6ade43b7b6bff2ad1fab1a2efc467d28300dea02de23e26b49048762e921c5cf07ea68612ea088bebd8a0f06e0df53b75d714998e79b922833ad2f28510ef306138830a839799359d81c71a9c801564db7a304ff35df9429f8bd371e34045e3bbd07882e876255dba8b7e50461c85457866c773dbd29c07aa72a4dd28df24b9d167f3cbd4f973f7728432d270c0054957a93696ffd04b9f13351b219e1bd4d37ca9ae70f251eef2885fd46107dae7acf53bac873e54d100c84fddbe7e9bc62857b07672adfb349c61dd13e873706211578dc661c3e32fd7b96919307c20aafa7c8d913648117efdac1aa641120ba31bb61f6260244fa1db945fd9497c6675c702788713a91a8cfa4328261dd267ec6e331dc016507af174f58e594d0311de4d59b98de4486b6afaf64c622755e36591af26d8e1c28dee04c3cdbafcb105232bf72a889b8ada4e44a0c53ba7664b843e93832100e486de29275c0df6c3f4fadeeca404046eb020f90be37761550c439453fe6b713332bef98a8d70e1724ce82e939f0c439af20452b2ec997faa52ad287d84a98716f3f38a652aa07464c047c0e8d99061ffed1f8efd93cf2e564b9d6c52767edee7cdf45a735ade6bc8cc02827e934acbbbcef38fb780797b4ff4aaec1cfd58068b90380b9c7519b770c1155b484eeef764e767a8e71152bb3fd1e55a8016ccc5ee1c10b144885f49b6d346afcb9582afa432d8397391a00100d0ab7c7adfb7707edebaebc4f4a478f0868a4f0b0097d131b6315e1029c6fb8d2cc92100fc306ddc86ef9f48458f3c046d5d89ad34fd31fd3e6575cb179487d17534e55ee2870a59a805c32c7b0df8893c13d7d3156898baf9193e517c364bc7a88deae703852e2e1b2564d1180d32e83501a95ff61dc7faea0c7b29c86b097c3900c5ac3cab6bd4e822449e49f5d8b29b7ec085c24876ca55aa535fc8a0f57c696823ecb71217db978bda14d30be6b922547354e269887f123f7f7cf0c54a62d6f669b8e8745330d99b5f69252cb5f06dfd6ddd78476798e612c4312dad7c2cafef90251c33c401b7279067dcfff09d6bf441dffa338b2ea7b8ff480d6268b36cce2f78fae07f013fb3bf73ae40ffb1ade80db28151c0772082cbfa813628b34ea8cb76aa07ac2d51421d4387c0fba4231aca198b703ebf7f455ba83f0ae1aad40f345885e54f50ef11d6356b5428731803c33570dd8a4e7270a8c9fb27929839fbd1b2b048270bb9ccbe620910ea5ea14b07ae6f066ccaf193332b3ecc0a7ea5ffc923c24f9a26dd2d35060a2869a3959805109c8fd453c2c9c97ac9f0eef632a0b39cc075dc6d0bc97cfb5c2afc9d632010444783ef60a468ea03de5cc0dcc234045aa3d2ee00999c2e9e51ea98c864afa6be5cab2e6e695dd8de39a7d78062ba37d09e92a9933d8ba542bd8abc3dacde32ab8d7a3c8dbfc373eba9c02045cb2fcde978c8e30fa39ff5a9122a21b23b11570b70b1df0ac36f0476ebe4f1f77cb6036d75151eabff5110996624d9b638198e5319b44483351cce4b03367520193e50c2e2ec4e9227985715cead0dbb6e9825db7c8f8a44404974b97383891c21c9dfe9630af78024221554f840c92bba1058ab92e2f2e1620b9290fc2d19d4e88e71c2935378b128d7ce3112b8993617eef37fc710f7522df2c23006ccb6258f32d580d96011415ff29a308118e3662625abc57ce33df203994c16cc3199707cce3f92e95f3d1c4597af790f8ce67cc3d6346527b4b99b689630ab4c21118c09cfe8ae00cec82d4c310f036befb3c3d608da2237c28b1b615c0ecb5157da7d67cbbd2c686a6c67e413bc7258fcd25cb9808007cafa9b92322f2e431d42919ee5896bbe47cbdc1efc1e97472aac7dd2b9a7e3fa4d1ee9d47d64fe851fa5ceb40d68200c0db931a922952411662b99d4b36fc673ba6d83574e9e50d0c4051a89c5268780403e2ead70d460f65b7c1b5500112c1f4a21bcef58c4d71080aff1281a14e8d177d776794cfbed090eba96d06bc624178cbed9869b1f18c31159cbfde5a87665e49f6119ea80c4d233062d26bd33e60aa4c92bc9abc5e8aabd0e54d8b1023adfc86dc0d5adb939b785eefe37c2fe76907662f376f997c98f938e60a379aba9086b5030d5574b671bfb6380b771a59084ecbe98567ed2578ec35a9dcc838c75e9d1e810d7911dc663f59374b9b4ef11971284e65f55657efd66827fcce8c2753e65c56316c4e1bc14cfb701766223269c1f2f3edfabda44233dcd4d07a52f0b661947f902c56a149382f958af1bafdb445ca8d9b9adb89bac7078994f65a097f02f2dc25476038d276a7e3b41913e04da2c549c11c6f0311e8a42299fb608adcc8a279b9127d7cb933128e6bb87b5b4a267b3e742885e9c5ef3373d9464820a458d410125f473115f0a3637287ce7e6a90d98db5396fc4ea451ab1d2b303d4ca022695ff8062565c25aef1fe0d87b56e2e0ecab1387ff7374f1321326fa2497690d890996968cc0c303d849aa36aa7372a72fafa82f68836c4ea9c57a9abe53e62bfd224df33c3c72cafd24905a807944fcb01f39cbd802c4f1722121799f484d105cb08d48182fa7bc21332e6cf1a961a6609e71ad0291ca810003c8fff433d4ba2215610c042bf52ba27c7331d7c3771982fc5452299fbcae95be68e4640d2f230b69e24456b861a81645da14a823f9a13a36fe923644749d29cbdf0a7eaf33ee3802aa7368eedb28a6d8351004668dd3f740950cdd4b1f64d6c893c35c1fd2ef937d7b2153c206e1d43dad7bdfcb824d6971ee8f86e7bee7ad7adac8b3700b0a46688a1ff4a578fabe06826accd4af71ea96fc6074fe37d10405f66904b7b30e987efcafe59ec473155a56f14aaa3b08a0db8d69c2c0f02e423d78c8e8265864f1f3f52457ed11f898133774cdfa385baacbed9eeae0ce7f024131d35079f58fab50938d985aa0ffe97ba457f263bd4f707df1c1463e636b5965540b10204a38439ef2405f298bea5dc5a6111a7e01f92df0e4c782ed4af2c0ac965f470ade15f3b12c580b8d6293e303b0751ce6ff7e3c9fdb2154d9d5ee7ce4e00cc372d1d9269c0b1774e809f45a089466f7923da57edd732a0e7b7dd22c5e96f197f08e71ce82551737aca6ac133ca895133b39c963058f9ed50bb860f88efde9701dbb97001c933954f6416dd62aabaf7e1dff56a414d00a26ba6cdd32171b5d3929cd4c46b64c44b961c46a131f69144e8392d38e57b81a77f803ad97f2ef4c8991c109c7bc24e5eae62cf6d81773804741f0591076fa9e4ecbe7809401b5747e94deecaa395e66564105bd2dd1ca23e38dc923b87d0727117d15f74437c29924e18722fbfa3ea12c42241a8a3e06c445d634631a709012a79c8f2d3447b28afc3795864bdf76eddc6330f350d09b60d8a2d463764a79f678c1ec67c6c33204ec8be18fd3a34ed719952d995d6bc523b5810f7be55ada28a62e968049979d529c078c0e50de151df24d668031906a2f26cb7d49ed3379aa6012bf24874f9ba5645085cf90288e3dffb9445fd86814a86c14ee8209d5864a2693aa03caebf8b5985c6eecbe4247facb5fa0067391025517477bae6447f9fc8b9ab6e81137f45c0fac937e48d9b7ef9b6b95ffa21bd1014be6462c3ed5301c156a98dc40d8eb79406bb5b4cc13007a8596159ce52cd504248fa6733cad4b139e70b5176ebde6a37555fb0b8b5feab24b12bbb4069c7fe4255a9cf4567227a1b724154fb17955e7e8ed34700030c0bfdc3f547e868bf438536e1dc646d00c3611c9cbad882c534a9c44f868a8765d0c52006d8582c4e326c198df35a57c2129cfc32058e054027d35bf9440e210990395618d70c634737c2eebe5f2f9ee29e89a8339832d5c47086bec30b4978fe0b0450c91b040496ca1b7bfe05dc58f5b23bbbab7cd4ed32f5e2b3daed9e3f4f1adea74412298f97db176c07720186db2367ca7202918535a6f103da4dbd428e83fc059789682cbad82332a625555b2923368eee47a378174b04e2296a3301812c51fd279fa8db4f06d5199106fe438817e9e31a6fd029fe542454996cf54d5873511897a01832c5372e08c765e2903d0e51a320130a3c4d8004df1e1b3eb05f034a535a79401be1f8672d05e5465e0bbc9d13780d0b2a91e8d991bd4ccae3fd993ca3b50d5521b969dd705eb1c7877a167287c064fedf91fcdb58e008b69b079111faa69de2d378cfed92d260d35f90efdcb4c019206ec9de90650ccf0b9c429fbaa9ed42ce2e4738e5fbf35e0d136a4f5cb31a04ee36b9bf8d8ff2a42738ff25c83f13e4c620e2fee18f317f70d2e92fa6c8c448f848b1ba2ce073a109632b5a3837e3e957b1b1c13919c1d3a90ce8fdb794307eac8f153b521538ced9f48272ee2f25fa19ebfe17849f3973d38515109acab28e0228500dda96d8623d5d489ce4c690cb5e8338a537d22f9dee7c509fac9e9016b5e67528db771f57794134c01a38cccdf0e396f33ef05bfa80632ffb32596b05b8f60745ea084112f2825b63749d0e36b1fefe288c70f3fd91930f8721bb1392222e29aca9acf11d74b6ab76a9e7ffdbcb74b654de7b9871dfcfb851d84f9258172522236f46f2b2c75716d801433cac66face73f424732dcb7e6aec8f3b888fa0e588ecb931b1604081871087ad1977e673b375fa1feeceddc18321a5c10b981ae12102f0bd7d2dde2671147a511847b528552478c32b9ed2f8165748b18a6721b432742ca9f19767286353006a63230afc09eb3111a3fc61913bd3491b4998d3fb658607371f8d85b93c0f10a73e822af3c2c1d9f584a5dbdd12c9f8cd5aeae358924bb540b66c7b6687e13e760fb24ab4dc4da168b314216c77b432abe55ad5829561539d925d067eed6a6af532f79943dec71dd787cdd22735388b43ec20fe7cac0666d4bd009820e65ebad70c9a46df567cc51a082b3b06d31b6418fd41ca084f72f889bd5618ecc6010371211e90b6fce11e90356a72c3874ead97591e63dbf1cb15a10f6860ab352e162dcf03c0d5dae274f952d2fb8b29175e88ac90bf657ed63b508a612a6e2343bbfb1a3ef4a84aa6b94e986c52c7311f78d8ad1d9d909f416b5f75a3382c8d98916d062280876c1f0e6d1ba8f00a64c09589e2fa8087f8aebdf90808fe68c9406e76c0b49d9e5c5047ac88eb7f68146c09120065fa11485cdd2efff5dbfebac93533d36b86afd00ab6e71d807cc9be47ecf24dddb3c4b4f03bb64da8d39ebb538647403e3031ae788e81892a0b6bf2bf7804d0f6a1afb9bb5a6bbd8d771ee112f858c0bc907e7558281f2f2f35c547cc8b4dd757e06c381c1ca71dff030a7c4be8d14f4b779a99cb8b44e1d923c2783b24682d11cd83bf1d9193a9e34cbeb452568998b313576c5699f38126060bf7ec868e117efb063c922a1b3aa45824486f44cd8eec669c810aa34e2e10f83df786759a964a9f79d849ba9b20bd9b82720264138507614a4d414798b917238c85b59125cd98737a407748f466302000360a02daeaa4d3dcc3199cf9e55acb1aa7ddc745875a6f94178e2077487fe83d313def8b05dbae094eabe5e1f5a609676feb2b7431f0ef021b35684d63df26844a47147388bb84fc5b064875f213f02530daf959590709d85033fbc59033c1fd2fd4e106033000000000000000153c3c7014f44b4b402ed6fa24968f3fae1673912c23086dfeef803e749ab81625a5964c44b48bfeea92dd7e1e60586483e0608816105b31a62a7adc7c3d4f25b452e0dc53b8efee0433c5e0fded9c38a97deeb36ac70eabb39fe1b89f5224a743019ca077052fadfa469096d50eea21808d08f5a4faa38a08d1bc90633023039f6de3e002611e9bdaad2b573a99f4b0780b3fd2e19e6b3ac6e93e7bc09aa4aff0d19a76100edf714a3b48035d82df2f62388603256f9c2055f802aac51607db2c335ecc476a44c6b6885ceb1ba79c994b6e8def8043c67c7e63d7ee73d236c51949afc4ce7d78580c2adf2a129377db99d8b38ff9e026a2eaeeeef4eeffc71ef426459713123d57ba9535d201292f4881e1ad0ae4ed59570141f320f663438fba24afadacccdcc3f84df5366e6133ac38a87eaa072b5ee362f053b5b609218c87119e530412ffb1e5439e9bddbdab7ed97af7666ddcc7a8d3576637f0088a126d8bf0778def345867e4733ffce3094e0265809faca89161e2c6bb3fbc37324549030bf8c07cc16735aece7e21aba4d9a21508962f5f823bf777a1d0ce692e0ce8ec6c14c3a2ff2706201596ed41fbca4f6da33f0839d51f1e8c8a31621e358976d3db640c808aa248329ca1463aef215c1a1dec5038d040d8e6d251f646d81c49861f4245aceaad0604947a6cb05f4b34253ca4090d997541b91098d8a7706b3e5fdb054c494c33c09040706d572787126d4ec4ae9dc7915d55737a3314ac181686228c6c5802a88a0a4df72aa7c8ae2cebb4afd6141ca997c3b5a5a04becee5936abaed832676504979a7df9b8a0b587ebf3adaab03dc7fa868117ccbe561f0796aae91c1286940effb1f8ddc0368aebf9c2b3a7c324cc3f746e14ed7e18049e4963f5fa0b05dc6d60f22dae5ca92bb93b880374817d53d1bab8b4c68d8d8e0e42858070bb90775388134c3c109bc2b47dec0967b4550bbeddb40c93c9e3fef221c0a73c4f50e33613958ce82b821823539ebfdfccde90d5cf104e819d6e847fd6c88d6ecb8b68e2faaa46d07c07f45130faa954cf212df6e6988ec94b72fc0788166a9a5307cc7c8b4810f6cdd6a92b97ada589a04b759d8228f9cded0dd58601995ee69fbaace450f3e4ddd41f85d75d8fbc3710b2e595403d3dff1d3305dad944892ab70479ce7d939d49ba917fe30471b37fa760acca992ecbe6c475672155c4fa2a9e6f2d66fb99b9ef99c9188ee8678b5efea505aec3c8912300f2b578badd63a3ed2c6f8ece99a5202a90764fdd892dbd58839a827b75899ae3f542dd4ee4b80afae2349afea33656d68b212687e6f267a3e7f02641d7fb4ac3a04fa89684730cc395bf186ce081d8d86ecd4a3b8fff66d9cbee1c5fedcad8e250a045dd53205072fcc4285729ce279ee4c19875803e962a1a6dc1135f3f00eb1e0e69e15808d2c889cac52cf177e027b745db026e9ab172d0bc421cc62993bea76f0bf8c6f0116029098d4f0cf26fca0d5b002c4c47589c2014e3abe8e50d676a73c387ed84c1823e1b61f64dacd094bc42f0343b869f5f7c23a9101e7d425f8fd8c991af6ea339762efe3a1d2cba0a6c08c29ae2cb6e93ff79ba87f289b320457b587f496f400f33eca1a7e575082204116a9de9ece8f74ed2006f3b3cf37360d1f281ce285e44ffe48b0a5cc01f9210cb390ede87f0137aaa2171dcdfedaa02fd960154a0a76e04295220db9260cb28c2ecdd32943f7987abee4e55629705a80c40ba46e624beb3a23bb99abaf0fa0896334c7b6b567dec9e6c3ec4ea02d46fa463847076b58be9960a093cde08fec75d569c47d0f2eb688c8d757b8a4c882af281ba3be4ea407aeafed307b80dd66917f2648cd34e0fad578e3f222c33da2f80ee087daf64fd8bcb30e7e3557fdb21c4f155c0f7a2d4aca1b6eb1699029576ef93c405c58d34e90221c0203a2f275747db4e9a028c17d47ea9d576198be3d3be82fb7e6cad1e67111c6739523360568f11df7a4a906f3e4f41b8b2b574282b9189c1a765ee80ecd85bb1eacbb8c28a4d4749238d63920f0e17922c6ce2bf53feed286b8caedb9dc70dbbd2f2cf206534e272119617dfb2783ca1c2ba646089b7f75494d993d032227bee83153482ee6238190a65e08bf0c570e3502f20603a5c634749006e9a56a7d501c18e367512e9d9b02b32ecd145783c5cc07b6fdf254566205d4fe25b357eba0aeba72220153ea4fc1d44474322ab9fa749951f04b7c7a22174b02e82e2bbac157b84d83fd2d99509e4e2b2bd078103f6d1d667fb34e574bfc02a75d3178639b88e2c15b7e719a5ee8211a1ab58c28b81aa78d3e393a92997cc9c2fbf5cefb92d6ae258643d9283e1e13ceb528c984248dca88cc7db26a04c164db90f3bfb9b642d06cecefb23eb2f033c67a79e560a19ee01d12d2fb6f4baad1549326f7977e733b57d5c02c8fe60fe9f645ea7c59c587fe13a599baffc34be7156bd27d0ef92fc1e6a1d1478f4ce15aafe629050fc0ed5406465ead8b9828c9f1aee732a2532aaf2bf89d7cc7e9cad576243d5905f6b332e46f515acc9f424962d2b30bc64cad25bb761a64dc8c93723b97a0690e0f6d8a4ef37833df13e6fc8bae3f023adc4368fa08cf842eacb2b55a9bf52346ba02126e1a2409d3d54fc0b8f7728e8001d7ce9bb62e35c65098e429f3908565e7c112786f31c83a20a88d42254cde5eba3af36db8ea09ce11c2526d89b577b40c977d9039cb119b29270d3e45fa3b0d72d19b664ec6882b8f24fb60a24ed35e4140acc9fc8b580f7d44694c436e4522b59e6e861a06f061d74ee028da4a7d5483afaa6f105fb257bbaa6181b9754bd04e8810e69dc8c7872d3197857eab1245917e95ed911e4451a0a17b624f3984c92cba878b58fc09f1e1075f79e81539d50f41b5af62a12e4db4633d2cee1a91a507ce70359e88f5f1a2e6431de5b564ea2e7dfe130418aafc047e6facbddb7d9cc5d0a79cd981a9c1decc066b0fcd74b89e8bb0025c27fb4be2d4560ed918f55e58e74dc4a606d14cd7c2c60049c94868ddb26d58a8c032664553d644d6838b0ce057f265b6241bfded8933ec36242a9494cb24997ce061c12cd42026d771e6282052718a9a77cf6fd0122c0c324e04bf7c838849da16bd9eefdce831ddb92e19679bc40202be394d1f25b1a356fd7d23630da7860a5277d155057bd7a26910534471c68d3c24e8859a8fd79509e82ff15eb8027668c266d034ead5708bf6924a0f0060c39c74c030f63f7dd60c40ce7d73644d68e4589f20316f60032a6fa49dd7af49d98f9752ec33e025c4c982dd82bb9ff7fe4f6c55604bccea4f5bfeeddda2762940352b1935571d2f9b8a06c959a688b4c39b62dd35bf2148ead8ae52852302de27ab91672ef52e8363304d8ec6429bc0f10d920f71bb85c4393911e7610257d7eb23e20718800ed8c4a0fe484f63823a04010e0e02591434967b12a90c0181380bec86c8314ecb047d1b37f8a0351671a78168575ac57a0d3ca723ff7f1f6c5bc99eb9309adb0b122d6ae81caf5355c2bf1451f220a77ca6741e86b15a98a4912792eef9b05d6883aed374c15b2265daf6677cb7c9bfde7bb41c6170d87d3b1a02b3bdd99ab7bf95568722d64a76ae42ccd94de186a77f81840907bea8366cea8bb3860d82206bd1ad65e1f6b6b85c103d13be6e42fdb0412a65a1b6184b6c7da3e92ab2d0adf59dfdd9369eaf1510dfdef16e638fdd6bacee1d52c6a5883dac6fb749b0cbe88974e80b1ebef364336666ce77094348d7a9e6383eeca4d31f76d04817ee118c32ee71cd2dac541108cf6a399cf4d4c9eae04fc40d39c81114e94dd2679a9fe96c619fd6c4f8762f43099696a57d4405b4a36692b242028ac72464f1267a6e0d51dc80c4bd5d88147716d76a3d2d844dfdd6aa60810ba049b4fddf96591cd01ef4891bebbcd3882f87891332dbf6361296fd3357e7e04a9bd4a482543ba7d87cc034f4b3f082d128fca2b2500b0fdf6862c5d529d80b952cf479a3ee483bc071b3519c9194395c985584a586b311a9f58e0e76e765c8cb5b0010b62b537e03c1a72be6be6f39c73a5f68e6d259e805a903b61464af4c451c00f80b7debe7aa969dbe51061e1e66669671b55fc12dd95d1b4f3441e1c1d0976940d39655e6ed1203d0239969191eb520e637a7264d849e1ae2a19efae945378dfb0aa1c8667d47c1997b08cbfd41a45ec056aae76e52f7d57dc963449e73aeb58761681f0c5bca164c16fc1452213e882ec95f98d935a9b836d491cbaf4ad755bdcbbfea00b4cd97c036d2e0d3aadc39e00e417b3bc6f2ea0b10ad612760c9669b88b0115500f64e227de7e828afb99556a1b16c292e0c22451858b3c9b5bf7636435be61db14febb925440cde40166fe5f8b7b3ba2c77a2fe694ef7449f80a50c08410f158a4b238db7d3fd99639b2efe16fb32bdbf2d901949c9eb627adcc649644e72ee789f8174828f159f548b957aefdbee4d11924cc567f02330ed8a9536726582f3e7cfa5c9270238d6d827ad3cd64cf4c56be039cce0184cc9744cd09a5ef7475b7ee90f1a4850d1e4b8df9a71abbed87a6032408b6e7ccfc498f2d57d8405b1758570f426bb2ceb12bfce967e72daec08c2479c9c49193f3f4bbe7a927868d30ff1406bf51ed99dfe7bb77bdc46982bdfbe14528ff642f4370a89b53a3043a639a0d9ff79b3ff0b25adf406d68fb87c283185c0a307ec55eabc68cc1cc0f3a2c25ecd57cdb14fdf89a645eeb26dba74b2614ac6f8980c85f045cba0c8e6513e3700119dfd6a542748b33e4cebc49c32efe55f2517ba58ad9ffc0fda64eff02588196fee4912a3353a93a84d13fdeed354b0f2a92cf7066b147ab7a7eaa067aeba65c21c4092967b7d029d0131368aa7ec05eca1124f97572bcdae062818145d41dffa5991a346b7efdfde2c1b27c3617a362f28a48eb009944402f4c69ceb926cd76ed2243ace4553febabc3a3a76bf7b7e5f7f0c1a1f1909353f68429f7f0f21b6f6348893c0cef20eef952c179fe949768c3feb0644eae60bf13e0061ad7db09950b9fa2848e6aef43d6d6a0e7aec824de011ce80ccc0975e84a38b1dde2a8a3fed2c5806f937026553adbc035ae305c8a8e9140be9ac705396ab9c9f998a0f4e86a1d9cb0da2dde48e0826fef0a57501cf3397a546fab9f9b5bafdc6229010123392379f3810d47d567dd972801953f3d8438e0c232038fd699d62dae6e6791677a3f8cb64a9dbbc8a31edd54ec9b042a0e50a6eadfe4b171272fc7dfa33cd0b23239ee93a10410485d3729df8d49a5b6ec3c16aa106c7e331b94ad074c9fbfd80370bde9d6af943d88ee6e771c05a3035ba114fcb5c6c488a7bb5fa59e457d275e445c2fd68c0f30ace456a52103eab5cf881f2189529c6a1b5226d6a0e6697df550a50baed2a5d59bc54b43e9fcafa255faa64a8eb20b586b27d9d220dd50fa96abdee0fec46d3eb785c4a9835c95cff9128c0a0eeaa508b33f7599f5861638b6a48318a78bb4cc4853a3922076abdb2768772f47cf95251e7dc26728d91de3420745444cd0a136ecced532784536be30f72b9314a0c6364e4655eec3306c24113772c206f22bf6e698543775ebb67f4a9ef87d3a45451ca7071a5058ed2dd9cf70a7e5411b11148785c3bf9697c3d77833c846961d525f48ae5310f5d811b77bd9bbec7031828969f75f304a58248066c01faf2fce0eabc4b366199d400d976df4ac8342cd244e2b72151e90414c75e277c311359ca7928824d8cc926abda5e5b26a98ba1f4a20e95ed8d4e5603bb0d4ef50adc590c021adadd000ce51a3cf48a7e7a2775c01a5b202a9c4a649c4ca73b3357630200035da4b6bf1799289b26fba1974d152809a7eb87ea87159f9f350e739b7c0a9c8eb849525515126fc76d80af8924b68f78808772bd737c5c2520bbe7882c35826f7372cf2d9c074941172db04d823ca40e3c9d038691cc84dd96798b1494ed311afd4e10603300000000000000012ec49700618f294d05efdfea3d9e825491ca212ded6d798f80f7b1aee8f53ed7fef4af055bce1f3add0fe22076dfb84b4c8e818efd8463ce8d88715e12754d2f12375f397926d9e618bb0f59f0d04e13711c4df0cce3f38563614f239cf51f56e3f5406b702fdef2ceb706d219682c9facd1ff1b0ca17fd88f5caa2a50215757df76bc6a6313891b75c23bf5853ca419bba3ce780d35057809247e2fa00a5d9cfff0b171e301035128585def66662f8a5f7683ce89f42497f28997fbaa6e7500b98c32df37866f60feb97176df5a1934dafb9799f6485d6a4e340f3a39c0e0c6dbeeb35e775668734ddaf0da818b520303ea40b7209fcc9dfe9b9cc9ce8240fcbfdfc4a61bf8d96d6b15794867d2511789facce3bafdf3849cf54cb8b9a6e19ab2e8fb234817a56341f321856db48a224855f129c764e33bac5cf6e3fb1c53563b1bf4f279d5e4df8647dd9b06538a7b035c38fad1e1b798f17f92a11c630e3fe14c2941b39155fad69f3eb802f6dbdc3f967e8f2cb3407961c6350475f82a5b741ebccadaf881bc64494a4523b53af609ec993cf583fa604dc4be4d3400af25fcca5c93d16605870040a09bb505ab6cd62e9bdd0cc2b6effc1820c8959b6cf9e06fabb7ed8c4e04380b51b2d6fa917b2ed5439f43861d6b39301d3a42df22c18834227b442576bb4a3dc2a6ee508a3b2220bd3e9f38e80fa491a9d577c191f8f41ec820f9a9735971134e7ccc877036a3412ad9c64e77ebce4fd73d6755e46388cd86fc2a9e5c3f39186b215196c8d8842817543e722ccf7ebae8b1000383ca61cf2b9fce389d2075c9eb36a6bd535068fb8c0f8158282a587bdd95d00b49c414df56cf923248be7c84d53b80d31544686692cf7f11bdbb3a247d779c6069df8900cc31b6dadc44c80443d980c0b0bfe4bb3a8faf7f3a2b072b3d68d433a6f401c10fda158638642640aeb9899fa1db2a2b510c4f67be45098e316b38d8a88d3e657e624f4c5d7b8f817dd6d055f961ba91e6db384fcc2a9c11d7d6e28c63ce91db1a52d11db1a84cf2f4696a8c3b61cfd280bb80428e26d6ad420b1c29c2b3e46f1093e90bb2f0164a44a74580339c7416ba652fe913bb72ca0c8e965c610295364ec820247be450810e70bc0dd868cdcd8acfbede016c9418bac5b2d464a7f5bef84cad4327c8dc32483ea816a12b3261fb78fe76ddf7185017f4a1e009d2646eceaaff11f180676b32c95fc5cd3020790e5542941cf6f16bb83ddbc4acad8ea7318a8a6c23df9ac229ceb2fe5c5b29ce1bff34e85e945c31ce838f51d565ae15201b4662859392f1203aef731ace9156d0df20a68f58b4d58401ff7ecd44fee02806951b18c73787e365d6dcfbf9df2af8e31b81eadcab3189f125f038747cfc122256e5582eeff56595064d378cdcb0f7b80188d7e5fa3dbb612b961759b2ea630832de5da2e6fad3e93284313ff6573c66bcf272bc1149c4c2863c475a729ce8e59dea0bafd9291e9ecb17b708e2e5add4430df0f24b3cc8f502d0549c440252d1123684bd1b50cb4afcf7a2b789ebc177907d0c7de8383a3c91ede3ce20d5ac3a76bc2c33c891eeef35982eca5a6247af917b0a470324e0f48c17caf43050a11b6847577a540c9f972c48ea616cf24cfeebf6b4b3156b961f0c203b0af7220c11f2f18d89ca61affc6fecba0c82cd5b9fb2a10e1510eddb96069c598a08a201e537568ec3109ca5fba7c8e5c9bd80c12bb7928b2d5b72e591176904377356b9182405fa837e9f78c3d8aed0df9514f7b2f638cde70cc5b172b287401a6253b5dcf75c0788e2230ed64c7cc34d400d608f81918773976b431d73c3cc6abe2fda1e0d429e4047e1a4acf620d0f8d8580676c5a250656c7859699d986e2081d4021a5985f5e31ec60c56d77f7189b56181eabf3c6614cf5c7bb9a2f0eba66cff3c48dd6cb5f7fb1336d988820d355015c1822254cd29fc8dedee92df607e0ec8594edf71b0f2c0d20c7d2d6cb640f83f4d94156ac4611da8cb0055253a8c848bbf13136d21fa382f7facd982df8444595d97b5dc592723402382a41ee504b8595749bf7ac09946a56ff4da88829acac409b23bf86c107c0d50c8fdc38b27f4f626ce0d80b5bc4ddfc13ff96e04e0583df3b574b31836c34305bfb87504654744c1e0ee7f1ce423e2765a564dd06a093a48a62cce18d101af001d0d6da8e0794b1a6309634bc1356b3a889c69c545e385c321f3ab43105e0c0f59fc51b84697a28142e9944ccb24b89b4a2ffa624b024447a775b0f66bf305e62d6bbd95341e4a434a5b586117f24c76d2fe548432fb064ba018ae3d894c5f26e8bc165e552b8378aedee5ac4b315f1d9c126d3f832c75e1138750fddfd4b63090884cef823a5e5456a335312cb6c143b314a7c915f5c8de401bf88938b5f3aa25c5903e2579061a0397ad84db07191aa4347985278531da7852bd96cc45f6f67fb9c3f699681ed5cc1730ab37ab08122da80e73cbe7fb0cfd508cee4d9d1869b3ad9473bd06691db287fd382367ac63ac97f5a11e3cef276d85eefabeec43e1080d3b553ba692b00989d1e62b51b81cb36e2495360e2e138c2706bd1c78a7db19c84fb5ef46d36437a5a9c3e220b4f83773df032c8d0446750a830596f95254252b08a9573003fea296af5388494664b986fc863fbed536fd25d1ec0cbd2f7ec533594f3d64691e7380d42de1730cb05ec5981c0a3e9cd5ec6c61deae54e2a781c085f6ea87d9a018ce40ccfd514b0bb2e4348985b3e4f19e5a0100d85762f670c1e0cd905e89a02e3701d946d2f1aeca82cf0b8979aa9e7716c0ee485c16da08476fd9fc2517442ca99f642abad0d40980cfde170afd4b0835f57097eb0352f717ab4c50655462b196ecd3c681d33a0b79033cdb124cd00fae2576459d77099bd3432bdf1c85833f4647b0d8f8bf191d70df1092293da5406c89aeba92aab47c0989bb0b86002abafef5ac690d03a85ffcb67d4196e1531636df366f4a332325387d4144472b1bb45cb7c504aa44bb43e0d3acdcb275845c0de323de196e877851085d1829b4bddbd02e561deb2a029c4fe8dae1d997fd7b7ae9db9fe114ffdac857b83d2c71ccc6bfa561f928e009ddebded2834061b04b1af9de0ea97cb4e0604d366786b58bc5a4d14c00e3af2f781c12121ec1e2dd000e5bacfd9b3a84cdb74e1290ef4710d4578dc3eb3d1718e609bfd663741f9e4d575b8417cebd4d2326a8703a08459125c4516511964fa59dbb0bd584188a73f7d34d2badf0c059687978df2aa5d5bfe179c0fe5c2d9354b1fe9ea30b16cfd59e35d498d1310eb4dd0d52490d488bf0dace801b3c35a0b7b484506834afb891dda4f35c44e4a6c328d6e3781490b53a0bcca409f5e4b00197413a2243d53087d218c1f9846bb789bd29e54d30357579cd65eaac87ac4eafedd7a5d0ade0fcee3cc8cb126dc0f538673e1cfbe2fe5dc29a3424977d0711d2ffe7e2536badc6b55cf4165ec8c6d2878e37e196993a1d65e15d6a72a218806f230227c4f0e387748752d26b93ebb4e69bccf2295bbe29ba4f112d35303ce9c43a415f5569cdee77755e64f99f09c10943aa8af89a572bd7b52e9488e364a8266e080fd580f36bee43d5d5090ffdab5f7daa3ae948be9c7f5697b68d84817ee33c53558ffc08c9af1599c4bcbdbdc5a28339d1e54c988fdda938f0da175cc8e27ff211577677cc720cb4b5e3f024c76c83fbcbf75daa28a3a745f24fea00340afb798244b3c9b14753ed769bcb2efe47891d2a513c3e4e98b59fec6309e526a52da5d009fecbde224f595612bb8fd8712ebdf1a62957e37d471c739a3b4f059f04361187ccd292b783d7d250ca0b602b4891749c30150b62ba01b7b84c6ca2de03cdb1f3a974a94408d7c487f2048b3bf08d67094b4fe7d9f0243404f6caee62a340837ee76c7d9d95c992897383114085377fb49ba6c7f8c2c40e8d97d2f5b661ac2e5fc61c0114ded7d38439eaff54c4bbf00528b7e99572fc42a53e2036cada085285a5cf6c85f2d7923e0cbff49c486f06d59d635974f67c7579095d599b0b6b9fc7ab2167023bdd36577179c1346ef755704a02973f5cdb22b72b404e81440ecf83e63c49f17921548739744e45f755e9b9b932369ad0edb4173964e1003186b1ee6d9dc755bd10b4e72bd7eb6e6e3242a9643e1f9929ba9995dc73568a65b02c4eaa8af15606779a1a69f5fed773da273fe85cb72d1df6594d4954b5bb838005e17e2d8f18b963a417412c616f346751eae2fa15b0cbab26cb68bc7cb745efdd1dbccddbbf55948d3749b8078b6d2b2929e09c91bb1d13acb184ba91b4fb9422c59ee785f26d77e14596d459a268a726da71b8ceaae00689b4c8d8782cce15f1376e8d384ccb45ab415725d38bf5011599ea568a835bbbf0b9605bf3ea7e694b3dea4a434b0fefc797c66f81d66291257964674bd37741069d08be03adc5646f93a172bdca6a5b654c62f4bae6a35c05f17e9151fd4a03bd528ae7bd0296a5d883bf750fc4dbb56059422394cdd77bf470f0d33ec7c7cc9a9cbac1c8d2fb6c6a91d6d7017990eb2ebb5c4110b01b5fb1752a1953d8228c46b3b1180e956b3b050077a15a6b93539e29f60f0ca37e8c47ec4bcb33ad598e2a85bb29b259928a6c71a0b92f35bf5375175bb3caec503f917ff4f9c232146ebb6eb855c4cc39a8014c9411055f7e558df84c4aed1ad71afea0d1a3a8119dbe9f0bcd0bdb80218bd00f4cd38108df9bc6c0c76a5c19de120cfcd9351584693b6e9468a26b26e8e0269c5c2a45dfc0a47366d64bf8f5fab5a5ac26382151bd9ed3511ef45ac04ba6ab0dfd4c9c213a6807536c21b56d2d7f8e51379e92b7f7ba2f7c0960637e12d17dad9b828b4e1fb6d01a7a73f0f39572fe9a85016362ef98d332c589da7e1eec561fae5a75a50b3e88ea10f755676f0a6cef0a7422a06e39ebf3d51baedb93cece379160b0d063a31fa93b81c84b57fdfa946e721707dba0d459e9ae474ffd7627aa071f342cf6abcc9ab92b92675a54fc60c9937855ca0b9d0e953d181ae9bf24498fcb7ad313e2ec06b0eb00e4d75b0341b67739c5c80f22f9a181b2eeb1ec8722c754a31b8a16eeedad6b36852a1d2fed47eba12b95202854b84f68a4cb13219072b25b9850829fa176276f2a4f087ddc74a52df87311fa0201c1ee2af7282427cf4240fb5cc35fdba2b1145c84166c8ddc907a0e3192825a94f4e7ae90ae3e675f6d13b8c43c2e14aafcd5350a388b0f3bb403b395decb8facc2019e21cc36ea809871e24b9eae8ebbafa79aefff7c548496df9c233432509151458617018427bb66554d77ecf7f5ee3676d65ef6462d87fa4f0a5cce5942ae172159168ee232ae345d59294f9bca9ed887ac72036546f087cf2989ab3ea84a5cc534b68faed59f121d032c2c3de49764ed492060fcec4e3e6fd5a239ce85684491e92d08d12e1119830894c7665fc173b6affaeb36c1859cc48584f7a7b92c95e26c963dc7a128ee788b18407c420806677b7de70e7c3bc2063f1bbba2003d325d67d60d936653b2ecdb9caf4444835cea92455fb7690bf1a83616d90d98aedad894304f3617e0dbc6022a9d07fe38a32034831cf319d3235450fe58188ec2a6e0b18c0d009dede33b8a894f9af1123adca900a4096a4aa78dab53d7d3da0c4ae0973cec8704774d08dc6a7d0547175f7a410cd834883f2e5a9eb7e79ee23a539c31fac6590215b6a4a69c7ed1c686ce08c14bf3c2d302db6959a6f2072d86bdb857af8833fac98dd67fa7f63384931ddd20bb7a6121e2d6bb722f2a0f13f525a6b5a87f6acd80bb05740faeb45b6d9b4f6d2115674507e74ae65f630200030e7c24c00ce389596fcd502c084e220a9d3ade25b3dd9951c6e097219c47d25a0084a6ce753affefe384bbe0d4287760870949bff9e3906f5ec7aa4214df65dde4842155a4c83d70cf3196cabd4a90d3280ab8220af6e1af3efef092d4e3e412fd4e10603300000000000000017992e6008992faaed376449d93d569185acc2c58fdeb3bfaa1ae8f1963ffab58d515bbffca2ca7533a6719910231580cc13b1cd39ef07a2ddc52a36a968bf4101c071af8f9630eb89b6db3d26fb7fed0e9b9662155c519826aaaf2df8c64713b18a83fbda62fc3dedb974a844704a48ce0f99a818c9ecbf14bd0f180df63c3bd86840e938dd27011e2363d47f3507cd69acce1315e592563130a17ca626bb66ac69a154cd28c01ec24bbfc867d8ab7771ed00047334fa280df18bfb8ad1e689a9727ad4cc6024e2eaf1ea1f4fc91152a99a31b332d3c52fbbc84e4d9eb387e364b207aeca5f75a4cee87573ae3b36054f7597edcf63a1c849b5f66d0119c2190aeaa31f4539aa8016068b97c89a1078747c4c134f51797c05f3000f0e29f4ba581c393b16947afc95384a124344ab541bfbf1b147659a82f32bcdd2e4bdd49ceed79a62e4f583e91e84f04a097b0eb063d6cf5d8d106962d5edb82e0c322b83d6356778bbfa566639cde4608e6d5bde47588dc0a895e25f4bdf368b81ae99f70c3de151f82b1432af68cea344148ce40e86deedf720a3715da2447c91d42660274c8b70215e2dd48e9a96e255d89deaf775dd1be27b31d6e2777b99c2196a91a44d531d730010bacd6d2ad35650e0b958224251761bb6a1a610b6ce8deec69e446424b0b688a96a46faafe98d0314443566322dda78387097f0e794bf84d097b5f8153fb60358ea889ff3dda17ff140b99c112ca231684788ffcf37ee27be5ef0df4ebbdf820f30e8740cdc6bae3bc85e4d6bb0d8c0e5ee1459c859f8187aa7830e9ab80e48566f298ea514d81440d0e7ccdc26129387126427409dcc84861f6da0a2c6c512e95f5af036af0e407215630eac4edf062ae4cbba537c23b3b309db43fa67b3e9ed37ad553d3b1308ba95f6405f65c28c110adce64092609007f31a67346a8ccee0f5a521cea8b392b386681398ad5138a281001d509f0714d31618b42f432b382fc7bd2f3231c2b1c1e44b7250074be26dcd8ce26a566bd3dbc19b59b5561638a26784137eddbb90e8b5e38f7424a0b7023fb91210e9e99ebd579b77b113ff6d925e0d607501a0416fbf7cdb7ad627f714c094464249688fb211b7d1647a5f1c6207d3d3378d98d388cbafc33a3b94d15f589e9d3c414295e93298bf0a1b99cd29d7675d04da209ce542285d2cb44f36c15986bc13d73a7ad1073fa5919576daf352975eafae480b5586df20ada6ae9a0b05d4a980410818d7eb17d9d9840d321d9f937883fd113260ec4595b997c0722416591a027317d93dc1cd255f2d799e0e10c52cf6a19b434f3b05ba33310673eb3117d5d3ab43bed583345625c3683f7fa7befe1cd156f726c78fc80b8c738dd8186de3cf76a6233b8101acc488546c0dafcd574e723d7bae0763e85b4b875927d45b5b10aa115750df69d51f2372f94141ea03e696e0b917e1e74d1f1470225489d0d4919e3e850b887c61ad78f04447b31454289703f57e906b47602979b9cc16d7d2dd6a5e6837e50ca8143b3abddde61270934e20a8d0e391be955294bf367e69ae9b957563177baa17406661e4c49955a98ce2e6055394afe52a1963a45d12829ca03bf40e9fa7564f163a1cd68535dcdd12d5ce62f780a768fd7d7d7cf3eb3982773d453e830ad3d858d90eb1fb57370cc45cd1fcac61835210e8e174addc7b6d5e724227e678c6627dcbf6977a3d805e74d694bd8bf80f3a65345700f436e9cfbcd5b0bc23b025b09091a26a7d88e08729d51ef1e6ee86789409230b2b6d0edd8de3b6b345560639cdacaa115a186dd97ff10bfc9d679ce268568548a50b193b5d07e9e5299f400094dd8dc20b090f6a8b7e825e4fd30f016bd67c6fef9e25e3c195b50cdd5c9a2efe15080700f878871946a5dd5d7f869fb0ce9598890bbe957fb5d49cc4174238e3262f1a3265c492116e00d69e7aa9130f58f2d9579beda6e580a7edcb884c79e5986dac2c9785521225f3ba3ce56391d5ede1d3bb28c29bba067134c3972fba912ac865a3f9fce68af3209b690dc46394c4dd955010e9e5f451a2ce02dd23e36d618072e79d58e2902bcef5e5de18615acb15aa4db079f9470ed15be7415e18bbd13b003fccd00927877e0b02f63068ac1a806385c6549e7977f75ad0e066e6692f0505b7ca4c7e94c336aee5debb8c9d665eec0f6253c4a435c37645ba1c97be6e60fae6e640938fb47972d2cadd5c1119c359b7a732bf0c8d37b8e124aa928c29dec24e8fb0dfcb2dc1b27c8ba1033acb7d91bcf549ae97186519ee9d257e9a17933047f7e30717af72dd344baaa7c4284e6456beca321105424a6cdf50e08dc541fae563b108ea69679e6233ce303b294f73e7ac169af780936e9a995445e93532368dbd91fce3055e373e3ed56f8d5b0daa74ecc583a6744b99e1201bfd8f67d5ed0411802d8b4ed5fd405dbd61ea890cc0b7a0ebf9e42a77b3f57ebc70286f49e83315550820e2e9691a8b3218d9def9b70a47ac3d0f520632a722475fa619d33e45c65034c7fa5621732d2309c1106a50b2a4d0a40cbd42c4caa9905c1860e162efb40013b138eb9dc56d9d4fb5e33b275c7652079ca6c28320a9d6963ea2e4ebd25b244f6e8fd5abedd857d02bf15d78a5b90a71efe84631b48524b3f40f57b0e8268cc607dc0a7abde35d77be2a04b8d13a0cec161504ece8e96538ebc0f1797449b21e5bbf5cf06edfcae3bcd81898e38fd177fa3d3a3a081d9f53635dd7984f6fb2ad01190c43ebcd6198fd1a95b95014e1fb9653b081e19be9fca464d7cfb51aae485c94d7e37f14557b2df866db39909c9ad7e2d3e748e24237b6aa982f0e283fef5db6a5085316af0554cc35b84fe8ca38864f53c395ae2a642cc6f9e886b0e7f81d6a8fc11401296011e7e03ef5b327e467e153f5d612b190950e335914c7957719d837e7868970cc0ff9fa24af08e105b750686750340c09088b06cff40f7b5c7ad7a6b8e07f8dc90b070000ecc8fade67e63d4dacc07b855efdde8d1334b2ea2d9e3a6d57367fc34ce9694bf2735a43d7fe19c8c3d35f966f7afa3210748e01c0bc887bf9cdc91b63fa0b2e02261dc534f559de72d88f248b8b9b05d7a81212497bce643fe89757a9b825f5bf3e8d84a129bf231f6efb13fa478bde185c62101510d329d6bd8e3473d0db847eea66ce2d1975232f87b9730c3c7e3421296878d1e5c48d4250b34a0c4cd21e7d7bc01cdf4887c8a954db53c3257549267ab405b07e51d71c90ff75c7db5785549381b9cdb348b5c91f8d00bd0884363b2a9f928b05fe81d9e3de3434cefe37b215b187bb9062950877e42beb0c1335f7ebe01fef8de02df37dd81909b2494ef279c496e2970d992ff90947f23ea1b1c42a0b75d5ac8ae52ff66d86dd0b1aba76b575adc2b01b9b374b93a069d7dabfa608ff37184902382aadb582dd10254f426012b3d6a6e180e38d2dc1599bd7a2c4ebb43d6d8d066340d9464af685758fbe4322e788cf7500a6ffbaf04f02821f72d9d824467a6b7512285298c78b06dac316da4663cd3714eb25cf188cdf7b5545203963596b75280b6b802aa3f6ab0679c9d8c7195702a4e8805e5e129c156897565e61f61397b3aa30f09985f6417b2a26842b9382afc56d2c7d3d4fe94e04da9a4a91319f8a0b5c3a2d38d900d56e4c87299ced5f838c9363258f61f0020b15270f5e8e5ff30f0b0e75fa453caa0e60556237c417735a3fa3ff07b73cb7517b47fe973b6998c4f536217d61c379ab47b04c7972e68839e28228be3e9a6f7fd3bef7fa2134b9d705d400c4a08e7150c8fcaf5f10a044fb39e36920fe7f17bb0e76faf58af6ee125e9226c0cf3aa48d46e147e00baa95e07507db9d85ec9bee0c399afe803623720fcdbc54c413eb0d8abdfce6907ea6461244e46b8e429dbe7a2bd145e308124d59eddc9f1b83f070999abedc427c0fa8bc5ba83e9948a159cdb29992dcebf35b90b5a8becd971730b055022f879b175c4b31b9dee5cca39b76641a49d8e99dc4c9a3262ed4249a84057c9fed72a28fd3f82dbe472b3da5f841b85dc7308dba06bee6389c357f33999236b287a998557594af60ea9f529086e06e7c4e5bcc698b18f4737baea0f613d4198726116c8a11cca7865dfe78567d62554aaccce463f29d5f9dcfc5b891429e579e31dfc3df8ec6bb03e37c6c97d5bea613ef6c6119bf8f3e90fc153a55750c2f7d829363f74db4e04955eb3908bf3fec12f1742abb7e6a174ebd30bfe56dc85f076ac809724dbf5c7a59b6915106492e5fe60daa254c963d1612b70a39124d89340d68778510268671019991f2ac15654d2aa699646814860f03de6639426d3b6b08f8f353243d556ac045810f5edd24c5550b50f7df9f53201a67da6279b864ae28281dfe6589f5cf7082883875f17826da76e1dc6c608aad26e7235ae883518c4cbff3ca930c74e67acd473119a665965f61fa8078fad135143e26163bc86e170ba0354ac373f019fbdb0324b74071a7c52607a0bf45c5fcaad148adff114e8c90e25c59f50e8b57511a111464234ccee58d3ee4f44f73fb9afd4d5604aa61e11d08154c45359cc1468c2c0d0e16f80a0a906c49f92174a6bf858d1b32ed959d5fefd0763852ac5f4df23d0c6ee5e093b3bce3d9a06d609a4df759a103422f27d52f702a9d7483925ab76cabfe39ea8ff885e62a9b8d13798dbeb6c450561720166d4e2cb931c11036c8681581d9520365cdbafdb2ad2b3a38879995ed8fcb0bede44b505723929b384e1aad64487dee16e8d62d1ba966cf356171de763ca7511e92e6f350f0c2d1272a0e66205de28de0fc8bf5f2622e9b5ae2f6b073af1609d241488704ca61dafa67ee7d732de6c944f825b2f124a4c6949646c80684f246888364a64ebe3ed8d21aad7771346e6b63ae009d5aa1b845a265c1162496fa638bed18398f59a1a6172c37004774405379c1064eda243d3fd5d89e509b8b859de15660b0b0bff70e58518cb106f32d1dfa887353daeafbe22db58990635e35c237de270ffbbb4d1bdfc7ad83338ca7b8def055acbab3c518b0287931836233fd99b69027263b33c9b92cfa1863b0487271c911ce18a9ca1d326e1bb6a07f59d72425e6661001ba80838ace3d9e31a45e3ab639bb8aa54528a9aba8b28e654ad476826a0132f283fd8f906796286dca6a3f378c82feba3e6b728a84614fe4a530017efbea1661a4930864093c7b6ef1d6083fc8a7b4634663a754a82988f7d17b54b7bc3d2bcd851235edf47d69e84e0016703d3a3421b959ff4fe72fa0ff16f1795b00adc2000eb3a662a89dde7f173bbd315cb9246de0d4cca4d4d39303ef1896e3c11bf8e7965e50f7913cd069edc94b47989b13e04ae3fbd8c3be2b54ae15c3994f47cb9251ecc10046a06fbc0eb793c039cf341939871e0affb358bf67ab06d99acf9ac7188bf03d967a10bd38f9b5538e56e41cad8be5176c57377561bb0db6e3b38db083ab987307fe23e5bd393dfa1e839e3f427ebd4070873c023125bcc5d71cb9eea4e741e78aadb6a66b07186156260469e552dc5ff4b47f6fe22c4ff888d6b809d473bf7c849248fafac4b5c1f6ddb92f3ad736a4be64d636fe26d72eb38fa9bdff999b39d1d42de244b12439a09fdee334079a2ff3ff70bd577ddb6fdadf32474d100dacf5500806d08c29caba04bab5281f1e5d01ad538b39a8ea94a8e2f43bc4c4a0079017b5df9e2ca10424c22269a1374e5983d36967181a97a703f52ada939ff2dc4316102eef0aaebfbcbef30056e388c465e69216dcd5d32c215f72781b71b07e7f863700c27f726d8ebc9f71bde442c7e909be84943f8438904173137ab28420cd263020003255da9d267e04b7732668549dfc74e6c25213abb3cae0037a8d16720482ced449d8675c1a1446067f8753abc1b87ffcb109d3740ea5027f8caeebd547239eb3824fd1d0c7d969e29ad7afa85216e863d753a5d15e2d38d5ad2cda648416f71b7fd4e1060330000000000000001c33d91017e2842b264f8eb8f263abae58bbf3cffc1bab7ccf867918f1e4f3a0c0120220e95875c38d7e2139bd0e006b13275c5e79991b60d86d71d0fb714daec525fc005addeafe2c7e6e566c85f7b42a03e5669d7f7775ca8573b2886c9b4db5c4d45620bbdcc350d481e79402b83b9f9ab1392e4def529dcc06b86d7d50e06ecd94e2e167cdda5c6138f3f2905027876a95dd284e847688a5670ac076696fb1d3758cc380c6fdecef9c42e0d2c33f81b39b9e132efad3581a0e3f6cadc18d949a9d3a5c40b715d3462a7ccdc62b0a04db16c5487e15349a402697a3d7ac367e1c2eea4db2afe32cebb472d95c1b582d1904354b54a7d38fda4eba9ca416f68a157bd4b336337ce9ba0dad7dfdea98001e07bad7123ed4c16b5df86a2ef65ccdabf660da5425f5f1a0dfcdcbef7d912f6d221e9e815e6dec766bc28ecae23908ceb246541fab61005633b290bcae07e680ca879e3d62e9389db886c236c324bca5a8db63c831110c751b0a0f795258cda457f4b920249ead7c624ee5aeeaee67f1baaeb33eca3bbd339ecb0cb87453906171205367e0b68700d2e44b93975727caf9644928d79dfb7f2eee6f551de021547f22e5090f9150e29b43acaa258a04f28aa9c3c8f543bcfe72278ad299c2db3194936873025c9c30af582906fa6e23d697dc397781009b5ac71e0a95d4194b6a823344da52fd8cbe5f185bd06b238061988b2381f7176cbafc140c47fefe826383fc5ca22fabc9b66f74c06662c6fa45659c0e56d454026e62241d97e089892adfb107b5bc8742ccbb90eab43120969f19cb001119bee595d6092806208558928cb37482931e2b7dbb4207af09c745509d59875e6610218abb2ca3df14802a55be197be2894ce73124cf41f91a3ba09be6b7436e6bd72f35ba37b878dd198917d9ac43619fa32775e2d8fd82eecb03795104b5d88799585d5ced9614e62fd68e16db8d6f911c3bb6a2cd6197a253f30d3f615f03b239f798901820fc78dac3e622961fca281ebc5f63e4a29d7c3117513d8c07bf7f8a74748495a1ada326410eb942110bcd7d72e367493dcd2dce59cc84fd40c87b025b4d6e40fef826cb0d5f6af2113bef4b8e400187a84ab7cc215ee52538d763f4d7715724892a4fbc29f6be31b47b47ef1280603113736f4d51c3e0df6b0b09a0b1b84b9e0e9b531d32e28b69df6504489b219a4d6d9e303b7496244b411a193b1e0095eda316df21b42089de67501607d1630019e7ee2007d4c9f3f3ab6f75d6bde6103bfbef0655a3320156cc9e17c00efecdf4cff74da63fd50d062ff051610f76f20ab2192dfacc0872bef33232260811490ed5dd9cd61e2d5512a74e05f1cd3caeacd5d3e233f936f1fb301171761c999a894c4bb274254ceb109ecdd2088b9d862aad60c28c4bcbabd9707d979c7b95043132798171ac3f48f4641230e6d9b10c12478e79130d16b155b4170f8cdcd0d80aa78b912949a51e76b9e20b669307ba2a27da7e12ee49a917deb0dbbb090fc163da993742205f20da0540aa08dad582d8a164c26731203f3531f52bf4f1a760e788131bbe3bf526864aafad0bb7d5837885ad0504f6f133259b68c71cff5784e5350f8b58e0e86ab390d943d33726438f0d23bb84d64dd969a5b2fe198608e28dff3b5e1443fcf962ddbe9cd52bcee7de9543892dc294abfbb6e2e1ca6a5b3b640fbd1141e46a6f3770dfd101244d31f70d31b2edfff3db7f473d8221a0ad9c99aae726377275b6c4e6fdc3051776c1e7010cb46687960c0650e09e054931f0b40f7b717e5ae3c25b28df31b387845ebf648b7300b612306c30d80a4c19a10e84f7149c9608d993c64b7713aa1595e101bb43b3e33ba0f188026e3d55d5f74f72eb33bb16389b19001d3ba197bad43a90850bc076475e375c4f45c9753481e0e2d9890720495cd19379b2545005d5d2030c565d01cdbbc25c57a0db7e0ff6e9104559dbbcf47cecea244102d27386f3c452d60a29e8f618a779472d819e61a8c8fb3f184e8db34b02fe51266849ec90ab99cf18e484fa2df59e9c2f69babc7a8fcb74d176ad6c5a926dbfadee8a53cac71d4bc2255b7ad6a832804782df0f23891a877269ca3fc1f43b80fbc5ec21c14f2be8342318abeff5aafef0217a04fd83ef9bdebe9d5484d99437558abf2a129300ae82375c40d6c382e10e4c33945630e1ab8bcb2e8e8bcb17d0ffe2d131b4ce10d88f99932707b4be6468dd39fc51ba9eb3ff75dcfde7b771415854f8e4b2ff0f7b1a7222b2b30be82c3ecd16022dada430e49f45bd5fc5dd0f171c5cb4785970ed5861504021300db6d5ba6376a4b9fbfb1b3b7fdf3f80dd09c2783be3e19ce0e19628c000c8c0b607b67c3b36f6a41d7f364e58ccafee04e554b8e293b609fba4cac4f9379f2c823d190bd321c0bc9aad08373f5e219daec83a27098c346ce92efb75fc863691197b89e9c3209997411bcc1348c6d96063697d9815bf466ff4c9411901bdb7918fb979ff0c942cfc687fcb8aba9e559810adbeeb60f0dbc1fcc8e2732d24882ce0f18a22f133ca9e31a7da1f32f98790dcc48a4fba217e7342586433063168e58aba3c646056cb94fec4b4594225a2b965f3c88d79893675d5293e947094717bac22aeccf8e3edecd7b9cf7df5566ab56e3010613b8d355208bd54cca7f9c749321be7544ff2db934bb2329b286e08a9deb21896e36564457d1b4a01c26a1cdb10ef41cacfd8f572c2fb749cd27a4537fe7a67a9ddec78f890d23b5d76185cdf7e57943b58f9faa90d6afbb9e93a8226bdeb9de351c7ad2149ebc33b824c952481d8f2a0f959061663d9e0d44b9b84f7b0392ac15ebac6e47137a809692bb9d765d2b03b2b425b3a011177d7eaf646b24035201500860f44514f9aeeaba7d30ce91c020b2785867000feb7b9317870287aef2acbc7367327e219ad35ac26e298c88f8a7609206a0da1f93f5ea90f2a9ab9dad1b7f745cf4e8cb2546a534f8a131289039302855673326520d93507e888c4451be70a140ef25a6a22809f92728972e8bffece5cab549b56e0daaecdafe23d9dc1ec839afc3485a0d26807b8c945da7db0989781441bdc9a961a267493ad516cc20fefd5b52ca4e56d66eb51c2efc285cde9688d27f38c5f957bb963e5cb5f9d905de7dc6d29cb03dff44c9ec0acc89ceb6e8e9b4145d1385a8cc3128e35795e9e9a84fc456f5bc766c0b555b6a60a981e3d53705b76d38c26012e21d15f448590d1890d5f6c473fac3ff4bb6088b1dcc6e0a12a9f619864a61af92011cf93fdf3c39a2a8f93098afb3414f3f3b3f1059c2f02f5e3083db3ab4085545415c372b0b3eb7c3ae492f1559f72742dd9877752747a14e2b275cc044911250173e251cafa05f261de33808a3a0f86f99b3ce1da8050292712ce67ff4ce89e09727deb9d6c2880326d17d4a404d374650b83927ab47e472377ae6700b48e0d9bff66e354482e1bf22e3c2abdd4eed3eb3fb50093764720fddf2a88ed4076d10c8be2e0abe1b16cfad4380daac7d1283ecc35b6aa4f5d8c8643a4dcfcc0646c29a9e51796d1f66e44d2b1f072238fd915ff57e4b4c02b771aa138c92a2e95dbe5857b1c420e95abbfc8cf0eef3a00f26f3b23f711fcd531f8de7ecbe9b80edc57edc5922bf82ec0f6938556ba94fb224fa3fdfabf57bc67bb2a187844b98d387c53281b30be60a52b05c3d1f888dea96d6381c54d4beba4283e698c251b381c7bf30bd24a7a8b2255b153c49f63ce88c25cea6cf0d01e5ed06e6267d0837c1c7c0b6535551395ae5ad1474f890c0173ec2bda3398963378f4116a7b8f8aecb9c1cb40650b9d1e68b2e669478240d28eda84a5a39ecd4cbe1eff397c31864aef93f836b6c44862c3398651006639530426b1efc96329a2df19384d50fb5f970ae5e8f709b121bd4f6c5d3861ad361927c015ed5be3c5ccde335aac4873dcb3bd3d6e2a503511109aba866ce501f56413aa1431fdc8383d60dec9fa2b1512c318b07357d377b1c77fbf9be17ca98a486eb586b267b14bcd57d9e01014527b84520e64de90ec7b34ae18574e58921340dcdb5a336cb2c1eb4be6f5da85eb438bf8240996a337521aee3e88ec8e7b26ace05cfb9db207d4562f7d82b014f45a83b6d2fa6a17b7fc0ae184da3113e73d9b07a4574660a193b7423c1ddecf7e461091c55cba7116a4df76476d779710570861163b3ded3a29bb4d6e253536fd47ada2c3fc6d3c05a18876767ef9693936cf11f52b3f026f7f126b0f845f01bb0b020caf22a5fd3df9f8f6eeec48dcde4d7ae98514db7baa8bbd7a5736863893d690baa2b4da1837377d1a532e474cb70d5dc6eba831d42d80e9b47cde97fc6ab7d24d0c1c3503fe048accaebeac4c1008e9e74acf01cd4ce0aea8f96ea066df63e8182428cbd2997d2d23f4c7315f6f503fe3b99322d43a0237b0936d6a7018cf9f11d4ff4d794f8cd8ed28a1f739597d1aa2e3dc9532cd77d40671c3b7f7dcff9aeb66ad94926d7a772ff02a7d0965f5346520e2f0cbf8460823091163dafdb0a73a93dce53157ec76330d0c19fa05db4ce6375b58f1cb1037e12c3d7a7cd7f6d8347fd24c86bf88929779a557c7a56d2218a8fd9de4026dcf64554176b2a5e666d0b9703ab62048bd227f42ffb1d8b74bc86662845ff510326347f53c1c012d9e1df0618b3f48e80f846d5a36e67353c6f484290d1a43488a67bd037301bfafdc3873f374acec41c620b984b722d12e3c37307d01bdefde0ab81c94cda747a000822599fdf34699f4bf03c1978aa10f9a14e99a225ec37e4acc2449aef3e7af422c5145c897cf06ce5127d65041a2685232eee613bb98741d9149e58c98b95707e5d584cac8b766028cfc369fefbafd225adc9a2d3ab9a3e941255fb57eb77d96756155b05fcda1856df92b19fedf6d1d01b47014d0cdb3cf8005b0684b43bca38b051b0925ce2584709796fa64db54f6f558601a1f825d42cd22fcd6be23b14c4c4bc58a49cb530475f8fc0dcabdbfea8f9371859fa0f820f3861eeee832c11f727b7de79d7767a907f2b0b0b38c4d4f912c9fb3bb5386efef15f516b6908d25140916591a6d504db70b0d19c0eee0293cdbe6b4de7a9088b5b12a2cb1e6ea70fd99a73275e2d4f6021e5ea0c774cfd6aa52b2f1f672e099d8db0a54ab4c7901ea4fb2a4008185eed5bbb5367a0789ad6ade9c658c05e323a7d31bd8631f56b0800acaeeaa991e939062187b81babdbca1cd35539f732904b3e19e95c9d474ee9fe5f22b04c505f36e427fa69a005fc731999a667c485e49af6ddf56e56bf0b431b871ad5d3471d1eafe6071eee3c9184ecdd7a1b5a8fae6c1e7d16923ff97df48d95c85672f75ef7f9b9a07d8640eaa080741ae93f70fddc28ed34d7fc675552523a38804318a6e3ac41555ebcd41a07eb0d851ad46be57df683e2787cf0f229868c72b65974f7693086811deddb36abe89f3c280cc87b49f1dde9041279dbb9a08572fb4504c5671e6800185903a72e604508beff834b0498a5463d196ca37675e664e8ea51a20ad8d771c9caa58a20d27735c5523f9df92b1302c83819f5af03b1f57d3804df49a8ecfe8e3615d2f6bc39b7a04e13ea22b8c14a7aa6ee86e32f3d88983247982caa158d821fa6ad0d9e452d699f6eb37f669d27fcd5c9fa8d4e4e338d43928023d938c752daa7fd628b6f3011d4bb0c39a02d8156a5a998ec15459259051f6647bcda8fad8358923c853d2a96b397e6f8f44ce6b0e0c984950b70d9d56ba71a397c9e740da8bbd17c56a4f2148bec5365afd341e0c33b08d7b18c092ed8598c0765356f848374ad2593665c0015c9ffd99268ca6aa25eaee6f4609563020003acd850bb5a3aacb206a189ce5e3f128f9cb909bf24600e03e5f24f04d904950cfac2430d533f0fb8acc2ebf8eb43c34bd65eda846d38235d473cad374fd1eb10ae9eea0318b89745d01ffb06535d38928056319575b1570d263c241a0c5ae353fd4e1060330000000000000001c6d1f400769c729e9412f8bbed33d856bd7d816cc231ad0b7c390506dbea00b4286a0c675b405358ff3a6e5a10686089f67bc8e5248fa54d05617915c96bd18a7153aadd43d91bafee548fabace1f2309b6eec4e60fc775b963d8c04f1d2559808cd52a99578633f2b22af7caf11cb42e399adcafd2ada94edf2637c777f5b2ee79582496164c0242e6e9b9b0f9855a758c5d60ea62d490a33fae4f92d58b0f4c26d250002466fbd93921c7cfd791887500e80732cf44e690d2b4de921758524e17c0f733d1bd00a200953af0a867c6e213721284a179ba2e931af6f9d75768fb9136bfb494733d86caaf2d9b5298f4018b5f6e006b9a85930f3a6f31d1aa843cc10fc98cd44fdb56d08241f5ecf41c3c1dc81ad479aac487db16fe52b9ee1312b0fcb68d756eb88427eaae3b2dbade315eeb5e740a74df2bb984003a0697b77026655352a580e17f9cd4d50a9c1a68fdaa98e5dad4e44cf58202434be6a43ec0e122c54259bd7ddf85961b4ede1a8a8ec502c27e5bbb88a3abfe39c01fff006b281ccfb0a74fe0f7db3114941dec08235ef29602d2b64908b200544215a4dfc230ed6877282de2bc99d92bbaaf35f66de32599d4173109e3594806d651b8ce7698b12e6c9c8f34bcda6890cf7074685ddc81afa17d13478fa3519849ef6aeb2335a86fa2a3a87d7d8c8398e8c7cd35effd04e47ff881f6b29264b8e94cc661b79dc9bf13b34675bb9ed8a6f22067a057625a774522f9abf1340aa2e054bf9438cee865fa046654a71271dbd67d2889f2c2bc58dd7b1b64bef9ad4c184eb03726977a154391d3547b64519be5203e758017208a823774287ce07744592f0ac6f084d14027d9e699db21fbd6c0ad24b9caff14254328683766f4c7293ae17f0bb61fb4b854a1035ca2b3d95096439fdf6d1547b560d7e0c7fffc04018bc66e84ad67fd9519d7376c083b85d4fe6fb6f8cb231d223ce5a9567c7134048740856d9a514c2166f82fea2f5d723a3a24c00f611eac96772eaf9a801f85006476355a18e145900d56a57e455221ccbc7d4789b0a19be91c1ea9ed8ad838baa1ae4eccccbaa2f128d8471aad10b1dcb45d7aa912e953875928cde1ea5d98fff76a2af3d2ab8afc6c11feae0f9628008fef54e9c0200c12680ed2b7aa269c6afbfe02dcfa8e70c130cdf48138ec68c2c95e9d0f6f2cd0139d0fd7bf220d91d2a3c02e6095648c74acca5ef2fe7f261408fdb814bd0506ef84a77713c180c89508e301813e2b427180945732077bf0c88fb3cda7d689d7d83a72de4599fe674a1bcc54ff6bfb44e2899d45ed106ed9f10ec56e3b62a23f85d75b9e6e5f3d8415f47cd572fb09769574a95f299249ea9eb0a4adc21ed7c0b82ad677d78ce3cdb89a66f15b8ffbf9aa43f19a270687ba9419091074d8f5a700ba72c964f729e4fe4ef3ebcda0ce4797c74f9f8583b72c709ef2058c8a45ec091386ce9de3beec55a2f5131f8a58cf44159dca3850eaaa14d8a57c9cd1329c76f1ea36df0b761cbe3ea627e1808c8743ba3af7dc7f14b75974b5c7f1c5b8787a723abd7f591e3484064403b5541b70059753e3e6719e4076e19263c3ec7bb2865c605e3329b792fcf70ad2280491ca0ff3ebce0b9f3d4612dee6eb8a9bb5eb2fba17975b31ea0837a152f87575784e0406c6ec0b8f53c0b346ebd9f913bf9c0b20f0f5d217e2cb333da4056e5aab642bec8ce4392ed54aa82bf91663f32891f54d2ca6ec1b8aeab4bc5d8b7fba4c47a5988a799fec72ae6393119833936d41729b81a7b90c78a5144b5ec45fb7029430644533e5e330d812a9d79534cb3e3fbd13ad05375436a898076e8ca4316dbeb92e9bc3e0d1e2d9f7f8561e86302f857267c7eb09d23950aebfb05487785c0114088fd434f6585fee9373b92ccc7ff6fe8f6b6542593e7c477aa7eacb55fb4971c8fff5ec64cd2731158795a6722d73f3829628ec9fe640f101f6d1c8e0fe4392693f15db42765d34bd2e28679aa211712ab4f191bd78f52a595a2642735beb6084990bfddb5cb36e1620289357cb019e89584b9c8c01e88eae15b3cba7d4d028ad9d49e8ef699be45714ffdd556155a6a03b85a67c3776444fe68a19d872c50803e83b1c09d0ba69b8255e52bd53a0423652e0d7d3071bfa16bc44b52cef8edbea817598b6673f3e36a58b22fb82c1a03a6ee828edcf2464c1d85d08f2d706f4423971edba40410f2474110e42c6b18dc733c34edb1a68019183754de85664f2f878da0d76c20558fb7986eb6b06c70dfd2d0adc9372080d367d7aa1fb2a1a2acd51ed374d71e0641b0bd32089b74ff758486fcddc0cfcf70c686f24e20f381072f992b55a9626f566e2938fa5da6904cb7188a9d53cfa6944ea012ade3ec02e6ce66529134f9c309ee23e1ac903c3d3c51c1dc25da18df7234683d341ba807928a159370b0204848eda4d1787c70268d7e5c87d879c9a51d122a174e2a6a758117c15be702d9867ed991b51974db2c7c68899a8144f73689211b1d0c54a96b9eb64f1d377e57201acb9a004a423812d1d94743ccd4d29373090649c70b90455fdb6131b09e59a81661ac495af3abc2e6f12ff4f43ba3ff070786f8fb6a0d5b0a3b30a0c45b9500c28314507355a2b4ff4197722500303a6fd7a179e6fa26ab1c2fe45ce3fdd9456dec5a5b227d2a0fc41cb74ef24d27dd73621cc2bd8d844bcc81c1e7e2437b04a4c4ec67d071ba278418e69ce131dc85f435ba5a86d0b62b730b0bff76464d5f4fec6b9b74562fd9f21f02aff6a039f7304da2560edb7f8b59447b197b9f9bc894b28126a02dedd608d10f0d4ac601cb2f047b9cf7081921a4d0e3f62402382f2e453adef81582af909fc34d2f2ee10c663f96b7dee12b9ca9c339c2bf17f739d3a3637b285450ef98daaaf551f7530ce1241cb30262c1b0267f3aa0f6361aff2808c05206025790e3cb1d42562cd759f4ea7a82b223b2f38d483af7625b4b6fd38b256b06fc8a856a6ac0271face148ba8a3ee83e751f7ffb434555ddf7894f3adabfd7ae5479e5b48a64a449d8adfed028fcfcf1371f3116a6ee67cf7f8c33d51565b0ee2f051ad3df182b79f3aabf4ee8574aaaaf934604c5571eae19e61165f6fb5a76f6acca86e112fcf923f0e5a0553bac81c27bcc2a9e5511ddfbbca4774c7e874f1deecc83341bce587395999ffbd6ac93e4ee6911de9f39c5f2e3687b89e18d98952e784369919c8563edec90d898071abe66bb2f273ea669e9b97955e358db56d5dfa23bf4160f2eb43646eddfc902cf1692d9078d30394ef3a42def26d8299125b20991f588357e1ae360c700d5bacb413d4cbee5043e3d2a6e73dec751f92a8b7fe70ca957e2f86b50839af1aac2ff10a952e0995ac26b38836145c6f1441864510575e38eac0efdb3f6ec6e9db3d18916130e5133681bd5ee2cba340f3f53c597fb76e2fb21947017183000ae142835d1a0fd9fbb7466043558605eba888f686ece4d1904f87daa0b91af687b6c6d5e0ace468dbf9d0402bf13f4d6a907e0d96dfa4a41183a3c5771092bcdcceb139558e5f9d47bf4a84f9ba49a753cc9ef8ef211b4e59cab5edd0d4315eee63a5ba1e7dff70b13384417128719b4804f336d2e712862b8401177b378045b97098f6c16cf342a7950e4f552498520c96410437eccd0cbdbc0111180d173af9ad1bdb37798dddb85ede23122a9bd8cdf6a2a6d559a972700418cbec04fdf21242bd98981e0782c8881af0875ceb0363855772f72b4d53e74ef03673e38dd225c9db9e36c596b0264eb91e8531a140e55255394f71c3a37ff9e6d3e77953ae0a7732a49b8ee94397832be9a07f8a1ab9b818f3de081e9b8ad27b9ff7bc84fbe1f5af0796ea813d987a4adec03dc1bda93421d6e746e43cdba796362cc2340db723c2196c366c85d050fb2fd89ea1bd9fc2c28f3345d0e4c867afeeeeff83d2f444ede07522ba5ab363c394b609050868921a1f3f8e3081338e41a931600a8b94e278af651e5ed06f0368b92931c1c6a2c6725cb123b7e25e017eea8284a853319ae1be9a53fc275a30eb5128277fb7a32d10c9a5fcc6dcde0043236f493378417e32e8a7e51bd18dcac112876ac4e092c91ff50607de692696acd608991a3ccda0920b26b1ee174c663bfdc48da1c0164e072ff278a566b5c5df52aabbc82ad5c77190e5da94b58b2b54e734198fc5901e96a537298b1735e235855c18ffc8c155f92c4b2879ac9462455eeca7a6b5867912c80d780c62f39f7712979b70ff0fcdcd7901cab5ff031f140369fcb050a848d5c69f5e11b4532f5a6cea261c2a05382d788a86bd913c554a2bbd8e9725119c01254e78766ac7f619326624b7cd8760ef6adcb2dd45ea1051a59259a14fd04ffa56889e3d53cb37e03699c1fec8711ef8d882a9cc068101cd6bdf96300c5787ae31c42121895782b6d8610e82acbbe66d69545ee1fd7fd5e6b5762358faa2da13b8204eba367f2a511c6f471f7d5a006a6cd9b808f58deac53b49f13329e83581e42b5298caa26e557374466d0cd561df711545d875dd75f89df953694b3a9733e9e0a6f82c4e6238502edae435b3e0e95841eb4c9a23094e1cc2385cec6b160d59948245eae04b3cce02be4a8104a22ba509a262d0d85748f6da2a9b7403211ca0966f31f91fe00296cdb43bd7b75cd07628c5dde7e5fb4baa5b10113b35810e92037328d127713b77285b8650ff8ac3d568a67a7aa0d544db113bed6f79aa025a98a3dea6ad4c8cca5dff409c988e781961781b223c1428930e3ad755d12cb8add748b5044c9310f936c8a9c14971628b14dc19431a5491ca0c6b38b9463ed957034ebffedd8f98732ab2fdc5e1d4bf1cc74ee24eec7237b3d74f1f878153621304773326209ad322cbd11429c8f1826c10e01ba4b0efbd61cfdb4dc8e5817c1fe86756a92eb063230fc864c7dba5e599fc401327d930846006009635495b9445f934e88606ff18e0e0e679c79dbeaaf25801d4a0a79e8701307dcd8285186c93c4f1890be402bef66d61635f4edb7a6c694429a7d820fb98612e3b1549a66cb0b4895c372465c660760dd402a87b4157be6fb889c390bed5e2e189a134ff6a7dd34766b89f0cce3e4869189453c94c67fbde2b5e9b84e3076a0a3c20d0627f70e24f3c91619163c43f31cdad8329b1c125480faea02e3bc08b7ce061eb3243707c83488359e94c216edde71c0b793025e1d3f85a0405ba30be52693d2d0748b8209d6dd99db3bc941b30321d35be533fc71ef2be1cadcbe5ec0377c44fbd34eab71f76e49055f3b1aad33f439a7cacfed87e76ad350fa599183f8f254ba395c3c03c62a235be942b11c22d45ba975443ff208a7d0f412d6cb6e1a47cda5f05c0a85d63ebf5d31f1a3cf6998d79fb06078c9e6dd29d7fc7a875fb85bab2ad7ab4a96f557e99f9f72c41ace596c31060a8e55dc34081f8570919a798e87e298fe5b5191f484e8ca3ebf0620490dfa2fc7676c32b8e620ea64ca47872bcf146f4a0e1baa6225740c4b6312a7533c5779730dcf86be297347f5bac60d4dcb3943bf7959dcbbd1b5f624a85e5bcc2c8785282d41cfab97543474c5a68ea5566be51cd340227ac5a6a3711d17c5d80b9657480b2f383ecdc7d1124ef2a77aed673e97b995d4077bf2bdaf0170f966a17eb69ca5030b2e4927996a6d48022a0fda8ebb439e0fdcbd0c69043ef835f5376f9a8372a2e2c41111f06ea7d06337ee9b408265672d2eabf80348b9ccce61fcd6fff7d655587011f6443eefcebbd3be579c3096e9536783ee12b6905b96dd11dd2e168b30da945fd62799db7cbf142aa4b8338b88ef9000fabcee1b3ad1aab57824875bede4906302000360ec3a4bb9a65be0cb2689be7fcaade68d250b9e1ec020fdf9d9497578e339b90a1a2b3f4f0a11419b95e64926695640a5fcde9190b20f2fe11303179dc03e45df098b231fbbffdf51ea820b07947d09b6d1c509b8571b42383ba5afeb3f1629fd4e106033000000000000000143a5b5019c60be7e16be5506d11d319f3f42b059216bf5e3d1a56e67ba1171c47e3d5276c7f540c5698540a1dad230661e2c5c6cd43f6936bb772432589c621a0d1d10b83045595814bf1bb84471e2279fb4e661bf97c4eaa08083c55bb5d26e9315efb5966f0c152d3dbfb06f81b06b6b6adeb00053038b8230cdd74014f3c69b6d9b269b0b3eed4020abbc457e1c4b11a425dd27000e5dc44493c59bddeccb298d2de50e3328dda9b079d8fe72ced033fed61ea41eb3ebddf09efa0c64ba983817540a4b5faa864508aa989708897858ca98c027c80b152b4bcb34afec416a63521e3fd8c88be607c3bd6229874250e00454d20509eb30fd50bbf03423fa7681c449c347ceb1be34994290a04e38d186f54f37aa829eb1cbc295888855287e4ab9bf0e70cffb3cd6768ff80bb6889353ab2b6253241e2842e4c10a86a0f1f26dcbe6d586a9352f8ef8440c4ef2ef3d09dc3aff80d8f5e6ad2cbbc6682bd974a3733deaed2a2b7906cc6b96b6b08e2868a9b13deb4c9c748cb45861f0829f819b8432911c60d456ac89af653a3f0c23a776846fcf1fc2c7f2b9492c261d73ef1963ca66443af1056941b0b9d72640af3f7f1ecd28f7e25385b4cf002a6d7848451d13b1feb65e217ba274ba0b95b688225b0c54b63e6e3ee006886f9896e5b89d1bf97f0d8b00e6f4d1927b957ebbadb353785a9424bf007b26f84af99fc20cb9f3dbff54595f33f4a1fe815d554269bb3aa35342033b00118d3320cbcb82f7b3d0c6f4d2bfc99a71d51eb14f15dddb82a553cf1f56f70eb7fdfb58dc1b9a409a9c33345b207e5c0f7807f25b00a6a4ca6979b06c02bf90069e1087a6aaf571a1df8c64fe5168932b0e964cc4dda69c664ef58b9040ce16ebd084830d5eaf973f846400e82ef3a711f95ddf35705128adddb3e7563c5fa898c138b9045e7a6813584fd3b48041d80410b115ede1c378240a44c36cf8057b16ba632b40c425d0e564d7f92716221ed99553a7d270a73dae65d3c55d07b9f8f1ea06c70eec8f6434bec0d02af0500e27ce371aeb77f441e10ddcf85040b42243006c232d4c816eb54ae61a24c92dce20a470cb4f1deeaff8ac5f6bed8ccde8a7b23b3f467f0786ff5439d862f5f151d182e11c3d4dc983774930b7cba8048fc04b71bc3c088f83c3354de258de13d02ec29b2eae91c61a831c5c487957da505f1031e5b4986ad98c955d95d4bf8eceed15d42784ce11e9fb6958c5e84d4518a8ad4b0760a13721ec99ab1cbf307053c939a7600cf7263185669051936c0f1a2099b0b7bb1cbcacd726df96cf6de3cbdb2246b28a14f67d6b802d733434b968274cecd4229b52b826967a8f8c97ac6323a1d327ba13a7e27be5a3b807f967e3da2f12466e51716a1863604a2c53c3471cce67f8ac0e816aaaa25aa48ac5f9ea76f825546e21cb7210c3cd11844bc4db9b037deaab1368f50781bbd245e62c1f1eda71ef8024fd7e23d17933bdd9b4056586ceefe53a108c75b64808e7d1d6b144d9cbae3637c2b3fea653ecfebfeec133306df5a29339d09836c26b891bf4881ff3e889b850629bb8ff0beb0b505e24fbfe094202954f4a2c48ed08d5c286194ca8fb0fd127d093d2959b049e0618f193d4c5c46f31c5a0ae0f79943029568cb2192393f7105922a272f2d8d1e2754277d52b942fdd272b464bdcdca69148d06e79b07ef773cffe8589ff515844065effda63015d2c66333127a7333f8abaf4a389c5b0aaadcce720d6aacb1c7cfac2964f922c21bda4be69d56c7cbc2de6c56dd2c60bb8dbc98a8d024d26373a694bcddce56c91d63bc2cd17b1899244f34a582c5c9cf81c27fb3cfed8be054846b4292db964bd3b269f9a4cb95f75ffa8a29fd4c0fa44e3d8bb5324da5a0b10b5057fe02e5e0d96e41bb9ef3700da3e9bb84f08a0e4b3e01cf41dd545e4635082cc350bf87bff8cc50dc51ecd447e488b6deff26069a3a1b6494074bbdf968b6435120338671ddc687cdd9506afdc3cf9cfe5c8075d31491b31f2017a08b3dfdecb61c21cf1121bed0292ff90730e31e8fa5317e321ea3821f56ff22f024b8cc836842c349ab98a7aa8dc94367fdc802fe2fdd2fde334b6d81b5be66bd69e211f940c442972e54c2f159b668841658e20d96e82701242b3ef3de9d311becd9fa1bddeb61f42a3fab879d63fe424ddccedf6dc63e647e83028d5a530c7797fdb2d6fc35c8c95dedec86db5cc3d8e4a2c13c89fb46232431d2afd0953d5104c83a5c03eb9b5d08ac9b25a788cd24df846ceddc6cb1e1051dea31c5a6c3b147f6a47e44ca9853f593e70e41405343ab223d8692a8fb6156ca2e87bf069350ef89795274f59b1f2315cb7cf503f07fa30815e7e4906ed62cf706c9d1ae0c7816155517a45581f046129af7958049a5a528e16bc5faccb1dd73ff17cccc0f74dc7d375d72f6759812fdd974ac0e96b87c0175b3814aa2efb2106449d22022473663bd40397942ea5b2e3577962b2ece7c44b7dbb5cdc14a2c33f7434149abab2334c8f7ee758badfbd5dd53b172d7ac395d820fcec3814dae32540b54daa3ee30c6ecb945298582a46854c7705a22312f128cf3c1a886245b832e47ac6dd3f07b1911d857407b6f3997b5ea83651a4cf247eab9c8782266a16ec35785d48dc46822ae17dd4bca33d6d38e6a4178733716d49320cb1686c28cecc79c9fe311a1866fd062462549a7ab99418c4eb1a4b99a3766d2770aedc61a9b406b7bec6483efb0b48c3a8b9b95c3e45b9bd4cce770bc40e627567b4e0fe1a4238debae657db1b3f5e41202b41bad91e54a16a1925563a898534f60b74dcad2a053fb2c5a1e1272b83cebddef2cef1dfb98129ce4748e754b7a573f9397b07c2be2d531759e0f7df84fd3b5b076d052c3569021bd22411b3130c432a830be73df7d80743ff0c1547e2e2b120be519884b06550dffc554a08961b79fabc91d5fcac7b98ba98df4f937116e5fb1003131504d3debaf37398db9e09ad4628d586c24e5715c974fc69d092e3e1e4e960c2cc9372f8d6de652fd34a23fe1f02ccf4ef3d5b75782a010f59ff7c950f8e423b0d388380bfff750c13b68913c1841e2029b9a35e5f0daa9f80ca55940ef8121383af699f12f1c540ef3827ed6f985690bc2a1dad4d9746978d423982a1602069ac60c32540355c4edcaa525d6e51979f9fab4582d8e579cc9aff0b9be31217757fd378749189be44f75d48fab4ff3749e8d07fca74ad86411dec45e0b820120e6c4d5d38d332d8617ed439598264e3be7a5d033fa7f3598ec0f309afef0ef8ffbc3f0826b4e1e03f73b4fb9bef889bacf5bdc3bf8f570d6899ef72801283f517bc6677b338633a79de18491912eaf630c89fc1e9ac42eb1fdb81ff6bbc1a0a7e148ec473de69d22b24a061e1526fad4a979af458d959de9aac53774f07a2d274d87bd7a26d93ffd623245fe00f5bc26e40479ee3af683c2e0aefebc3115f47de8a087605714c29876e9797004571c52dcb4ac395d1b973efe6fde5d1bea686e85d661af0436c3fd7d0d31b94794dcf20cea5b32329ffdafb864015f7fce8d5ee30f6fbf69fbf763e2fd98d3d636c4b182320fbb64ce7ae4ca52a554220607f1832502cab93ccdd2deb267e19977fb4030e2908d5792fd9540c8eaf822c2711d2f9d0d19348f43ce422043ea54cc3d9feac90b1b5a51fe996e3d33a62b9278c6fb3090a2326047c8a8d977615b0011f735c983c2966c506dd8a61c00c32b1cf939aa064d1b4d0bac6176c534fdb06e3a32fb9fe613add69aa2cfd385cd800faa2256458fbddeff833dd118e5a589d52f0f7e3ce12d9ec317131c64f4ccc907742a4546ad7c0beda679b215db0d4b37e8eeb123554333c7ed1a0f2456d627e34bc19f5ac6a42538f5c482de636031e23f14f6eb28133fc8529a395af6365668aa871f06a955d5305a24bc4c13f43daaff426ffb75f50bbcc494247503d96a6734f8a897cfe2d54d2be208f8790e0e8934c6c798e4d07c85fa0a080e2246242223e12b75d1389e4c789b28241d396c43f160df74c8f44291bb5de76078fe44a5210cd2a4b02235cb0801dba2a781037014f4d3c86be3839c5704110be21f43a7a90a0d2819620c7a501e96f1540460cba9319321100bdd0b023af3518e0e5e675f82832914f8d20dc8e9140a4a86b07cbebeefda16c5e2b97bfc719a97b8c1000c549d9585b3ab7ce68cd04e34f51df77c1da1ce3f5250b4ca8fac4bd906e24acc3b60fca8fdaf82d674f7069befb6c7027d9d052fa74add353316f3b50b24d304ba35b20bea481c4ed41d5d5e4714d3fd5f96601a6dc404cef76638edc84be11e84f2dadf3fceceaf92559d5309f116c708ae62cec72fc62f65c539ebf5929c55ab5416b7e037f2cc3b24fd4dc53d65b3b8c0bbc7e81909840375f9b4bc3e0d0a21fd4eb9f777a5a09272c68f74d645d8075afde7c3e3d533e4fd69d375794566f5fc6ab4a8922db31b9300f831b819cb0696dfb248c59e6580b4205fa014aad8a9a1c66cbce92f6236a4e0799840fd5da4dc0f9ce05a873504a75832a7653db84d5aca9f87584f1a4a8b6547c7dc00b81edf426d5cb568d520245a529ee1d772835f44e30d9fe3239dabed7bf6b61bfd4b1add4ade5a51ff6983fd51a66218676465486f02f7df41e400d49ad28abd31fa9ccd1f57629a029f9bed07c2a04866561de5beeb18a7992e82dd2fde42fe38b24af2b3139d4df1e5153f411ed1e848b9c2eb1cd919676eda3f3d7d646bd5cb73331e5ec39c83a16b8942069abcd5b85c2a767b5c0813b90035cf8080b8f999c23b30ce2ae2ca6e96af87e1e5f95eeda75e29cca622e2709ba4ef62a2a4853f6a063d058306b208d8996d8005069f832826f1aaba9e23745a61ff442f0d4567c076ecb491739363f42498e7178f042812a1c02e89ea5f4529009981e913a3207afebdb7ef23cdd28c79af6e68c199b765b825294d8c2bd577bc944bc01e305b36c5e245fea06a0c00a6c3d1600ad0f9bea27449c7e06f87034284ac24a9be38e9d73460089ff970d961bee12023e4effe1ebba183e18b6a6c1b8373879315f208d5123b4f9f5d491a3c235fadf1a8f26a103d56974f62d17dc7151dafc175bc4c34b2f1dbb80e029b721249145e7b1c3a1041b631248c3f411df7e0cb2f27e5e6eaf877639ea1e90b2e5661e80f90ad174e8e61c43ba25175f930e2a94974f3143f59a4617edf78657247c152aae09c56d02e4752c306c4a5837d2f038852b2ebb2d97047a3297141d70d617a7fdcc075e644a3332a8077487e9dcb1f056ec08f8571bd1664a432bceb7c90dc8c7179aedabead80a3eef589ee32fef3de6f62431962eb71098442819adee0e030b0e2a932eaf6424d3e21a2b0406bc144c98dc3d43f9c3d997a20db1c0e961dbfa33c820cc10f9473720e409a16291dd508e8f598c1cb008421e2859e5a69206037109e0282f20981b4653433e36637b52822513b3fd73573245fcf07e54f16a7fcfce5b3315f1945592bac6b504e67e0f396372fa19a7ad1a18ebff55601919c24568722057268dc9fbd615e257d04aceab8c4a759b1845e746de4b6685c8311a33aadd1e42b0e5511defdb279dc376d58206341894d445ebb2332c1f67a8a91da91edaec0c481e77b3e2e19dfbb45912fb8975fad75ea5ef2fcb6603f0ea84412e1cbbf0515958af9e23a7a9e34ecc2eefcfafdf0117270e566b4b9f88cdca7723b7795060bbb1484bb5c2a136d275bffd3b98b9e6fe14073d1fbcb31bf00b984dbc2b5835d953ed72551bf8249bb79d873c1d06e296e40a2b9a818bc99156ccfd403fc605299b6fda4de8f372f42da3f268d94e21717817bd63020003820741c5e301402efa10931ad58263623b77ea178607abd7c1e4ecf6f286e2c0e208fefe43be567d4d845d1a35ed6b3f74f52df3bc46037c95e06944f66c7732db210cb3fa8d4cbd3bbb79434dee4609796b828840f017c3d229715e6a76cb70fd4e10603300000000000000017796c600f697d8c4a11a172c989dd0726e0411b329dda4e1f67ee73edb3327f669a91cd3e3977c66de03a2785c4a864ac38363bba4d4f5c86ca5d965fac5e949b8a8b64d6a58ad4dc3981f5e053d76c1f6b90c910da6b1e5adc468e0fd8eb81c101d521808fcb5c21b386ea9997d3a5aa796331b14d078a3063c21a58ba58da1f80b9a9d31b0392954d8c85d11e629f38906068e46e71892454507ee2f00031c0380b6a2809fa8947187406d77533754a52aac97a109f290ce47ffe213c824e4a306e6aac2f6a832e7585b12e0c50d58628a7cc37e6b6dbc23b5b905dd5971880a6b01fb778eb1ebd00123a289251e77c6faa93690852cad09ac0f2d8fa5b482f3e07f8ec79afcb1ee68f676c69b9e22894f57807c71911f0e0124c3c2beb98e2edd743e5e62aaf7999026e6be54b5ca6d7e65c0439e13c0d16588e1185f6df325154d84a25e97cd1fec156d9c8c267cb5601dcde096353c63ba9fbf0454d18bd7d299ef22e7cfea546f4eb20320cf68e73537ebf701391573bbac7d9d74ad2ee7156a2ee9a04dbca6069fa7be0477e58e92b961005f3628da991a9f690ff774dc1d8a714ba594d4e4950f8e95e965df837af9b83ad745ae6c70b08aa96ee1528bfddec4edf627bf36bc3d78d119180dcef02fd635ed6576d136505003aa23a7602f34fdd02a4474a56b9c3c3057fa820962c25f4c8265e133a1cfb18ed803e1c6a12ca9f07145b92a7db233a47fde30990dc85618627ede45b345411333e9af3426edc4248ffc037694f4435f0a98f87f0bf74b8c0e9be583ed6e4817d3fb2aff0e6af55f6c6a700270d0eb504abfa9b840139b3023c38d047450e4646c3d9a588e6b2c02e1e9ad26be13fc96fe8e256579b7fc18ac9e3450966e6d70cc41dffd415fe7d9bb2e3397e91d3799a2e0a08fa1b42ea29d8a1476c6fc0f72600884dea3af729edb85823859a37ce82bca5ba4c0da6b34eae8e3814d5950d53da109731e83cc374c65a96d3b91491d0f89d99542afaccdff18e18a42cebea88894dee94977f88279af63fe9db3910116333dc070315cd6b068e6fc946fc0155a77ac8a5f134b64a285ac1fbcd87f410497ef6fd38425720688edcdce1b6f7ff799e9e65311bdd7a4c278e266dec5b582839752ec43307f9207a6beb16283f36746c63b5629eb0791c0afc1179845f4efb1c01d655064cab995d260636354b97c9ff69c1d3aca2badb65a6de564612c0f672362d0d7ff5e18352e9c009b60f419668bd3df480ab7a5f8b2e2bb950416701289052722ba205302e35e5b31527f00a623190b99e742f2be808fdc5cd87f7121121a8a21da5066a32055803aa6b9a91e0773efc94a86ca8b5535d1dbf80d1f595295b9750745975e870dbed07e0e6047c5a98d9281bf63f3b3a29212499e7c672917b66df39790d3d80e9219f22726eb410837449888eefcf64c37f7d97a849177f54692d93be39a9d9cae16c6b4be0444fb578855ac8e97e6aa6e07688572fe62abd6b2dc983ddb89920070cd1ac11d1b27d3aa7edf432d381a4608a845c04e5f25ce62985fef04734222fa4ac2d4919af9c1efac312e9296ddddff61ee57d2ae9b2da5287a9a50447d21d5c8850580a37402d9126c17daea16122328d2b013fc6dbc20e0848c2eedcbda70d6dc9b95ededb65e5baecab25bab57acccecde87bf08eba8ab72c80cd23721e921a01a653d075e6adaebe97fa3e21f6ca6774effdfc6edbeb2b76420a46f8fac9bf84ca7f1a19af5be59c4be42db1161f741c800d7bce42c7b3949a0172105ab158e79df0bc307c61f330604e6a33152707f5ba55c60f8f9730316c4959c710f5e21e2235105aa819345955543873f3d001e9742eec59d822419976025210509fe9e8e64e4ccc8e5ac5b8ddf0085cf0c07eefa03c269c9c6c919c1621de90e990efaa98874603b5c2f70b096ac2ec024d8f3a87b51bd3e65195af60253d8145e79c17d04bbe9a460e7cf9fa6771b7f0bd9cf7bc04d1513b5872bf3f62e3863c84c060703a920747e9aa8e87f16003dddb3802c7267490069f78a6822bbbec6bb01ec0c65180578beee51f97b4d381611a94d5804c91a4dd65d21b3429d70372746f36e7f77c3d029964e913db5af8c18ac532400c7e2ffb6006de7dc64d473d645bc47318db9c232d07bdf514fccffdd8ac7862c5f5f0d756d0f3924dfef6e9f98345974056212d258340bb5837ae9ea4503fb0fd3db8977f49e3b12f8e47b883b29139362b5589a18139ef9a2526d4ac0fd81e2f8a0e4b9f0265eec3f2471c9ff93ede0f26794720235357ccd806ee1288bf2e0f5ff77e8e210c5c3f90b038db1f6aab826008155171995aa51eb17289dd08a531ba7b069731d22be14cc3f613911ff75748e3ad4f467e9f7d9256e8a31c8a6e89a1f548d81f23e394fce754811ca6522c03b438f23c324fb22e88d5fc3314f87480b75ab7c95368c4e34aeb6983d02d07cc9f6f6849ffe37121349637d4358237fdd4db015c069b3d535ad534deeb4605f4c4a7b17a8a5d9f3b4ad8d4853a6d5b7ce61aa84d0141d3c0122f5579178067e5c98d12ecb737f2595d37838057de67c825c72e56c06cdf43d907b808ab8c8f3428ac60d37b33dba3937e5b4cace5f8fe87e45f12abc3cb74c2d89a167afed25cfc44cb74eeeb2c91903774a2859de55b0ae25e1428f7af8731d66aa6af0cceaf50bbbfb4c20297eb385b3d74d2cbc3f4c251b5cd8fc458fb824eb27304248e46c9b7ea15cae4eeb9e2615e29cf30f910b79f6899fddde79daf32740083328f48194e03d8ae2a15a7383deff1cd45479daac780140315cee4bcf017b1ecded8a976701f508379187768eaa6fb6b91e6d640a907cf1d9b0639b51783fd9dda7ba50a887b09219ab52f008cc80ca60bf291f2e8acb7c54c149d532c608fcd617ba94a2fa3d8c1088fc3ccf7115156f80e7317a5d539b86344a89e155c112f6519863e37ff9319133b09802d571e99e41b9be76400bfe5adb3977cf678652225459e408483781f23c9f67e3aa07c4824cf41be8befc9ad95fb50073bedf0ae34668e158190c25631edb2cdbf85c9660c896e1246d11afcc633babbcdb8e66f367c8f424ec06000427538afd14a1222e152cf1f0dead40ca554131654eb4e841f709950835c238a51ad8e43bf7b85fa2a2ef40750417213c61f6d7c0bef8646257c9d527a00bbbf2d1abbcdb662e26a58e6e149f6fe01bb113aaad643dfeaf9dc39338ee4481f82adabab672100d1ec19fb0b0367fce44b8ad4f0e186d470d569a8fdd76effaf4c1490bb144897ac5898bda12ce161de69db2e18293b65b1afb26cd211e7ee7bffb915663ab80f4a990fe74d49e0b222b675e78fde40b4224e62bc4c3d08dcdbcf5f7ff93cc687e847f15402447edd303ac802e72026db8d14e530d29fac06ab3100054ad578ac1aa0b1f3a6696dd28d280b8f6b8cd1399eedf377b1bba9dfbb1ff2c2664e2f7b83de7465d6ca3c2d1e9d33d9857b1819bdf2b99c88fb212e6755ae6990760ed8700c858f245c3e37cc5cb3ac338fdaec8ff8c38097b0038bc9a9222a5b26fab9de55919f8fc1bb96eaea294c6fc7becd37a7ecb8bee4a083c0f8c721cc01b79c3aec3f871360c610480664a4074a0144a6047d86fe35c2948e4a94eeaf443f47aee41df7da0852aa743523600c80b22cb4a011fd546534be4ff95d831f1e1ec5d4a425a94f3a34d35157604dbba6bdd569e51783fef17a522a05e1a7170caa0872267a933843a9afd1b8569d72aaff5fbb29f2b9bae7995b40f52d86d524eeaeed1f847bc42333f602730b71b715d15cec39f954cf86a1556ad3a73e874500b90ad63f7229d9026ff1643a3a490bd44f26639e017b3f3996776cd28f9ec980443166472d55986ee8a600236dc02cf0c5b9579158c7a8313f67794912577dd806d7420910620cb8693711f73e63056c9deed8678a14eaca1a5a6dd338231dd13199560d1c6152935a5e7d7ae067e20bf3f53c0174f2eae167f2110ffcf7e783f4b15e8a66db78336f177315e252534fd041a34a05f4390f8736bffcb92a9cefe45b569dd0a189838e3857981baeceba82b08dc212e6db4cf45150cc8ce9104e6209c20641ad7eb80bacd2dc674697fb0225d545b8fa50b7652a91ab819cee4f30328b352f4b69ee304f2318f58aa692b0dbe333561c11d0c5fca8f1030a6df6bcb5343cebf7d2da20eb4238b83da5b3b4df6b9af984597a95a9a09bf21af0bee4b0781d90bbd10b6bd4d0e061eb786acfb623e6e4d767d30e810740d888df523cf6c8f52c7b25b06619e7baa3fc510414cc19b33ccf080dc964f858cb359b62d9acdc0c07498668ab6567f70a72422736bb5aacc3cc01a1721610340b1cb6ff2e5d2bc9dbd34afd4f299e3a3603ec1e05f8a5961ccffa77c2fd974f3046bedc0c555ab58e61caf48426082e041363984ef8002730c9d4fa0f30c019d03856e874733cf89a6729822bb6bc2451fe93363eea57c3b89dfbd0b2b12144ab0736e271574a1a0c4adf31131da26df06b74b415c2cdf4b4120c845a03a211c5dcf1812632600f7dbbf2516537714ec439b1fc6f10a0d76221419d972356d3f023535040c0f99a8f8bec8b0e2bf7d8a205f1ff0b1a1891916ce8bb1832e9e407d740310f6c86be8ec87896f867dd1070fcf8ff8e75dfafea01cebe2fcedbacdb4465129062b8c816f0ac32520afe3bbdeaa9e7e18f2493d028ad4c20d135fb8f2f33d2431d2e94589ce92c6a3e7ae96657a3e2154e80c9fddb67c5eb3538ebceeaf25d893ab767f780a41d5c6e8503b15bbb1b4acff771ecf0a0c70dc40e7ae0db703aa64265a3e291ec61b407c2b0f97ddf08d3596bfd7b1e034cd320085e72ac6ae28dedfe05c086d3ec8a8bf05d970639687b23de5c66951e0083674466df4281360fb41e4b4fe229fed506cb85982a6034c4ea9da788cea969659c33297e4da63bb638fa5f87e0ecf733d7d747167a2e48bbeaefd738cfacf2e7e6e691166dea0070db65646eeab651224188e38f1ad9175cec9de24a804e4cc6210ece8a3f09eb6182a184cc8eed6cb927488106f574caf99dedcd89cb964396cb7842c346c99b9afe99138d416d751f39dbf4c222bbbca0cd39d591321e8417f5327755fac4c193c1e2adc1da79861d21c3f71d58251df8b823b0314e0e3c8faee7d7354ceca4fbb0b03e0899b2d1dae292cf8b6dec76404d0b9b0a521a272d98e77d7c9582c607ce71b960c0387587ade2170ce31e7214da35b6e4260ab1d89d11ead8df7379f874cfb73893120efd5cd18d05b50ea11332922c501b94eae971b388c701bb0d1d22358a6ceea2e15d1494e9227dc21e424fa14b1852329282fa5abeabbd3e331ff7f09f510322e33268e6e2d39a8856c726a735230027cf38fb3bd35f771d7dc0c41ffd3d7913e27f055956c10611653685c2626a0ad4f21e0117669d9c462f5cfc941c1950f1d990144bd30084dcfbc6bb415f9913915a1830f4ce99af13dc7c74ac30df4b386b20a7cbea46633186b2d46e83e25119b9a8a628fb856e13ebbcd648744e767866d6e8418c616ff18e798b530b2bdbf88f9e4bd400aa3cc8d46f31a763eaecf43affa3fd241540bc7af39aa147303dab8f4d639d16260a4d95e5401577bc6cd1ab497d99e092174a19c297af999733e5efcdcc0b92309be3a4c8a776c17841dc070ae7d977c3baf19523b691778387dc4ab5de4b462c7cacab9cc0a85580f8086c352226c5b71bd1986b87f5cbe856ebb315b169c9cf35cbf78cb8ed019c1e2873ed8e77f0543bdf085a41f1e00fb3c6a629c53371c8f0b4059bb0b337d2aa2e940c73d40ea1695a0272b24d178f18751374e63020003079b276e97ee1ca0a84fd5c462e014c0a9dd29d2f6f63020e904215b1c3369cec65c2d2e64551823dc3d4ed90db729ebfeaf6ac0747cfa818a75c57953866f3efea5d3d2e9eaac6640836be4d66a656203c4c59337894e252fe2a4343d88bab9fd4e1060330000000000000001e12f00004b0cd0b3a784062ba31c1181565aa2ddaebe8cd0732586fc1fc37f41ed8fc3467eac595c4314f7543de0d1eaab4dcc8a40292ae938d563f8e508eb89dacb5ca85d1a1921e20e898472a3cade302b2f0be6039f8f93fc5c66cd10fcedfa89522d2fa94657ee28261504645ce76ab786b69fe85c5743c6e769fd489f9f697837f7e7accf0556aa268e4af28757e7ebdc1eb3ea36765c3e98519705d2f11b0a41146beededfc7281be9f3bae0579a789b2e55def54a048b3c8eb49b3c2eeb3b5d3ac21fb87e3f431a654fd6b2fb74a0f3fa1f1b48bab5fcbbf6f2808560817c692f9ecb4e0ecc8ebf33385c89956622c8d1ef35984701c4dd5c21b22dc6be47ad22c7c9ae7a26034635745ec263ba61f9ee76e1d4279bc815359353ce4a776322527af1c67fc4eae1904ee716d93d1cd51d49fdf27f7200c7c1d89d65688abea94f538b43a2055343462e4b9627d4bc6c38356540d3467b203d4675c74695f04f4c818fe288fba8202a95c83f966a9dd98ff0729ce8a6caf51e6c3eddc1aa4354b7467e5d0fc7d72933a445bf32c093a607471ab039fac55f7114e4626ce22e75163c8501797141680733ceb83ac0c76892f07435117bc86171d230c2790cb326b5465de23a8e83acf61dd866b47632ad3dec8dbc25ffc5373d116398e34dbc69334581b6eea3c95364866a4cd184a69cffd1c1a87a391368cb6e7436b3f46c785380613c041ae296dce4dbb25f2cf3095979bda8b8a4d5ece7d374427bd0663efb00a215d7f48eb7544efe666c8d834d201a51bb80cc46f56722ba7cf34ae6c45e4aa6c30d4dace1cc227f6e5fc4c26257523e798cf014b7fd0867455276c5522e5a24d9e168aea61dfb7da4b1c85b0859cc5bfb9f461dd94b0e65b88ec882cc1c49c33faef4d923c15780baf38fb57dbb0df54d3ebd30f626aad51f28126e96df32244041c38e1abc7b82061d5942cb98d69a0330b9eeea31209f4e66a72ce4f6dd771a78969ecdc345e066349584c16adf43a07556cb84c0558b3691f6910205d8286a8e4ace8c3612c227decb05f794e245bbfd0a6105a879fc38e53d863feaa24d0a09e374884336afec64978c0ea5e1751f9d4895cea48da3cdbc8f1547df46a507df56663ee41e64e04004a30435be0cf0e637cc5f5d32ab78e0ba31bcb9759e98b4ae27b01b3468adea384ceefbd4596eec313602c87b07f82c76fb1322e650a41cd3dcd05c195b94f254cec7ff56f11f0e2ac62c0c6b683d36f08f4917241e4768db16f90969aeb6d102b3340d3a39473c1d06003bd1dd616c590413c9880f07dbf04b12e4d0b262b47f64b185422d5d4919f8d819fcd0b553660387e45c15c199ca9ebb2eac411f3fc2db4e0199268717655e84a1b07a2bb0d11215d9de2459b33469aee7139907c947227e9cb3263525919607fa45380a0110c454f298fb14b6db0dced3675a94792c0aec305a09a77c833ef4df873d01dba5e73a8b40f963a07ee8a76dcdc9c6f193a4d5f2e7d349e42d664ae463e8214d21d23b758cec7a1aa852ee4613807b84ed4bad9c5431c376e0070502f8b0facc44f48fbdce51e843f224b7e9bee7ed41c755157c55d0b39f72a047812d0349f60c16eac9b79eed5e3a33c539d4e8c9ff15ba7c93a0a51f6c12fd5813f3d2b98c807f85bb0b19f3cb8ff19ae237dd2ce271c9a2dab2f3a3c76fff88c6fba43dbc78f3f8e79961084891400d1f9786b005eea34890f05cb44456b812ba89fc69377ad51afd6ee45a77bfcf6fba92ee0ac090d32739973ab794d218e7d8f6d96a1716f07829ff336fdd4e58986e65e148989ee657b25a7131e3bda51a83d86efb57b9aa1589620846e56390317b9647bd093cd6c7c5c9083cf458ee053184d0561515c1ce0fbf0883a39499d6df945d1f9744d75427ce88084caaf71b22a014b107c0dbc4b0b113c7e4c3244eaa7ee0a12fa61b0c146e9a05083e2b8ecb47b3b49f279a468ca3e948b8b9d25e4d7d0845d96de44ee1d0cb91c498a2a0a96665950572d2c054896dbba8e5a309bc0d3f5bd145fcd008f805af09a2ab9588ac1b8f0aa20abec84daacce02595b66de7244dd191355a740af11c693dadd139f0094410c1cb9cdce2b02648773b16bae1dd8b37eb697ced9a6509a9555096d6aa1480ee3c387cc46431483a56274c19eaeb5302f3d9548c8291ddf2c208c8f30c6e8ddde77b37083dbe03cf3a5cb27c1d323909ddc6129e54ee5462c6afaff160a43e88bfec436aa705a3317c014cba9b872b88f8f05790a6e68ecc61fb1364f0e846b95562b973c430d862e2c7f55d22dd4c99110f62a42d6ba63f15f2f04e40f6eb3ece59c3a0b4b762aead6512206fa6f13e11eac821ac81393115ce40840f65130d6a79d7efe3c9e2edae0ea9175fb8afb79439b82b8d89c4d9bea5efdc8ab5ca2975410b312e4d8e6ee5760639d2b00c090efcf9bde300922f7bca39521b1dfc200827e4c7320d405f8ced70241335f2c10697c98f69ece6552b041a763e440db2909992aa47a6bf5ee3220b81e1525fe315c4183e0999be5ff29214f79f1c16f070cdb57b274748423a6864ac5600a35a3d2da12215b4f6082ec3e0a0ca93688497d83a5dc0b73aaffd62ab6e56c94cf288c91b816a8be5de7f530a01ed6bb4e303a88e0bb2211388790fafeb095de3f9745903a373ccca71a2fa539a748884ee016ce72b69aaaa5a53c77d95f05d68d836df15536b5d8fd4e8a0384887a88e294d5f799c5100af96da315ee78625105590ead6a7a968127dfe5115e532d3df13ad37ba65d1d15d28a2dd2e050b3ea89e58ec0dc1a6c2d44dea5c960dd47719a2036b293be485e6dcf80c869d4c0e7269c362da6f621aeb7b589e56db07b1885d1b6f2eaafb53de5f4c8455564bfd302237df70531c0f3199e3624669d01e052d1ceec69e6aefa9a15d2b774b8ff1f574868af69fc5b67ed99912f4d5a0a4eff593df4cb6efd03841ad5d8175d6676014892679bc34bfd5792fb062693be3377590b25ea7907e26cc66ca33e0022d60be5689bde2e939b7d7c5da9b3e9403d6846e286d5673f2aef467a9da9aaa0230f665ad8f1498f139d5a19e98526740cb4d0b2fe70a98a50ed05b488a41c8b51774150730bd22ec132018a009aa42e014cb5f94397a743dba9321e8c87fbbfeacd4ee76a08e533c200c4138101a4257bbd14aa3e4d8afbab972d19b813ade52337f637dae72a335df5152aa4f77e25f052631309ff9e0b683808cd801ff0047c43c115b698514ace481f5672b1459de7325e36fe2ede6ef7c964006ab8bf55d29c8ed93dd8990e0ba9e0af9466ddc56a01f753ed895f43efca8ef43778656cf703f7b7ffedf4132b00686ac90009e251f5483598d35e9bccccd4cf2763c358d8739c965997a91b1cac424d1b1c7256c71b4b7ebdda7d8ed5ee536719eb5b32dd7dd0b6a92f150cd7bc4b803963999e5ed9ee6fd18ef0039bb60b001403d999f3dd553bc5a7e289cfaf443d6d2c934feef72d913251082684d608bdffebe5beeabc05a3dc0dfbc030de609b60f60bb8527030685da95f7e90a9a712213999deadb057f63963145e71e0d826dd1a4bec793245735fd65763ecd3f2565732ed2b03dd888f774d5618bfe299ded333d058e51b7e9f8c4fc9967368a140298ff9299107b61dfd3a7169136c156ed006a020aa510ac87853f350b39baaf0e5563bac34a3717dbcc6c8750f84a054ec0eb947567fd36aa868d4fced08f104a65dbe3d42400b6d3785b759616269bb22b3d5b1d45cbcc92eea408a2020c422a309742da792a3aa2127b2bc6cc9a58c1c151bc4af219a6cd3cdf487300e58133c2fc6417ccdc6587e2da15edeb1624b99ffe1824a72dd41298d3684ab87acdebd25b5f093a17c18bcf445c5b72ddb170c6dd39c2f11c3db04925d030450995d1c3abba499a30f1b72f8fb30b63d1d8163815d993edacd43bddd3932d5b3aa6dab6e48735a9f06c856dc661d6cb4ce7766567d4ffd08b6f66956789a80a1f34b63f14469e2800e3a6e08478a69bbd1e800b02137cbfe2cf8e61750273d8552690532b963fc3ea581e68197cf46c084f57817880c40501d361ffb78cc1df369ff412e30c08d8e3ef07e7af46afbf6720ed010917f6f332bffbf1ab8e0d1d36f1b8b01a7b30d8b32d7c20f21e521ea294e060ea67388324fd86d681eb80d79e988f8e5e956413810fbd07a4bc53993cffd0ebaec3cfe8fde0e0f14405b457bcdbecc563ccb1416fbb7d7c9c60d522fedc8e2b619bbcf628434db5be673f474e12eece9477d213441b6f6f698ab8d31f914b5e4575ea6eadcc0f2a4a298450576a397eeb6554c7b93557ed9e371288dab6921a380ef240f3d9ef1c9f79594fd42ca0e4118880ad4ab24d324e9c88f5231cf301a2d5e890be61f3a74969d506b723c61f01c6363b55ac8a4650ed3f2b66e748296583831423a1ee33c4649e73926574ffa5ec7700a69fd7f4f1108fa496bc3ae377346380727a02cb4c0b962d3e33b4fe1a0d080617c335c726b1039ad1acd4f3be02d3d973a6e44b7d7cbfc6c373a0491c5eef45135350fb444dd34a87ae66879ea1eba9359c37c7979006888b4a3c26ea6b6d1241599ba1c6063d8df3354d663fee705c67643537ab37b947eb2b07b1e10d39b5a988807ef4a05cbe4a3638fd28223cb88a74eaeef493d7c543e0a6287b15e8530ca04216e4993ab30090cc4d569b7577d6ed95f7800e2f292e7e31e6a80a5fb2a2f4b335176a2c2fbc891f8af12d4e81f30317b58f614e759e5e1f04e9f804b929fa285fc86e843e3d1b2e66c84f268bc3a858ccbb93f55eebb787dcaa3ec7fe37b3aefd732bccce2579e7e30d1ab018b7d6d09ab8b90a838888c35638339e528438fd6fd6e536e55f8503b9311c2823ac53788099e8ed16b362b428dd34795cda00e5934eb6ebbc180e373ddb22fe99ac91380fb83e5947891cc34d20a1ccee479ea6300ab0b4431fb9454987beb69c0481052184e0ddbb2b8ba5cbda85ddab07d1451a06fe105a4f134427e6c30086a0f1cf1d78ca3828aa9f975cb94a7402cfb00ec7881c7c8db9643d4c3dcc3ab96bc6c0e1d0511b110be2c6d7522726f8e6864c526c166325e66f051dc58d54d3eadd3762bdccd3ebd246d2376487118a2a3053e80acf68952e68f61477a229c6d1ad13db1540849e7f2fffd69ae9300052c8c020aa062dc1bb10527cdbe3eb272330e92e3c5bf63553ef835eea4093155c436904c822c99fc2abece776f7235d9078a09ae138ca6eb1e60ba39ac35deac241971365a79b851004abba6b9273300fc80036035975ce354e073f39bec73c22736517f41b44b5a7cb2265cc572ae4dcc9e338c02354865c42f9064c5ae8be4e45f7e15821037bf255c05abf8fc39d3a248b2c79a2776080e8d2cd5f82e9f9fd0d5d925b7e5f27699fcffbe4a43dbe84f716c22dd6575fa86e66e69e5803e130b9fc0be9e77ac55cee10b6aff5886af50ac0eede460254f9b58157aa8fa482d55b9d55305e620fa6cdf8bb31d817f3af83e7c40205f33336d82af51cc7bdb0e1b743b31cc85d4beed2871bc5a0b8433f6867a5435bfeb0e7ca8168fadf31c6c1b389208b61dadd6ea0a4df113e171b4027133b546d7e3817cce5a40bae7874eec67611ddbd9e7270ee7b6ee277ef658625fc2ca08c71a380be0f510cc2b2ec203bac7094ccd39e57a5627783a0fb16049846ba96dd29c60082679927ff0c42016d545946e83fdc8b1980b19fce2853fb63d84be896fa9070f33f8e0096d8a614c4acc66300aadb1e98728391048b82cf1955364c2f7dbd069af15c7debde6944af35dc3ccf51e3bf2323dfe77797a6cba7c6ce7b630200037a6da6709d05feb844b14c5034649b02a4edec50a0cfa3cf71ddf17bd9f3fddd51e6702cb84de29ef462d2ade164d6d500129a0901e200a4618f95cfb2399e1710d6c075b271e8a7764d5a13ada46126c93c21602c6707a9576c2358c126828bfd4e1060330000000000000001659882011fe7eb88ee594460f92d27d859f795e28255f5cfb23010ac9338ce38a3670635c90a8e64a86f05e0f632e2a86c19516596fb6f25cc8995aed488a447f560a7c7344dbad76b1f4b69678683ff69e35130e19adf1cf5d76a6cba6611eed175b1b088f71e089e4a4d0f0d32b8051afb42449905d2165b06bf9957e1a57bb34a0bb0f640851a69311a81a5b8e4fb64ab90c4182eb3b3b150d528a76e319b3ec1bc2dcde1bf377228bcc61593847a32ebb14ef6ef326cad33e73902f7d7a25e0a060328981effbb0b4df686cbcaa97e403309a94ee23552a1c0a22d98439f34155d2c3f1551370b2973658f4a7c7bffaece164df7fd7cab2f34994fc0f05673e992cf5835e25d658bb10feb6e9fb68e2fe490a24437cf24e545de9b30d31d391ce3898e3e56c87648d335c41dfb88fc61ec84c9a1d40e18a5a4796ebf6269b5f2d948a17adf5d1cfe19bb23f2280048d9edba6dfca0c6e4c231c864fcbe56cddbf0330062f284fd9555f1d540c5b88c2cad8d037a0b2539d8ed973bbc3a0d1f830252c4b68f1b84dc81e44ad1913adc0d39cd504c6ffc435aa91b8fb187f26c75280558bd016f9706df8918b61f49744d7f40537f7c5cbf5404ef534a94cd339f434b62beb8d4c075dcb40c8dd91823785bb41e7c529c147eef84aee7ae50e57a8cebe361c441611fe5e7d3f1a16b3f3f6ff09e7f3988ca586cfe2d3748347124d986bd87825666ba2a9215350e32abaa5b619b96200209b457ce9541045804a933ec3700d240b971ba7379a5b3a0563631c7e5a8c4561746006284ab01f44d84fbf1b1d920837bbad1eb11c6810232727f43eb7227632666f9646e4f9bec6836b57a1b86a3044b8ae84ff3468c10421135300712d24ef33769105c73e0d88d0ec53e1865bec30f05f099aa8ca5c60b862005a5780b235f95d6d5a13ece773425c676711e3fe933797beea6f695b06f71650e045f187024e2d7dfc67e419c4e4df903b51ccad8ba47d523bf45644d124cf1ca948f508a322ecdd3af066f52b51463e1df2507376742960320e1e30b34a292b0b24aa59b32ab94cd0627147d8a62e7caa175510408da1444420e4e89aa26e42ea422e84528d7dba8784e5027a0aebb6697bf242143188366a044a329eee7ab8dd241d40d8fd0bbcace7bcf33cee3003ca343694f23f2319d2001266580b8b501c27258c1afdb2364b7bb6d8027736c73f1256bbc4d0ab6411edbf277c92d6cd0b2ef42b57df71177c3aed9c41bfba58cfd515a0135643b0f5858d5778efcea57fe40b1710ab447ed737c1a062fb9ac468ec35f7b348ed4ceddc953a50d4d031165f8ba043ab2db8144695119eaccc4d1d546b278f7a1a7a0c05144a783cfba0d6eef63d33eb2460a368d1e2c13a5b137f8ef4abaebf80564e71ff69beea19b289c2e791baf6ec3c615c46d6f84d8aaa36cd7897ae629f5051f35bb46875c049785378f3eaeace7cf87ad4849cb472735ebf8ce9360f48f715c4f74eb61d4fd53b4422294cd562240c50fa1310cf3fafb5a8ed2124c7a6ffb6acdbfc043b8794e3c2b0b4f8d7fd4895d34f307e109da51b192cdf4b93c43ceb3b97f0509deab52ab8d31c58894a47473c43b17e1455b7ef55e47dc130104253b22425b8e12008462943e8faad5907c991139f1c58be4a8a832c10414af575b45dfdfda8b0ad8fe2976f661ece7133939b9330a229d85ed9b1417b2621cb7e1389921148c61c3279401f894081d15797d0f06cd494e344d1fa0f587e052eee99565df04a89b43a43c5f0e2cd3539f0cdcfd68c3f1d682a3e4e0ef4947c39cbb2a469ad396e4181afa5426398a866cc1471d9f1689099dedc16499260558ba2fff82d5a67672ee642f6424ec15123a3306cbd16057f77b3eebd58d0320f8c3830cfd9d42791f7d298eaa4e6685f95c9a482627f980cb51c810649e6f32ec6d63a963b4b4a48ed37dfe5dc17f1b249bf87b6d38c1ca112f7520f21a525d93fa0c96c8f295cd974cb5167a03020fdc8f3c54389b9b2f1e00684db009122fe8d6426894ea7915a7950db91a1df09839feef4b02742f2569646ee8bf2913bb973de9c03c61e61db550f63f3c06964d0077963b9eaf107612eb554efb904cc7cb9fc6a83f9ef7a0c9ddc79e0c39c6f7f90c5d5d2a55ea506fdfc346e0401915ad0abaf8d5b818c058a54d324b03293f354ef83223ece678b3fc44735b5e679d8b15fe162872c2618634dd1a2071ba7dae8d7adec6930e3f31d0473633090f27ee07094d095222f0ca424116ba23bd13ba9b1b146c3b7d4c70812bb3fab93718944334b08cea55528ef6c9db5e6d8b520db63fbb5d088af9caa6bfa5a40239f87813c73e5110a0e0b7255fffa0ca79d589d216e09938a1bc083b19bd54aabd50d0c9e3dbd8b6a98611df57791b0901a173ae3ef4d206634a06cc181e0ffbf6ea756d1e97f7bb50a4991efa242bb5483ea6837954b22c4a981547c856833f4d4f1b2c6c69ecd2620d19d57bf1d10c052455de4a43a347c86726bffc1f98e8f15a50aa9484f0e3774b4bbfc4f3ec4540f8b81f9233112075b94599ed9a32a6a2dec9e6ab3f9c14cce2bdcf2552da0e8735d79642715488f7c6d846e1db4b23bb69fd1afe360e76da04c67d82383d2f9671c69568985eaf53637ce25e55560131a33647376e391efe71f9f58bd5b51e49039f66116e002799e507b4ded484edaa237f7fba40bba08dccd486193ee6bf3786d5ec223b0fd9562b8c7a415d6800a5ac2e5051d094ce1dd953524007284ba46bee751c447c441644abcbd79e737a501e5bb28a18c827814810ea14753f246f68c28ccf9f0b8dc757fb39429d6cd015d9575b89bf954fc3e1fd6f3d240857d4cdc256da8879b217e7f632007bd9ce7bc433452845178029375b6731c51f04ab79b0b457a00b65fc8fb9d9f9d17988cdbd003c1263da4259ad46587d5a631ca45b477bb89cedba9b830ec1f44bb7ddf8c9f3b51fc85dc445106ed8e6d6f0ba36cbb4d0792a1d4d614c6e2b13d91e32de63194a807a62b81e6196aa863adcafcae34cb8fb59609cf01d8432344e92073a036dfab37b8e3aaf2a9c5ccabb9205eb4a3d3b3199e430129df0b869fae519e16fc1baf3ff1a72761ca8b5e4b91de84359bf516dd6fd4f29351dd8bebbbb775fb43a5fbaba8ba0a28fb34b340ac8a534279c68fcd29f7e423276f0e50e11faa21145429016cad518a2e3d43173087234e21dacba49ae692b1c95389ae2b6087bdc189c8b4d9b2ee524557cd3c97e129ba8261a2df46bea9761ceac6a4041306b31b01d00da518e930c07fd290ac30bcda9e4fde0bbe8234e118f7ee477ef2f1ff9438b67bf51cc113d93332ed0dced933e9f1ca3bd5cd111aab4f3635c7de5c6430930cfa2bf6b0b439ab539377fd20a232827e237a38bbd8e843e343256dd50191ce94286c155f16c0375fd86612fd9eb4ad41997a25a1029ac7dfb21b2ae46e13944e7695c8334477060431867c3a05d30d0eb024db7a458b7a664e73a1e57789436ceadfb1f28ff77b3bd23aa833cecb4ef1010ec4ba2cbfe0e555e76c4532b68e7a8d14ee35acf05acc2bd290609ed53d4a2c7f089806346860d0c09cb37da3bff7316ae46bf58a92282fa1eac75f26b1b9554580a69e93f2ea6fe6e8322072c0ded1086244915c199ec69a9a3da4d6fe780bf5d0ced52058ffbc566b4062b4651733c231d3c57d143d884a93d0291695b03a1c30c21c698e17e30ea5f01a1a9ab770a6ef1af86b5e6b1ef74ad7dccb172d8119a2693ab133683e8f074eaaae6ac7ffe49360fc0eb2713467093aa8adb7561345623bb626aad1a674f2cd726faf709daae110f4a4646d8ace493fae8b3596c19efbb3472a91a3015c678775e65f5f204c7ef4a9194b5c62a0f51687a041ab651b2fbd8b8138debd51f38d1245cbeff5d5396b769d12fd845fb1fcf5b93daea9a3264e956a3f4a3993f1e9019b28ce2182638100dc13ba80b494d038217336b9123d14920ba74c216df74eaf4f5822e76ba6eef88633ae6992a44557cd066484f7fd35efc7f40e78a1f5a90fbb3499249baf0804222550064b03b0f01f12940adf156998880a36fbae58887bd63793d0987db932fbac40ffd932da6433170bac633e771df74f6a1a620ee7769c0faa946ba7d0f8e2aeb2186442315c25c64a45f57772e38fff0de702a77b011ceec1d4231f4707ba53034777d1f0d397918e068cf121837ceb3384547c16fb5c7bb1ac7a8d290092acf79729e531cd3d5360a7ede221f946c2ede002a9416053bf7f55376e6c535ce314b1f4d160444a4a2e7b30e45d13c4ac7f84e985c0f129b1c84ceddfc6e26cbb40a0d6d71272b41a107b85ce82cec1a9e6d2b3510c114d93fa8caeec71661122d6bd6dd72a10ef8da00b3209f9609d000040a7cd70e36f6bf6d9e539e44af156670304e178cd749489aedf85727825316ac5b79fe4831858725a91621760ebd50bc9c3fc994d4ddc854c1fc872f69ec717e6d36f845b70dfc72146e4271eff93cbcbdc0c99d11065c75b39ef71d95e251396c08aa0a41dfae344e000091102d17d09a27bf3234ec69fc91eab60bee6d5a8ce03d681ff78498c0bbaff23e917b7a96494cca11db086eb587e5dfecf58ffa5b0b1cff47337fab339fc2d418f55409b5fe23a0f05e9996bf8b92a5a42b09f0d3d60495cd176a397126e4a4f355117917df5d073a56a95f9ae5488ee53844cda9bf4585d923d750b07d18313f4b0b5d1cf8d8033ec97045e26aaba0113a3b8b1311479b07145551c8e5a9f1cd91ca640045d92f7732df705d9e4d1fcc14bff9004638de837fd21933e3182dc2ce380672ff4c91cf62f76300992275cf23a007b5104a240307ee372d60de8aba9ebc882860d6d81bb9e55f0afb6abb8a6e09576e0fe4f12f5b8ef85bccf6739d36886eca5df257656e5d09a7bd3548fba478bb4f873def7f2e2c8bdf02b29ba2dff6d62821c28323b7213d6332911b5fc8aa7ea66c595a4ede35c70322f3e8378837041c127d65d169aa4bf9e33078b60c22405912d547c1ec2fd6fd890d50f19a7df480dc8a0cdf15bb1ad32a1306a85ced602e404a09da90890e4ff23f59e9d4746e71ad9b0dd28871ed0536539a58e2529124ddcd4423c48aae4d9164cffa6c12978d7f3f4fdf3fed0b819ced888481f208d596586e70072bb0221ceca7f303944845a6aa3f69e3ca56a46e47c3c7a9e6d963780acee076c083cf9f27becbe5ad97aab82524dcc60ef36b82ecfc8671a2a06977897a8278cc8bbdbf920cfc8baf3dabc39e9d6467beaaea339c1a3f9dbfc425aa05d1249ce54e6352ff690948cab0f7ef45797396657fc091e22a372edd8034d743568bfeade805e9715fb0496352995a505fa42f5e730d370b4f86faecefdb0811c011a32e2aa947ba7e407de26d51b8957e63afcc7c8b9c8ff7d87f7a8e6d9ec535a3a4e93245b82bc51fb96a2c11fdcef4448428b72e94fd56f544e1ad2022fedbcf6b2bbe2a20f6d95deecc3306913540eee7e5a1e3897237bd377ac87cc241cac1cd86af7de9c8fd3c0ffe27e628bf843500c61499152420c20e2fcf86b9422338c28dfacc70e4ab3442afc38bcb76f6677555fba0d36d3c0a01d4cf17ed9f5fffcb2d20506f3cf8e4a0f425a9c2927d0246f0add5a5a5d3240ed8e15f74a8e16238a36ce665346d86a3d7c11d657877b64904e1889de421f6616a767b0e9bce01ba43908a743d1818dc19c79251b7086f3a09502774addd5277e13854f8a2ab16a61674209bc484824978e892ac92c13d1f658ebeaca43f70219986f4c5c8c9d42d0e0965efa64108527ed9701f3212a4294d14320cef916d1a68ba7f630200032108c2ec440116e3d14f20204a7c410c649c55897a69b182ffc0d339d5cbe281754f0e2f41d8e49ab2ada91f16f2d8dd7fc1cc4462f3d69745241ca38753c4e0c4b4263fb92b14dcd6572e29175ec14e77408bba41df4896b64ec9e01ccf9109fd4e1060330000000000000001cb4d7100a9654134fe39bca10bd73b695d12bf24577d1708716baf18073d73dcb0011d7ad1ea06a184de45b60887a47d9461b1c6318d833760cac85b9dee07b532d6ffa005343332b9b4fd0b4357e6a57330e6c0b354fb9196a18db446897bcea91bb91b4f382ada0d512936c98dd8bfbaa6473125cdfff34a2c9c2dcf7a7d307b6570468d38c5d734b518047894a24f7b6d074ca7f037fb8f7a53ff518c180a308de9ed7f037d4a913a31b1262ce2e71cfb092872196cd91e192af6ca3df98aa9a0303b7bb9d9d05ff8e6a1974a51752fb224b809e1a8339ff67009e5a84d7259c1929c254cbc927fcd83bd75c5e15f27f743be1d38947c6f1d7ae4eb9b8ee65e0a81c37d83f865c924641f8ae07025bb7329e62e63c14eb4861a8c10b55b2cf262f7918d17f143d13fbf387f254126948f41014a5a9324cdd76eb85b7966184d51740afa98ca61bb0dbe5c47bb3908628d17d3049ab01bfbba81c458d7ddc0afbf8878196c44aef743edb493a8b88e60f1fc79b4fd506f42c1b63fa9f70c1c715b6f8d7e5ff73aefe71f3e938282167702bb096136b26a3383e0e6c324b682f9443e0a2c2f9e717ae3b068522e9da1cddf4494215a9b2036006a48041903dc018ef7c8ecfcfe2cfcaadb6fc64ceec312395d1b20eef30328ba08e05da50ab9aadcd820f0d20308703613bdd48a72cfc200e02d524aadf4e3e84030dc37335f27a558f191d5f7d4fbc364bddc2907d66b22503c493541d52e600b14d87c98a2c5a4cc79c94b928b908bfb76a9a59b1252d83078f621278cdbb9d19b708d32231cc5e5c11496278e59c564fd951c7c8431599fac667c64abb7972fa27d22710eb7acc9d84ee10ebacab027044309ea52aa119155f0eae702c5499a3b6b375a3d3e7af8251b3bace39c7cb0a9d8369d43d1a7ffa7c6dce76149ecbba1ad28462edd1d794eed5ba4a875be57e3031d47ecad179b6ce0498981c4b84b3e99be2ef04fc6aeb9049c9366a15136c1c7732f38f1cb4bc9f677130d2e3830ea90ad68740459d3298927b5c5ac98fd8ee02c24519cb36c25fee86871d4d447d790f607308214e4acdb2a0c0ee572b128e0f6a884038bce3990edb5aac63b8e4adb34ffd831b3b1c26c908b6be079f2f75062c8d3b17e2fab5834d7174b1f6de1fdcea6a4ba30d9c2b38784c7a9ea294e24970aa4a77946c2fd3052a2b5985c3b566df78a0d97f296b5cec7bb503d35139ad5309a5e447a181de4333718f1c0fcf510331384ec708629e6f9f5da6238cf56b5228373287adbcbb4193191c4652a05af3b2281075afd2d474f4cf6e4db723410ecd581bf08b46e66235cdce48e1b7ca26f93245547410179e93591bdc37ee3a18fd5aefc0bc0d87d05f6290b470d58394e524ce02095727838788b3d8ac8ae2a907fbf6359cb64105ee067271b43b16e67a1665a9fb1be10910f0a28a088fb3f2d3bfc006c7e366ae78b8dad115adb0b06fcb261fe3cbc6a03aaec0e7b678e5896283751b2c570b1cab223b82d781f97e6ea926d6e8a4208d7298841e01178588f385be59b23095a66f0dde5f2526fd1f8d4f2584cd6c1bda55f916ec30bf9367d50bde632193e3875e6f393364e474ebd49d52f0c26bdb94a7b020c8edf007394e594e995619682bd8b6cf71936f0502a9b2eba3121a721d8bc91b230a7c2f6a2ac62e8bfc6b5f7bacf67c7b0bd161aded05e671e6e12b71c5b6a81d5fcc85fd5cc4462e5d3a3d165e9b3858a345d99e66c952978b4b513937094bbcdce408c221980cb8685f83b6214a303eebb99e834985bbde3bf3fbfd3a3327a0888e237df77c79e4ca9287a0e7fcb53c8ccbb2a33b6fd67173cf0c6681cec86b3dbd5d8bab6d989eaeb7bdad18996be9d6eb96908cf60aa5db4f4de062ece192f11d1fce85bae1d00eebb3c52f922a1a8508aef015692614a8a13d80ede3ed8e53926f885948e04a6a68f4779e869533e0ad888af19647ef9422c4d21cd32dc97d6cfa91743638c9b7ca9e46fb687a29fea8d64707b2f5287f9396b6b6b35ee2e7dc38ad27d2cb50652646122faec2060c56742de92ffb39aaab3c860bf188e3e2097081b0bd3f475ebefbb70b889727577fc4df7487e9671885327db8d88eec571f1c2b6996c83ae371de0bd9c53b661356f648a0f6cd1a30da1323f4b546bcabe767537d6507fdf692533e0d4e125636f6dc58c76d206a518fd9be789f8f8a86979be782bd3b4723070e4cc3beffc2c0fda67f0f15f767a2d9dd4d393a35e06a18ed382a383946f91e036ed15c1c0ad24e189c82697cf244c03cbb9f7fcce08b5b93d0774eb971b962fcf8aa041953600dcf91fe174dd915c50986b8c35d61ec580d83d0cf6eb220dc14a467642ddd54e5c746581241a9f22139fbd042d786e4ef5dd6dd15c2e2d9bed3b3eed825c69f800d51ee4cf06c184f23f3583e9c2850688907e0e3c1dfc2fd37b37a594f680a90947dedfb2fe9af36f801bc129227e8cdae62bcfdd4714fbb0f7ee40a6f44891c3bbf712ff763060daaa0d5feb67eae999d1c54b57addad3b2ac31127be42fb0a8491a4aeb43b339b45bb53c759198f6d7c13b0e257bb243a17f1d6acbb019799a57d56e9c8072a4b6241915b66ebbc47603e820dc53ab0381f2be1d9a5dfc553f6dae02dcd9bb78fbebcebafff8ff0f7e865d0e07d2720c79add0f2f97477903869dc8961085db63a809207d5ae853ba28cf89a30bfc7d362bb466fed81ab3ead1d66c7cb7f820fd65a99cef4d8cb843633ba03972e070349da81ae6ef6baa5b28abb24c3356ddcf0e1999b8b427ae623168ef07006bc74411caa175a6a9f11272ba0e11c8ea516da92ef1dd4ed11e647099bd59cbad77727b93cde07f6a3ba088a10a25d4d063821ca1abe81c62a7c7db0c682b1a32a30b5a28b7f12659a391119684715049307fe6c3a1b72eed0adec73043e78c20e8c3b657e75fbe28d9ad53a374dfd330d05e9cc5342bdc5db8268a8d736aabe9830e7384af76866da7334cf2f5bb779d559cabd2e4ef291ce3a9a8f488bb824f144eb608cc127da254c83112344b26b587221d8d8fb0eb7da1d4328a27b28499e9f1ace6151e55756397f729fd6e9d9232e0a677ef7a6dcc6dfba74855f7fedf78dfe1c7e8e1ac44040f42603037cb2dda6521ab50f135e0191f84faf48d08a68cec3e6b51932ecfdc1abc49fc9252868288963ddcdf7cfe24a07cdd98e1bbddcdd2b6936711a884e7bd930c505b6fc7ae283b8fc12d253f7a8082b9c71729804b1a173e813795d4c05f328a712a9bcbb35ab8c53e1a7bd9e89b8720fbb6a907ba1d277385ec082eb53f239c7f917ea9f4a8c1bbb0a3154d363603fc4b8ff26526725b5da0e56c634d926474691fa3683943a6a1067f467f73cab25caba3bc2a874696a4f8add24ef00a9ae028567587bb608d9efafa4515bc7c0d3521c25091b32cdfb2a136ca65f4d3f5ef512a145156b8d6c2046848b25dc1b28d9cbe4b23d403c577478494ad578c18dd62a39a3e606cb77b85c9440d5ba578e09acf9a1999d1a0f13d59dd46ee70e27c8d60aa86306f5a525aa6a45222d3076a75c1af1aef9d204322f83cd1f708f8fc55eaec01c54714048a794d56e55fa1e20e9f48b67bdbb43888b43742bc66de752f034047b44cc4e13961acf3dc30f3a9709c94889a960853ed47bed2b721c4943f35c88706b90e0a7c905124aca26830433124e9cc317fb0a4754d259dc3d8fc7518a2325f3882aa97cc8cd7581004e32ae02b20af90c48bb02ba7d0e29166987bca0d84268849c49a508b22f0c3cdcb87f826674861e03ba2df157688cf31e57b2406f1973cdf5994d77ed48d2d9270866c72312e5168fa0c31f7b7725662faba64ac1a8d7b3a318620b6d2dbb9cc22061bb121cb06576a1214894acb4db730380bf479ee3d8ec16f82b629acfaff124281f47bfe3487921ba73f6915b093c572c68739edb21c73e38a406c6669fa71f178c9755f0c8d0c6ba5be98eef2f7e27ba2100724e9c9f3079b79edc6ab4fb4f78631e26683ffc62ca8b846cd40016f35a808b23596f54aaf9d72c6638c87a405d98139cf7168ae7366bd2f440f00d9cbf5fddcab3a7029d386604de517f0c35b7156afd0e77d4851725699873399d5e2f535b0129e71ff8c41ba03ac5abf9759ee41a240231f978aaeb980ddf1c809b5bc2d2effb4b932a7794b26540a7770d7e170b1b0b81e10114a72db8713ee6c370af4c6ddd1d76c10e5b4cbd79fdc818a2e32f286c47efe55b540f59b5a555c43fbf673df3101b22e58b2e3c0f2fb83a586a5366d04e1739dc646e9efd0dd41b4391f2c6a45ba0cefe420b070e566555d7de61517f660cf95c1f2ebdfd71d9a2ac5ea84addf9c0f2d78e1876dfe80efc3b0bc2ee217249ccf82c3a06def9a3bb210d071bfcd604e223524fa986aa9aed206c4cdf037c7835cb27679ea43be4d8a07e08817358f9eea823812f56fd2a17b2455665fd55f09f8b0b0208eb6f38688a85ae7a3636cc2710259e2a6c19d23108bc81877e16829f26f25e878854f5b7bac0e2ca355ec50affa07b3330e29f93f134d2cdf291f9155797275ebb4ba96f68232f49f29b67c597e1f281a2321a21ae1ddb9961ecdca80711efa8c188e4819309c2d36ad1aed8a2f0abb17409145323328ecf482dfe60d6d6a3eb727d4194115e4cefb888a56c7711ff3b60ea969eddd7e96995646998bf25382557a2105543b4db2ffd5937b988a953a6ca167188688c50edaf44924b4b7fdd48391d537800c887d4944f2181c50434de8f3f87fc14f4b58fd1e4b990fd139f2243dc4046b9bae7729b0290e00a0e6623c2facddef9c88d0924eae647e48056857c45eb351d37f2ccd7f0c0c6b74db67f19a3da4d6569a45541c4954f99c119be3cf4f9d4a0f84ca549cd91220fa62f90debaac56c39fbb8ed5a53f428e6091a4bde41a430df18a4ce86bcef5537467e867562b53298938a7ff21f51d69c775aad8cd5a556e727ad330a08a7b3fb198cae003cf80740f107006311cab2adefae4b38ad0dd744b9e3b7cf3c9dd47a9f1545a62d664c987a4213b8be4eb33f4ce984b6b7ad8038b7c54bccbb031a82abf1192e26f507c3bd879ee42e1e74a560740ee173e23584e370e151f53f1ed32259549d70643fd1d24230904f2dd5cadb53ffdc768e4be9ee2cc528d04c4f8603d4a625fe267a36be37e319a374e35f1d20e6477eeaa03bc58cc9a6948b82678e0c3555b6589f72db2923cc5249fa1515bc8a56122529e8eae7543b3a01c4f180df51fff9c1e1b5465fdf5ecfae61c97dab19f706ee009c0dfb4aebefb15288f8320183ce0f3d0847d424279fcc9aadcd7d1ddffa12ac78e80e2bd345eb6624c8bdf82a5544bbaad8c500233f66bb9d781a6edb900dc373880db5e929ffdba840a024de032e02626402fb76e14e4681c39da2be9536b9499ab44163cf059ce6d73c40339dee4a4e79d0436c1167976287d4faa36b064d84bc4052091296a0405fc2535440d97428b7e5a5bb348e3e4ea4f13a6d7daf728942ef5a64104fd8ecdebcf60e8c76f39808cb18afb80b1d325b3ad2d1a1dec701a9a5cb8ace1268fd15b1b88d7dac5b89c93f28a3673ac4495f3c3965341d07e1138b848cd220c66a2ca44cc6d22e8038270f422045dc4265d605c3de060dd81d61b2bf95b9d9c9891ec9669e5f18dac332bbbeacc9a87c75f00aa02c4351356f8a2aa7ed2d869b52a7fa5e4bacdc3058a92ecb0f4db06f383e09f99aa541f4dcea30f830d6b721ca59f61a6f6989bbaca92f49c836dac0aed12d1b4f09a83ce40cb4af4f456d6becaefe5f8218379470e98e74520ae9900240a37c55d38f60e79e12b2d0000" + }, + { + "name": "3 tx test", + "numOfTx": 3, + "hex": "000000a03db40ab09a2c6840e2ecce95d8f23730eeb491699de4c306f1d57f236fc84632ec9a233ab87d37ce54f03270328678a7abdac37bec16e909654384ff6d4845638d045e607e000000012200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc332604a000000fbee9cea00d8efdc49cfbec328537e0d7032194de6ebf3cf42e5c05bb89a08b100010151030200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff04017e0101ffffffff020125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a0100000000000016a80017a914a6aa6ef00bfd6f6714c8969601bcf491add40da9870125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000000000266a24aa21a9ed6294309b9d39c458965ca69bcd5a36b478b8ddf2b3a0ca0e8217ee0f1e0e4ac8000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000020000000101ab6d4aba721be5e32a5124c9351b4396d1984d4a69f04c874ecc20226a84e46b00000080171600144f731ca75248f7443951374c8ccbe943e98a6620fdffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000012767649f8000002011cc98074a33e14ccaba093c11b4b80d51bd64719d7cb20936f602493b75b806101000012767649f800001600143e77536b6a977c023d51327ff4774309655a08130125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000028a00007d00000000000247304402203c5a6dab11deca9035dfb850da4e02bea71e09bf0353289823225ddad2dfcf52022015964324c4c9e04ce87455a85e0d9c9052c15f3d0fae5a209af2158f4779e231012103f123b7ee6f68351b0209799456f6e98b66912b7a78ed9f11b54f8a9620e441630000000000020000000102435d740d05da8077191107c03adfea535c95b3d6f9723813e96ba519aee68c920100000000feffffff1627283d8c3b5d07a9af75388dcce0f2069c8b6fd74a5955424ae69f7cfd25940000000000feffffff030a1d2f54b647653eb0171cc07c7048a8d5c3966268fd8d2b097a5f6d757bf2c03d09a8f858bc15772945ed461f9e3930fcb7e23c4e966b43e469453dc55c832b3ec6022550a7ebdd03baa1f9eb331d2b0a51e00af9e1740cec5c96cf14c048e8ff6b911600142e64cc46b950ceb16b940bdb7f5723e1e85505d70aa8ee99c6d5203ca61ced2d716e9a760675ed98c933c45be289f7c0c820d381fd0835e91b30a0136c7ad587198ca7c6409f3036231588f017f5f87118b79cb615e1032591e84a2ff4bac678a0997a12df347da2cddb6a2cb801b4b2855c254593e6ab1600142302ad660434a6a1699d31097c461f6b99a3fce70125b251070e29ca19043cf33ccd7324e2ddab03ecc4ae0b5e77c4fc0e5cf6c95a01000000000000141e00007d00000000000247304402200d88aadce535fdaf67ce9e81981ef6bf0fdaabefcf250123e62d2ab1583071c102205f69685fa5f225a57e2f393c09f35c4b6df005111b29c223d4dba813686dcbae0121023a98b032df5ff3555354d6b361aa10185565d147370e37c8e20fdafe64cc15020000000247304402207d1ad20ded877504de9c4018c9a8322c92b080c65f24bc4921c394e35cb107cc0220651a6ad9b44cc2923f0bfaf08a5829648c3fd080f904eeed27af1c33cf1b6f86012103c8bcae0c89b6e9c332eca0154e9abf8e59f6f190192b627598689d9edaae94a900630200030d298a450dfa839aa83a4499ee4f61b3eed235c168115af97eb4270822b5bb36ce2b1da5cbf2aaea98704886ae9c53e6afb454da3fa99f99b2dbfc9b01763649508e9258280937a10bfb4c4777ebbbff4d54c1ec2cfd87d98104b72268df840efd4e10603300000000000000011f46a000b8558a1c0b4c5dc89306856ec01f9466ba0f97ec857ff809946118f36cf444c860560d8a2c93b33e2cc3f254861b3c01de248991e8a1bad77f6c851322a834df4146126ca12f4311d54dc6749ccc5a67f8c87bde4fcebe9e04667c9e446a8d4258210c81267d5ce1bdac50b147c6288b1d5dced4ce5c218bd4f408d4c2dbd9462de518cca997238be3c74871ad2a9bba5a48c54842ac28fbf0cce611b57d5e7ee1356c2fd2caaa7c2112cd0c1a291059023625c4990e78bd254aa9e158c8da23860ee7a82c8c665e86f9f55fb68426a8cc2247ffe4d7a13f5969549198975e1e130a174f225a0c56504f4818c3f2980ad4e81f6e90b7ff4544408c88b471395bc937079854f4f14735100412607b5e73558ba8004a4e02f71af2a537741c4c1617946d75b2594cbe66761068630d03d19ae62d7c86017423670ab63699c57bdab695f80a93e49c8a755ac08c361c614ad179e6647ee026bc72210d2d154e1dea98194c8804c9cf0226f12792db91cc316db74edeb65adbf33069be66ac16d1c34e270144fd982a8cd030e8aea7694f63a1f69c196be4f6c2bee28085072bd2c150b211739fcf947d72fff445cbdc762ef174d94a9b2be8e433096ccc0ce795f4eb230b88b31f64e0b210a0c55aea504b8b25245ef438aaab23ce915e1fc9ecaa17ffcce6da47fbc1270dba407a9a917f1085d70af32b38e73d1b99e79297acec21f7321475bc50286a2a6e76cb357f375b874552267a606345d2a00692c6d8a365cf80836d8fc7f440bafb839d053d6216aa14bce7d8e9391e761d59dbc3632037b77156484cc1a71f5d1869c1742cfd6d5ef4b755f067d1b201775ebc0714893e922547e506c0691797254c6d56a1fc0065ee27a44c7c6cd12508b7181f281f28ba76f6b4b87f3a2b15bef3e5d37afaa3addbbeff84157365abf7ed45c968eb804bc05fb49c4e338e3970267ec8744217f8952275057ff655ef4142226baac1516d75d4b11eb25f61e9ed89e7a12cbc2ba481666f5f8794a48d1ab63fef3ccb7fe179dd53a87977e15c43ba138608c0812cf6dbd7b67b2794c8ecc8c18278afb930234d0e362d926c15eab8a74a3a59e3f4c443130013c116643cdae821c10984a832153ab49802a9fcb54b6ffe46a9e8da7fa668fcbd91d169aa85df684095f1b5e8bbaddde4b354e4c6d65ae0b9770fdc79f8b3258a8650c4fc2a2d512dbaad0a588ef7ae4ca30b7ec6639680303919a28a7443d83d93d706ea7b349d85f9d07f54a1446cb0dabf9cd2c6bd0b32342a9dfb1a54f4e53e1af0577922f95ba3b7702223b1e02586c98d118c11c9f0c2ee194a36c3bb05421f409f5c3b02b0189b66f556cef5afe2663f863923a50f0d614cb044ffe533b3680f6bcc89bbcea6a97f10b4a201d1651dd8d50131f40885553a22b768ba342777bd9dfe373752c22c90cfd5a34edd40501e5687cf42d72517da4263984e5b4beedd2de48a12b804fe144796fe52304f55165e76fe6e4d9d1167f2cff488418295513e3b79e3294dda1108931239fb7969e5cd1c01c9b73aec18d4cdfee83cb76852730869687c80fb97f6cbfc62a292e94f4516efb472774acd55ae64e523b571d33eff0f37838c910ef04da3c546874400fa65155b4e98f2573f2ab3c70ddfd30ebbb2222aeb8095727dbea048d80d1b8cc10342be96025aa3ac20499bc9601ada20065128fb71617e85a51e337dbfae322e44e990defc01890317854053a1e9618000c4c0bbc6174fbc24effaa17c251330be9532ddbbf240d72c8f2f94df12bfc48d79421e322acc136c20a30ca4809e96ec75572d64330641684c99492ac1806baada87cdf8e8ac78ae1e58843ac0874b9c3b9c324d7accfba6a1490a2aff6c8cfeee6db9086035ac9404c82623c3c1191fe59bc27b8d68dad6a584e6ca82ed349f2ac9e9857c7116b028ee20dd674fe8511d22a8044abb44bde86403000f5fc8de66c957814daf6a651134eed3c3032eb9990aa4757135b85d5448ace020a621e8d6074afd28a856dbcc2d6a0b86123213644314fb7c0d1e4eaddbc25f63b7b507855df845791cba82405031cbaa81ac0a5d8b39e6f53c7e5eb883985e4b84218ffa363e3e36911cefbfd7a90c56ffa080fff7cc572a1875cc525f173f4f1c305926f150cb49e087e5ad3ab70f29dd847b7eb77c48a3214fb61b4d1266093ad3b78977759d9665e5624d161eda765a3e6f148876bb155e0ff8bc0cc448945c588d6a324d3304060ad7875fd1f4222350d528dd21831d137316bffe308b7852b46eb55e97ea52045476fc3ac43649a0731f9615ef51e97ed129849ef314182e57b10d20f82085d64363d837f28b9faaa011cbbfa346b52fd0967b32cf7e6702046f10c4003426e74f05bd1e03fc27ee596c645c073c7e51678966af73a0b18d541bef8226133b174a8e3c5963d69ab078cda14a0b44fd30f4995b7d66b551f7f772f0b9b9536f6f68905b1116797d57f4598dad8e58e0034e13afe7c3eab71a16d19ea98497b94748a4949dbc954dd7f89add0952c3073b19e4d9a52be723bb5b2a5cd50cec9c6cec7d1e9b46708d0b7f48f6a51d49324ff29b0940e970371f25608f37ed30641515f6c820075e7b1eea7e3fa548a0195899e14e001314cab985ae209d0b2d67d6afe45147b3642e16644972ae8cd004ac5227db3d40d75d16d7800b44ddbf19d2a15e98ddae30cb0ad4db009ac295d3c65b7fa31d296d226cad46dfcd12ba0581c1500aed0cb9d14848eff962037a013ad7149a8828c1925e7f0bcee4157099f252a957b4e45f736c461c10380b793d42e7d22efc783226aac4633c2a542c434b762040daa4a508c015f59a8d143742417392278a41804a992f00bacab5d598220b4edab565147891d7ff7a23cd1affd950433df0116a784913ec1619b498a2ca7150d8f00d5cccbe22396baf5e60caa8b93cab70ed34ed91c5cf3766694c723a90cc3a726d56f0e3cc0dd7a6c7787d35614fa4f9a23d0bb0155b5f23a723bfdecd0e1a512441b20637d4a7d856f2f553977f1ecd57045b2b95053993e1fc297d14145046e37c2ed74d4328c5705b7e671b7913e4bc9a9f5198e9f7529acc2f23bf9fb60c17cbd33fd10f44835a397ad25cf85de0626a6b1188775e1b61cb6f16cf7b9763b3e49410f2859436294ebfd19a28d2fdc06906f816c4ae05a2c7b70d2a9a9aedacd3b0c4b1498838364d858f1f299429036eeea1d66d9ccb5f3d5e9f0d6222e1456fe58a35acc4a286158425ab1baf5c097104ec8b732ee74a1fe63daa56861197ce0f8d4a5f248e9147558318d15d5a0c28ca26e62df17ead61fc4549413469278e79d500dd8e7c758e905eb3f03c7363b4e8894583e4b971623c8f5def9b9c38b3bbe57012b057a81bcc1f005b3339ba6cd6066231d502272cf54b8d6c64470d796668ada6f675e993260ab9b7bd99ec58d7b480459209dc0301531fef53846b7100887ba1f4ee04e4ab1dc8c1aa9656d04d14f78189ae3fdee92659c48915c0bfd5506830ab0decd8f747d6c06f359d78e9c183bdae20f2f76820d376ee6bd4fafc8bdb1d8aad87fe19df64d9faa92fcce6ddcd1267ca69515abe66c15848264132775c67d8d589a9e4c6da3746f257b45186b2991b62c16a8ea9563f6e49b58399953e643d9e9e35781733011a8f64989c6aac37013cfcdbc0ee932bcfdd96e37f2649fa378c7ace04996d73648ae4675c29fff84220dce78a9e943e14256bd73de395d85f3c9fa5eca9576c5c11716e764c4560180099d4b9210ade37ddcfe8db433b884948e4da8f27ae3ab42d3c41feec2e92dfba3610936b81c5cc5098363a03cf8dc2fd41861e0d5e058055c701d27219d019e39a82d0258cc2f011cd697f61abd168d346e91ac7d937d43a8a3f3765f2cbde3491bc444893da4a2a1a454dcc75f8a483462bac0b7399edb5507eb251656e46541657115cd326619e84e567fea6092015055d38ca7ec6707f61f52e132217b4119378b4f3c955fc6e7fe89287980ca49107ce972ad8dda987e066c9871d3e326e9d6feb51ae856d4f3cff85159168c9603bbd17caceeb385c4dcb46f44cc419b456ab694707913cf1f34dca0a2e0c6404ff9cd38a0b15e9ef0977e09eca1d035bf87d815d12c86d568a0b615cb4702dc146e30b0f5c45412cf8ec286029bf4ab70f62f34b7f8e9e67d171ee623d0106ae292caa12beb95d8cfb26c47b6a1387f3cfb8319aa34d7e2e88d275cb704db1f7089b019c99c93758b8c0c5ae30c9fc1bafab6b0c66f1740e7e17b522b3cb840b67fe3f8f08346736c9dcb15c32a7c0b27b107d669b93af00ccec64bbbd320933e3a65e1b9df0353aeddb87cbc2636d8b2f06b2ffea94eca4b587e26d783815ced73b48fbe92d6cbf56ce3428c09b4d6809cc9090e5ec6c2fe806e7521ae5e3440c09810d0749d13f46a0f52963c32b7165b785ff71de99302083e6eba62f5f5d308c3d614e1ce78096011903a310a8af37cbfed48c2d17b7840ecb89525e46ce21b74f7ea07456a1acdf5870ca484f046c81a584018da0f1bb7687f446b1547e2ca714be2f7074f448e5c12d53dfc388e18441fdfdca5fcf5c50aea67ed37caecb137ec14351c272a6b06c3c0687b1ecf912e74532e2393b8d5288263d7f864be271823bd286c7648aea29f77e33a94a6f86a6bd0fca63d1a3fbd39a2c22040c19105b2c38cbd19819fef64ab4b2b7c26dcad4850fbb1ff37f437a819ba5ed135553259f1c0ec1753bc78b0d4a74a412373d86eb1b59c541f3b18d24f3260cd379dfbf8ed389370b517a292a34e257e5e6a833ed472066fb69b5cad6189002b33f15643697891a09205b7b1da30b43378df867afbc734ab9e2257bd9bb692dd16b836e8dedf9b67e5578a8769debf2b4fc2e0015ef6aaac9af3a81a419b4cddc4a3968254e1b326adfd83697c49f69621f034fadddf123a9666295de20bb0c7f3382609d4984b017d2a06e76abc4ef934edb1d2ab595cd042715b3868f19113bad333e908f1ebd1b3b468818389e137749c1435c7a990304ea2b80aeb01d75313114d36e5ca94a0e26a955a49c8a6ee2a4968f8ee09874859c6182cb7e1971112d8284b94c370a7d6e682e92dada0a74d2b825312d7de1ccc8ba3bd5ec0939e7750c748d1fbf4f0c3d4452d5ba660c92742ae82845a8f45676e0c33a4bfc6df4bf04ec6dfd443372520d5d82adeb6cb4ad697572b30876cd87482d34e1dfeb0cae1ca21706ef12335dc129aa0e512698555801fbbe01a8e5d11bc7ed08414cbfb826f62cc14e9c65b2ecf1e882b094211e67181d62e3a823e13463249c4e171eb90de10eef05c1741d31d91a7dfb2297c95579a7b4ef13d217d7d285b97869b40c5f2f62df1e800f3b0e7fe45a896ba617c0083e8d55e6d1c61bb555c24678c4dd6c6683a24d652a399bf0621f05bfa62ee2c57d4b86c6c4541621345c358c33502b2367734a7a26ca27d1111039b4e38b35ec24ba1a13ce3efad7f89676e56423bd4035cd568b9427574c189afd3788918a9f0a7cf9b28ad17eac350e8c430718612b5b8237f7a2f8b4528dff99e01679bf6c868b9ea9ba7e66628ada0b1c29b179e541493de0952fc8ffbfdc819a884373d504959654a935533f11d8ea58fc8bae4cc7d89a4da456ea8b9b8438acae41a98b1a912505870414808f7ace1c5e0336e0946ae42d8cd665f94948f9e56062ccdc16d42c30be28c540fdf2a2ba7a447ede427122fee78ccb94c33c3477aa6a064d900a1077cb8cb6d65f72f48d6c0019a5d1dfd32a8e7145e6efd4bf4aebd798f1c2869dffdae91c23800abe9c399077c70fc68ad70844037f10a2cf36cf444363020003562f6ed6a76bc80fab62621e494e15867e4910a934e8564194321f9134356b02f58fa5e6e24642c7348116a8998972dd79faca3f79dc1df5accb64f295a742a757fe37063124c7b17a0645707d2ee1e214c228af2d9053c116cb443ba3ae8236fd4e1060330000000000000001b50fb201669d2446d2945cf115c3a27f0be33843907699e8409db5896c0f0fe89b8f4635e003dbaa0d59c240629cac1cbe764ba860f63daf87cf849ae66f3fb9bbe9c99b6ea8f5ea15bdac91077927f8723cfaa4b61a8be17a9bbb8af98101324d51261c0b538088b09926a58c1109c9d075f59d66ddaacdf139954757ebba40f66aef92da361e1001df9ee6cc3698b8d85519ea41fecb777097ec7639397e354a559f717d5d1d82264f26a1c8b1543b2eab074ca250f8f95ccb1b2f0951b3c653ac5611b76c21f6996936745e08513fff7fcfd96e1701ce42c2eee1ad00fbe42163cafa5743eeef6028b541bc0ebd202fddc5fc56344f6acf321f759d192177ee7a8b51f2782230e1d348fb88a8d3e6cc541be26f8264bc6dd097a184bfb53cd8bef59740f84e92d9a8a1ae76ea3e2df69823e16bcb7e878da607b4c9b60a99ab80f86b009000dd0cfaadc92f6739df63110c43f4aa0b5ed4be5bdff9cb1cdba2f5a8f82bff133b6673d7b2eb305078c648ec45a509074174626908e7092eff2281b1f48d53a3f82e9bbfd8a69ad0e969bbeb31f02274bea0be04604fc192cb93bac7b7ee8753c3429f4ea531377bb7340dc7d5d541cb6d39604a2725e150a131e5753a3e9263b6fa148aa3268824c4d55c450d406a3a94735702904f90732758b7a091da43d952b7d3c78978b9f1a6c2918e700da47462fc46df2e6546d744230cd8849dfd15dc2b7a89fedb5ce92df7c164b503e7ac9cccc0fc9969fe4f3344fc56111f35b08e4c6e075fecb631aa8f7a0129cb3f3e8fd29903287567d96ec4bef9fb84065e4f813bfad829a7242ec7efec4854c52b0b279a8040261e3b888ed400ecfbe038b4bb311612772b37bcd101a6fb4a8fe99dc64948101d584a22cb023dd16eecf2d6e6fc757ce44284df762fe8bf9a199024b39eb17dbde3e6399a483aed278b3705f4c4dee44083c0edc3794166c987abfdefc2692f61a5085b63cca88caf4fffb4046a340aa2e651cefb6c667cf4109bac4eaf7668d29bec2af835a32a7a7d5691505083d79aa7a5d39a09454edcfce3f49ebb2a6fb6fba398cf5838197d1f8d5ca7cb9138e24b03663ab6248e2a970304c931d578e632fe259fc67506c5de7fda0f2eb98311c64bb37f4e009215d2994e4b350fcaf2530b50e62651f485242f46cabe6156222a7c3313fb7492344824fc4dd7c8a8765f8cfd763fb6f207d0bc3b24df339fdd16980ae5e2f5c8a24cf9f1dc8c8b9ef6f2a6c6a3ff1f54130be7fe86d6d04ffe981d74f8b746f27fb573b495631329904a4d00da18abe6d4afdd66073f1bbdd033b842604d6b64f8753009db366b56b0b456b0af84339eb7f5d310c72991d4d1d3068d24e72a6388929ce807ad292da52bdca28cdaa32d756fb8ab535f06b9a4754ab92b393885a53187d538352a995eba6179cd4b5783765ba71063f6229cafb0b5b02cf57fcbb361021ba7f14e1e6f9fd93ab6e8d9c3ac3422b4628e647a37b9d47d63529e1d51d6d0a39153c3ded64c22b43dc943d37cd6c032bb43c4fa2f7c5768efa2b02d01b7f0252acc2d7454d43047f0202d37f429ae69194937c61496050b51714d1d5e8eb68b3f5bb20001ecdb18538c43b60d85de51eb05d5a419e39e1dc44db29c1c5937d44fe650dd30d53f5d3176037dfa5411c7c57786bd4af0cf3eb5d910a7c29a760937f7e6f4ffc123aa62728ac88aeca4cac26af763386f80ec6c78455e27a4ddf3176802d9390eaea4daaba0688cbbed0cbe834ef75ffe4ae703574ccc03d6a1bbb1c42b9e3082a933062cf4e5be68e5dc6571d81ddde656c1cd3e2e184ae90abda89f1e2a42ac0a4cfa41486bedf984044bbff3d2d3054a0c2c4eaaf70553756dfaf3704fed2bd46d6f81669ce4b9dd4b55e1f01c24505ec6020b022d0fab880ecb3809b4e6604c4b9bbdf3558a03fbd399761b8f14b8620b30ea7d0c71a25fe6019016c6a7523e9c99d66e425bdaf6c2270b24c428373c2723faadb891d64abe0a447369ad3f7e441bfca5c2a8e8f858a6bc6d284e71a7a657e9e7f40af96d780fb9c5263a30eb1cfe079042b3bce8b5b1019df58ed06e3aff02598d63d322db71263e2c4c0b58cd21969506b1f64459d0e8f3f41b0b9dd26b4d969a18e06eb9ad5f560f9944c25dcab63a8df4194865aafa4c9df84f68e9215f239b3fbb648cff09ddc1f860f2cba4fa2691cad71720a82ebf86c5cdf803dcec102e4bd0e520f69b8259181ddb8658859491cc6d28bd5ff8ba52a7cf384dc4a39f105e86db0da4c7c7a107e19b60f138a7b52de1a240df41b1ba3f56037f6b7c6bc7cdec387cc605ebcf01b1f2bdea84d2824afa8f74ded6c89d921b5b64b314c8bd382d3d9a29bb7c3bd31975eff68094acffe0dd13e375ea33086577da24f979d88ca046c950ab41f81666b86303a1a6e6a116c527ea11adec262eb91069206f7253e7bf45c553876dc6a196b8465e3db12c422bef82d9b9b7e6f075ad3695442c0733a0c7a35479c979750221a9f6647334f73698791fac2b73193ad5232be11203c5337fdc4a43152097e5a8ac4410ac2626747c280b3872a4b504950528cb079c71e928b760c807735754c2668ead7b9f8556fc07a390c3b56892ede6c95b75f17dc1d1b126fbe59e0147ef10ec0bb7c02012ddfd76a4fedec3bf29c6cfc914349fb25f2f42dbf1dd3587fac7dc6bcd53bcb72c8334096dcab33a5fe3db5332fb96cf760c7b6f1fa533e54d4eff05c6552d7e4065ff185047c841d07dbb7e18e256ebba4925879a825f5c750ae936c10ee298020e6eefe27369138d7fb2d743c8ac6688196e07c1406ab160c63956dbc06652b5e43d52c44e636ac11bf816019ef57492d364ab4a95cc60ca4c8905299faa5217dbeaf28d6626e6dd255e52d3a1ea45e60bc17fea3a109be33a281800f8f52341a94773954a723d305e0e4ea018ca410f89658ab1398ac5483eaa8ec7c8e33673473525891aa19e3a580c3c4b01a4b240344e8fa0b725bc1587e9ece58bf3c785eec39fd21e91087f41b691f1b53e9a334593d7698e2b41170c86a5f52580163919d5e82271427e289120a5d0cc56768c55e10ab48124fdea472070b76aa1edcc9dade90ea9876b15aeb39e03663b554e74808a137c5dca72e6939ee7414f3f5777ad1e75f61dde0014068ebf147891c789173d62902b051c840b307bd1ed876f104da5cac77e494d4fe45125027960e056b54b1373b8691993ecaf9357bf8b0c13d491bbe2d42b6f38b70320b84596f32cb2310baedad1c24a4334be143bb82ca8343044ea8dfe91056e617f16a17768916597c850774da68d315a1d2ffffcc6414ed4f8308c49926c9458bd10f7b5c84a898c1e2a6db23a7d19e6515d704577d3740f5fec65089a1375a2318dee12dbed836494baa7f12ae4d728b5e5117359d1cbb0d3d4938758e65967bc32c28a3dc1e9238319e646dadc22dde3e0c5f170f56d9d3cdf93d9d9fe95db4fab4a2409015e54c75ca268b450f24c8373d918640f59740588e114fa90af4b7549f90a79fec48ef9a43812b286b8ff60fd8e2029351539227ea9baa4419fcd6e541be3be0fb2166d6dc8f1452966c508ba8a9676fdbdae423dde5b372a0c1172c26d5cd26c6b4ee981b751b1f1e9e45f77cffda31170ae5e8849a7167b993d68b12a2ea25bb2366b47d8bb38b9fab31c8579ca7b7cf581fdb215b6a16579495ae86931eab248737aee7095dfd73581e96168470f6be2c728135b38db61f1ac0a949e803aacb1c97215984ea57f4837d8a905400b295673a0d96f4ace36028beeececa42f2b8c7e350be60e821b7c47425947c53632af9a4e7a36548cd6e45f43f5a0a122565a33532d4a533816974037ca882ae1aafcf840d311057d100d7f95a87c0701aaaf8777746b26ca7f3e55fb939747052ab3b2e9eb44d53b0a37b5d50930dae1bc6064712ec006a87d2ff396b3bdc85bb06d5b0250686c7e61c6ea524a766d45377f9479368ab266b32707b4f8d987b004dd847ae118f139fe8d35d12336fb6fc00ffe98fff8c21b11bfea8fc847d7f7c2f2b2991203ce377a9aad8dc7c4b459e4c93df3362206c28ffc26e285d8bbaf553e2cb0c2956ed4a9712a85a055dea18c8c6eddd1044d6bb7b53b4688bf29da0144af70da1ea15d8dee148d9373a7b3beca6572db03ab3be244d0e24a21bde7808dd930054f63695c0310ec66e4e2a6a09e63f1873f9b770a548bff8603e38d6cf498749a8546f224cd04cab7307585ef56a7e0202b41a44336bff9db610e37bdfdcf60ccc737996537174a89a54e38a2e22ddfad0227c65b84daade4cd4ff13e82d8308bd97aa1f7fa766a8476a2cf8dc7641c7288ecc4979eed04bda686985f7fc70768e129e5dbeef008a21a883dacd838ced6fc55289c913db9c3d60f5a762c1cf304b388870e8048c5a6592c37ce8014b7fc9089b193773450f27f0f33ec05ea85e355f0d84c1c4ad734c1b08adbe942ac93319beb4e6cd28db263e83e5b81dc75fc867c2e89d5d0c79287c77a6b07e781ae6d804551c39590f7684c5d7a30d2e126e5d57684ae8a78b600dd481092978767870a189cb5beebd1236047ab5646e46949c24a8608dea822cfd47d8943a4cebcbe6256ba063a14f6369cdb75af0eac7394239775fde0fb0f8216a8b7fe7ef5fb66259ee6d5fe68c2008f2ebbdbacef30a08247ae4f3d53c0f2d73e368255e38cd03d0ad384318a2fc55ea812aac8936e459387ac1117f448ef5130c8b428eec42f0c2a890b3fe20bfa1278fb1f8d8ffe9c617c05af3ee666226e30bc06c82a5d9e0d341ae8dcbc4194864e0206a44837c69a003cbf6fd354ed12ee9fc038bdd62e6e5c9dad4ba7e9bba236c68e0d4c3bc1f4cda978d5d533c8c855bfa62c85b4d7343fa7d9ef61fb39aec226f084a9555d374a408bba85cb874683fa1c80cfa4fd780d79acd1d1330d9ea861d94556790e5e73307f08e874cf8dacf50b13a3152261b7261297651766449eb7aac12bb3e649718c920279e7791c6d85bb505fcb94db0ba8f8de3a947b648475c4d36510113fd2d3d6808e27c39d8f7b46327ab6f5e56b5e779528e34b54b087dca92bcbb0b39253b7127f0d5250c364057275a74837e487d030a685d29b4c607e15e091fa8a7afb2d74741d4248aca3c92b2d6640511aff60d2e45669af2483574c674e2da413864be0230263678ced2421c17c187d82c69bc1021768038242152e9b3abf5d95cc29ff28f432368df1bced284d2372e1a48ca72e8760b9e01f505d9297762f4b21108f08942950ce9004af6c6df1d5203322534b14543be8e89af31a85314b2ae96dfe0e53da37e58db402eb315619b961ae2bfd71cc85debff6d3740ef46cf1a3803640358abd8a7cd454903b43601a192732e23025a927dd3f231c5bcc9293041d08569a95807cc97ea0c996d95f16f9b08d34686aca58c5ff6c8841f22217cdc60744d4d1f59074e6dfc973a99a3f215dde080141feb19db447c5ed8913b80d65ec0d86467db83f432f3f609436d1b5c94a7d155de412a4c29705e5eb1e9e56465cec4c2cc23bd5970ce05d26264676f1168208d2b97d59a9983c380a19e2813a1fde5ed38e8acc31efc1528f92245175fd51bd7bdc1841a30fff7a19bcf7469c1835e1af4b9b4fcb5cbb194f1795d6258761ceab09e44b7fe8bd08a6742ea3aac62358e1294a3f651af767a56bc0a162eb3bbef36f5186eea43b4262bb0a9585b65bb5032314336fcedbaa0e07fc403c27f3d38b94e5bbe04f19b3eba49027e7012b8282f8a4ef294765413a29f9820562a758d273ce15a2522b3eecc34e547972e0ed93964d5a60727275aff958e4d71bf70000" + } ] -} \ No newline at end of file +} From b673f56608ff19d351b4a0db40192040a6cd20ee Mon Sep 17 00:00:00 2001 From: sekulicd Date: Fri, 24 Sep 2021 17:22:48 +0200 Subject: [PATCH 07/14] lint fix --- test/block.spec.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/block.spec.ts b/test/block.spec.ts index e88769417..d87b115c8 100644 --- a/test/block.spec.ts +++ b/test/block.spec.ts @@ -6,19 +6,19 @@ import { Block } from '../ts_src/block'; describe('block deserialization ', () => { fixtures.test.forEach(f => { it(f.name, () => { - let block = Block.fromBuffer(Buffer.from(f.hex, 'hex')); + const block = Block.fromBuffer(Buffer.from(f.hex, 'hex')); if (f.name.includes('compact current')) { assert.strictEqual(block.getHash().toString('hex'), f.hash); - assert.strictEqual(block.version, parseInt(f.version || "", 16)); + assert.strictEqual(block.version, parseInt(f.version || '', 16)); assert.strictEqual(block.currentSignBlockWitnessLimit, f.signBlockWitnessLimit); - } + } if (f.name.includes('full current')) { assert.strictEqual(block.getHash().toString('hex'), f.hash); - } + } - assert.strictEqual(block.transactions?.length, f.numOfTx); + assert.strictEqual(block.transactions!.length, f.numOfTx); }); }); }); From de86aacaf54be92b459e6c0a23add63dd90221da Mon Sep 17 00:00:00 2001 From: sekulicd Date: Fri, 24 Sep 2021 17:45:33 +0200 Subject: [PATCH 08/14] lint --- test/block.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/block.spec.ts b/test/block.spec.ts index d87b115c8..07e7ec235 100644 --- a/test/block.spec.ts +++ b/test/block.spec.ts @@ -11,7 +11,10 @@ describe('block deserialization ', () => { if (f.name.includes('compact current')) { assert.strictEqual(block.getHash().toString('hex'), f.hash); assert.strictEqual(block.version, parseInt(f.version || '', 16)); - assert.strictEqual(block.currentSignBlockWitnessLimit, f.signBlockWitnessLimit); + assert.strictEqual( + block.currentSignBlockWitnessLimit, + f.signBlockWitnessLimit, + ); } if (f.name.includes('full current')) { From 57e1d5251769de769257493266ff844e52849003 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Thu, 2 Mar 2023 21:29:38 +0100 Subject: [PATCH 09/14] fix --- package.json | 2 +- yarn.lock | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4d65a286a..bca25cbfd 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ ], "dependencies": { "@types/jest": "^27.0.2", - "@vulpemventures/secp256k1-zkp": "^2.0.0", + "@vulpemventures/secp256k1-zkp": "^3.0.0", "@types/randombytes": "^2.0.0", "@types/wif": "^2.0.2", "axios": "^0.21.1", diff --git a/yarn.lock b/yarn.lock index 2fc674ab0..269fa4b5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -295,6 +295,14 @@ dependencies: "@types/node" "*" +"@types/jest@^27.0.2": + version "27.5.2" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.2.tgz#ec49d29d926500ffb9fd22b84262e862049c026c" + integrity sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA== + dependencies: + jest-matcher-utils "^27.0.0" + pretty-format "^27.0.0" + "@types/mocha@^5.2.7": version "5.2.7" resolved "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz" @@ -389,6 +397,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + anymatch@~3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz" @@ -633,7 +646,7 @@ chalk@^2.0.0, chalk@^2.3.0: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -801,6 +814,11 @@ dhttp@^3.0.0, dhttp@^3.0.3: dependencies: statuses "^1.5.0" +diff-sequences@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== + diff@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" @@ -1204,6 +1222,31 @@ istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jest-diff@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== + dependencies: + chalk "^4.0.0" + diff-sequences "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + +jest-get-type@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== + +jest-matcher-utils@^27.0.0: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== + dependencies: + chalk "^4.0.0" + jest-diff "^27.5.1" + jest-get-type "^27.5.1" + pretty-format "^27.5.1" + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -1545,6 +1588,15 @@ prettier@^2.7.1: resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz" integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== +pretty-format@^27.0.0, pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + process-on-spawn@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz" @@ -1575,6 +1627,11 @@ randombytes@^2.0.1, randombytes@^2.1.0: dependencies: safe-buffer "^5.1.0" +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz" From daae90a8de314e206c93a4cedf438000058fa65c Mon Sep 17 00:00:00 2001 From: sekulicd Date: Fri, 3 Mar 2023 11:13:51 +0100 Subject: [PATCH 10/14] prettier --- src/block.js | 25 ++++++++++++------------- test/block.spec.ts | 2 +- ts_src/block.ts | 15 +++++++-------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/block.js b/src/block.js index 06aa20082..86d202e7d 100644 --- a/src/block.js +++ b/src/block.js @@ -1,8 +1,7 @@ - 'use strict'; var __importStar = (this && this.__importStar) || - function(mod) { + function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) @@ -65,7 +64,7 @@ class Block { static fromBuffer(buffer) { if (buffer.length < 80) throw new Error('Buffer too small (< 80 bytes)'); let offset = 0; - const readSlice = n => { + const readSlice = (n) => { offset += n; return buffer.slice(offset - n, offset); }; @@ -226,7 +225,7 @@ class Block { if (transactions.length === 0) throw errorMerkleNoTxes; if (forWitness && !txesHaveWitnessCommit(transactions)) throw errorWitnessNotSegwit; - const hashes = transactions.map(transaction => + const hashes = transactions.map((transaction) => transaction.getHash(forWitness), ); const rootHash = fastMerkleRoot(hashes, bcrypto.hash256); @@ -243,10 +242,10 @@ class Block { // The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed // If multiple commits are found, the output with highest index is assumed. const witnessCommits = this.transactions[0].outs - .filter(out => + .filter((out) => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex')), ) - .map(out => out.script.slice(6, 38)); + .map((out) => out.script.slice(6, 38)); if (witnessCommits.length === 0) return null; // Use the commit with the highest output (should only be one though) const result = witnessCommits[witnessCommits.length - 1]; @@ -293,15 +292,15 @@ class Block { toBuffer(headersOnly) { const buffer = Buffer.allocUnsafe(this.byteLength(headersOnly)); let offset = 0; - const writeSlice = slice => { + const writeSlice = (slice) => { slice.copy(buffer, offset); offset += slice.length; }; - const writeInt32 = i => { + const writeInt32 = (i) => { buffer.writeInt32LE(i, offset); offset += 4; }; - const writeUInt32 = i => { + const writeUInt32 = (i) => { buffer.writeUInt32LE(i, offset); offset += 4; }; @@ -314,7 +313,7 @@ class Block { if (headersOnly || !this.transactions) return buffer; varuint.encode(this.transactions.length, buffer, offset); offset += varuint.encode.bytes; - this.transactions.forEach(tx => { + this.transactions.forEach((tx) => { const txSize = tx.byteLength(); // TODO: extract from toBuffer? tx.toBuffer(buffer, offset); offset += txSize; @@ -371,15 +370,15 @@ function anyTxHasWitness(transactions) { return ( transactions instanceof Array && transactions.some( - tx => + (tx) => typeof tx === 'object' && tx.ins instanceof Array && tx.ins.some( - input => + (input) => typeof input === 'object' && input.witness instanceof Array && input.witness.length > 0, ), ) ); -} \ No newline at end of file +} diff --git a/test/block.spec.ts b/test/block.spec.ts index 07e7ec235..b6144f1d5 100644 --- a/test/block.spec.ts +++ b/test/block.spec.ts @@ -4,7 +4,7 @@ import * as fixtures from './fixtures/block_deserialize.json'; import { Block } from '../ts_src/block'; describe('block deserialization ', () => { - fixtures.test.forEach(f => { + fixtures.test.forEach((f) => { it(f.name, () => { const block = Block.fromBuffer(Buffer.from(f.hex, 'hex')); diff --git a/ts_src/block.ts b/ts_src/block.ts index 4d6a0832d..461428358 100644 --- a/ts_src/block.ts +++ b/ts_src/block.ts @@ -1,4 +1,3 @@ - import { reverseBuffer } from './bufferutils'; import * as bcrypto from './crypto'; import { Transaction } from './transaction'; @@ -202,7 +201,7 @@ export class Block { if (forWitness && !txesHaveWitnessCommit(transactions)) throw errorWitnessNotSegwit; - const hashes = transactions.map(transaction => + const hashes = transactions.map((transaction) => transaction.getHash(forWitness!), ); @@ -259,9 +258,9 @@ export class Block { // There is no rule for the index of the output, so use filter to find it. // The root is prepended with 0xaa21a9ed so check for 0x6a24aa21a9ed // If multiple commits are found, the output with highest index is assumed. - const witnessCommits = this.transactions![0].outs.filter(out => + const witnessCommits = this.transactions![0].outs.filter((out) => out.script.slice(0, 6).equals(Buffer.from('6a24aa21a9ed', 'hex')), - ).map(out => out.script.slice(6, 38)); + ).map((out) => out.script.slice(6, 38)); if (witnessCommits.length === 0) return null; // Use the commit with the highest output (should only be one though) const result = witnessCommits[witnessCommits.length - 1]; @@ -346,7 +345,7 @@ export class Block { varuint.encode(this.transactions.length, buffer, offset); offset += varuint.encode.bytes; - this.transactions.forEach(tx => { + this.transactions.forEach((tx) => { const txSize = tx.byteLength(); // TODO: extract from toBuffer? tx.toBuffer(buffer, offset); offset += txSize; @@ -413,15 +412,15 @@ function anyTxHasWitness(transactions: Transaction[]): boolean { return ( transactions instanceof Array && transactions.some( - tx => + (tx) => typeof tx === 'object' && tx.ins instanceof Array && tx.ins.some( - input => + (input) => typeof input === 'object' && input.witness instanceof Array && input.witness.length > 0, ), ) ); -} \ No newline at end of file +} From 239fbf6bbac616a2530de5e2718e5b1a96d4f753 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Fri, 3 Mar 2023 12:50:41 +0100 Subject: [PATCH 11/14] build fix --- package-lock.json | 2540 --------------------------------------------- src/block.d.ts | 50 + src/block.js | 42 +- 3 files changed, 88 insertions(+), 2544 deletions(-) delete mode 100644 package-lock.json create mode 100644 src/block.d.ts diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index f098dac09..000000000 --- a/package-lock.json +++ /dev/null @@ -1,2540 +0,0 @@ - -{ - "name": "liquidjs-lib", - "version": "5.2.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.14.5.tgz", - "integrity": "sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.14.5" - } - }, - "@babel/generator": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.15.0.tgz", - "integrity": "sha512-eKl4XdMrbpYvuB505KTta4AV9g+wWzmVBW69tX0H2NwKVKd2YJbKgyK6M8j/rgLbmHOYJn6rUklV677nOyJrEQ==", - "dev": true, - "requires": { - "@babel/types": "^7.15.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.5.tgz", - "integrity": "sha512-Gjna0AsXWfFvrAuX+VKcN/aNNWonizBj39yGwUzVDVTlMYJMK2Wp6xdpy72mfArFq5uK+NOuexfzZlzI1z9+AQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.14.5", - "@babel/template": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.14.5.tgz", - "integrity": "sha512-I1Db4Shst5lewOM4V+ZKJzQ0JGGaZ6VY1jYvMghRjqs6DWgxLCIyFt30GlnKkfUeFLpJt2vzbMVEXVSXlIFYUg==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.14.5.tgz", - "integrity": "sha512-R1PXiz31Uc0Vxy4OEOm07x0oSjKAdPPCh3tPivn/Eo8cvz6gveAeuyUUPB21Hoiif0uoPQSSdhIPS3352nvdyQ==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.14.5.tgz", - "integrity": "sha512-hprxVPu6e5Kdp2puZUmvOGjaLv9TCe58E/Fl6hRq4YiVQxIcNvuq6uTM2r1mT/oPskuS9CgR+I94sqAYv0NGKA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.5" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true - }, - "@babel/highlight": { - "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", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.15.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", - "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", - "dev": true - }, - "@babel/template": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.14.5.tgz", - "integrity": "sha512-6Z3Po85sfxRGachLULUhOmvAaOo7xCvqGQtxINai2mEGPFm6pQ4z5QInFnUrRpfoSV60BnjyF5F3c+15fxFV1g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/parser": "^7.14.5", - "@babel/types": "^7.14.5" - } - }, - "@babel/traverse": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.15.0.tgz", - "integrity": "sha512-392d8BN0C9eVxVWd8H6x9WfipgVH5IaIoLp23334Sc1vbKKWINnvwRpb4us0xtPaCumlwbTtIYNA0Dv/32sVFw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/generator": "^7.15.0", - "@babel/helper-function-name": "^7.14.5", - "@babel/helper-hoist-variables": "^7.14.5", - "@babel/helper-split-export-declaration": "^7.14.5", - "@babel/parser": "^7.15.0", - "@babel/types": "^7.15.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.15.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", - "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.9", - "to-fast-properties": "^2.0.0" - } - }, - "@jest/types": { - "version": "27.1.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.1.1.tgz", - "integrity": "sha512-yqJPDDseb0mXgKqmNqypCsb85C22K1aY5+LUxh7syIM9n/b0AsaltxNy+o6tt29VcfGDpYEve175bm3uOhcehA==", - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@types/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA==", - "dev": true, - "requires": { - "base-x": "^3.0.6" - } - }, - "@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/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==" - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "27.0.2", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.2.tgz", - "integrity": "sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==", - "requires": { - "jest-diff": "^27.0.0", - "pretty-format": "^27.0.0" - } - }, - "@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "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==" - }, - "@types/proxyquire": { - "version": "1.3.28", - "resolved": "https://registry.npmjs.org/@types/proxyquire/-/proxyquire-1.3.28.tgz", - "integrity": "sha512-SQaNzWQ2YZSr7FqAyPPiA3FYpux2Lqh3HWMZQk47x3xbMCqgC/w0dY3dw9rGqlweDDkrySQBcaScXWeR+Yb11Q==", - "dev": true - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==" - }, - "@vulpemventures/secp256k1-zkp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@vulpemventures/secp256k1-zkp/-/secp256k1-zkp-2.0.0.tgz", - "integrity": "sha512-LhWeEp9pDWYpuE5Lvvb8QRuxG/9cd+NTxNaAzrg+JT0sO11D7hyxyvSgnHkuTVT7FDdmmQQGZ6lkutfynE45rQ==", - "requires": { - "@types/node": "^13.9.2", - "long": "^4.0.0" - }, - "dependencies": { - "@types/node": { - "version": "13.13.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", - "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" - } - } - }, - "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "append-transform": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", - "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", - "dev": true, - "requires": { - "default-require-extensions": "^2.0.0" - } - }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base-x": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz", - "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==" - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "bip174-liquid": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bip174-liquid/-/bip174-liquid-1.0.3.tgz", - "integrity": "sha512-e69sC0Cq2tBJuhG2+wieXv40DN13YBR/wbIjZp4Mqwpar5vQm8Ldqijdd6N33XG7LtfvQi/zKm5fSzdPY/9mgw==" - }, - "bip32": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/bip32/-/bip32-2.0.6.tgz", - "integrity": "sha512-HpV5OMLLGTjSVblmrtYRfFFKuQB+GArM0+XP8HGWfJ5vxYBqo+DesvJwOdC2WJ3bCkZShGf0QIfoIpeomVzVdA==", - "requires": { - "@types/node": "10.12.18", - "bs58check": "^2.1.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "tiny-secp256k1": "^1.1.3", - "typeforce": "^1.11.5", - "wif": "^2.0.6" - }, - "dependencies": { - "@types/node": { - "version": "10.12.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", - "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" - } - } - }, - "bip39": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.0.4.tgz", - "integrity": "sha512-YZKQlb752TrUWqHWj7XAwCSjYEgGAk+/Aas3V7NyjQeZYsztO8JnQUaCWhcnL4T+jL8nvB8typ2jRPzTlgugNw==", - "dev": true, - "requires": { - "@types/node": "11.11.6", - "create-hash": "^1.1.0", - "pbkdf2": "^3.0.9", - "randombytes": "^2.0.1" - }, - "dependencies": { - "@types/node": { - "version": "11.11.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz", - "integrity": "sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ==", - "dev": true - } - } - }, - "bip65": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bip65/-/bip65-1.0.3.tgz", - "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", - "integrity": "sha512-O1htyufFTYy3EO0JkHg2CLykdXEtV2ssqw47Gq9A0WByp662xpJnMEB9m43LZjsSDjIAOozWRExlFQk2hlV1XQ==", - "dev": true - }, - "bitcoin-ops": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz", - "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==" - }, - "blech32": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/blech32/-/blech32-1.0.1.tgz", - "integrity": "sha512-1/vr1wwB8jvfVCkLQMVXLaIyx7Lgxh/6IqVuQlSsCNzWNzU+64oXzOQ/9BRnN4GANjx9i0O3OSQonDlL4FraSA==", - "requires": { - "long": "^4.0.0" - } - }, - "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==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=", - "requires": { - "base-x": "^3.0.2" - } - }, - "bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "requires": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "caching-transform": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", - "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", - "dev": true, - "requires": { - "hasha": "^3.0.0", - "make-dir": "^2.0.0", - "package-hash": "^3.0.0", - "write-file-atomic": "^2.4.2" - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "dev": true, - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "cp-file": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", - "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "make-dir": "^2.0.0", - "nested-error-stacks": "^2.0.0", - "pify": "^4.0.1", - "safe-buffer": "^5.0.1" - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "cross-spawn": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", - "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "default-require-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", - "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", - "dev": true, - "requires": { - "strip-bom": "^3.0.0" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "dhttp": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dhttp/-/dhttp-3.0.3.tgz", - "integrity": "sha512-map1b8iyvxSv0uEw3DUDDK5XvH3aYA7QU9DcXy8e3FBIXSwHPHTZWVrOot7Iu9mieWq5XcrZemEJlob6IdCBmg==", - "dev": true, - "requires": { - "statuses": "^1.5.0" - } - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "diff-sequences": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", - "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==" - }, - "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.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.18.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.5.tgz", - "integrity": "sha512-DDggyJLoS91CkJjgauM5c0yZMjiD1uK3KcaCeAmffGwZ+ODWzOkPN4QwRbsK5DOFf06fywmyLci3ZD8jLGhVYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", - "object-inspect": "^1.11.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" - }, - "dependencies": { - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - } - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "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", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" - }, - "fill-keys": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", - "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", - "dev": true, - "requires": { - "is-object": "~1.0.1", - "merge-descriptors": "~1.0.0" - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "flat": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", - "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - } - }, - "follow-redirects": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", - "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==" - }, - "foreground-child": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", - "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", - "dev": true, - "requires": { - "cross-spawn": "^4", - "signal-exit": "^3.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "hasha": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", - "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", - "dev": true, - "requires": { - "is-stream": "^1.0.1" - } - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "hoodwink": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hoodwink/-/hoodwink-2.0.0.tgz", - "integrity": "sha512-j1jog3tDfhpWlqbVbh29qc7FG7w+NT4ed+QQFGqvww83+50AzzretB7wykZGOe28mBdvCYH3GdHaVWJQ2lJ/4w==", - "dev": true - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "dev": true - }, - "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true - }, - "is-core-module": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.5.0.tgz", - "integrity": "sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - }, - "is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "dev": true - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", - "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", - "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", - "dev": true, - "requires": { - "append-transform": "^1.0.0" - } - }, - "istanbul-lib-instrument": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", - "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", - "dev": true, - "requires": { - "@babel/generator": "^7.4.0", - "@babel/parser": "^7.4.3", - "@babel/template": "^7.4.0", - "@babel/traverse": "^7.4.3", - "@babel/types": "^7.4.0", - "istanbul-lib-coverage": "^2.0.5", - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", - "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "supports-color": "^6.1.0" - }, - "dependencies": { - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", - "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^2.0.5", - "make-dir": "^2.1.0", - "rimraf": "^2.6.3", - "source-map": "^0.6.1" - }, - "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", - "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0" - } - }, - "jest-diff": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.2.0.tgz", - "integrity": "sha512-QSO9WC6btFYWtRJ3Hac0sRrkspf7B01mGrrQEiCW6TobtViJ9RWL0EmOs/WnBsZDsI/Y2IoSHZA2x6offu0sYw==", - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.0.6", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-get-type": { - "version": "27.0.6", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", - "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", - "dev": true - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } - }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "requires": { - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "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", - "integrity": "sha1-AfOywB2LJzmEP9hT1AY2xaLrdms=", - "dev": true, - "requires": { - "bitcoin-ops": "^1.3.0", - "pushdata-bitcoin": "^1.0.1" - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mkdirp": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", - "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "mocha": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", - "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", - "dev": true, - "requires": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "2.2.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.4", - "ms": "2.1.1", - "node-environment-flags": "1.0.5", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.3.2", - "yargs-parser": "13.1.2", - "yargs-unparser": "1.6.0" - } - }, - "module-not-found-error": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", - "integrity": "sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=", - "dev": true - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "nan": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", - "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" - }, - "nested-error-stacks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", - "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", - "dev": true - }, - "node-environment-flags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", - "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "nyc": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", - "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", - "dev": true, - "requires": { - "archy": "^1.0.0", - "caching-transform": "^3.0.2", - "convert-source-map": "^1.6.0", - "cp-file": "^6.2.0", - "find-cache-dir": "^2.1.0", - "find-up": "^3.0.0", - "foreground-child": "^1.5.6", - "glob": "^7.1.3", - "istanbul-lib-coverage": "^2.0.5", - "istanbul-lib-hook": "^2.0.7", - "istanbul-lib-instrument": "^3.3.0", - "istanbul-lib-report": "^2.0.8", - "istanbul-lib-source-maps": "^3.0.6", - "istanbul-reports": "^2.2.4", - "js-yaml": "^3.13.1", - "make-dir": "^2.1.0", - "merge-source-map": "^1.1.0", - "resolve-from": "^4.0.0", - "rimraf": "^2.6.3", - "signal-exit": "^3.0.2", - "spawn-wrap": "^1.4.2", - "test-exclude": "^5.2.3", - "uuid": "^3.3.2", - "yargs": "^13.2.2", - "yargs-parser": "^13.0.0" - } - }, - "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", - "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "package-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", - "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.15", - "hasha": "^3.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "prettier": { - "version": "1.16.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.4.tgz", - "integrity": "sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==", - "dev": true - }, - "pretty-format": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.2.0.tgz", - "integrity": "sha512-KyJdmgBkMscLqo8A7K77omgLx5PWPiXJswtTtFV7XgVZv2+qPk6UivpXXO+5k6ZEbWIbLoKdx1pZ6ldINzbwTA==", - "requires": { - "@jest/types": "^27.1.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==" - } - } - }, - "proxyquire": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", - "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", - "dev": true, - "requires": { - "fill-keys": "^1.0.2", - "module-not-found-error": "^1.0.1", - "resolve": "^1.11.1" - } - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "pushdata-bitcoin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", - "integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=", - "requires": { - "bitcoin-ops": "^1.3.0" - } - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", - "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", - "dev": true, - "requires": { - "find-up": "^3.0.0", - "read-pkg": "^3.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", - "dev": true, - "requires": { - "es6-error": "^4.0.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "spawn-wrap": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz", - "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==", - "dev": true, - "requires": { - "foreground-child": "^1.5.6", - "mkdirp": "^0.5.0", - "os-homedir": "^1.0.1", - "rimraf": "^2.6.2", - "signal-exit": "^3.0.2", - "which": "^1.3.0" - } - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz", - "integrity": "sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA==", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "test-exclude": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", - "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", - "dev": true, - "requires": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "read-pkg-up": "^4.0.0", - "require-main-filename": "^2.0.0" - } - }, - "tiny-secp256k1": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.6.tgz", - "integrity": "sha512-FmqJZGduTyvsr2cF3375fqGHUovSwDi/QytexX1Se4BPuPZpTE5Ftp5fg+EFSuEf3lhZqgCRjEG3ydUQ/aNiwA==", - "requires": { - "bindings": "^1.3.0", - "bn.js": "^4.11.8", - "create-hmac": "^1.1.7", - "elliptic": "^6.4.0", - "nan": "^2.13.2" - } - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "ts-node": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", - "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } - } - }, - "tslib": { - "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==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^4.0.1", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.29.0" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } - } - }, - "tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "typeforce": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", - "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==", - "dev": true - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "varuint-bitcoin": { - "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" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wif": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", - "integrity": "sha1-CNP1IFbGZnkplyb63g1DKudLRwQ=", - "requires": { - "bs58check": "<3.0.0" - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - }, - "yargs-unparser": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", - "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", - "dev": true, - "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.15", - "yargs": "^13.3.0" - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - } - } -} \ No newline at end of file diff --git a/src/block.d.ts b/src/block.d.ts new file mode 100644 index 000000000..56a2e77d4 --- /dev/null +++ b/src/block.d.ts @@ -0,0 +1,50 @@ +/// +import { Transaction } from './transaction'; +export declare class Block { + static fromBuffer(buffer: Buffer): Block; + static fromHex(hex: string): Block; + static calculateTarget(bits: number): Buffer; + static calculateMerkleRoot(transactions: Transaction[], forWitness?: boolean): Buffer; + version: number; + prevHash?: Buffer; + merkleRoot?: Buffer; + timestamp: number; + witnessCommit?: Buffer; + bits: number; + nonce: number; + transactions?: Transaction[]; + blockHeight: number; + currentSignBlockScript?: Buffer; + currentSignBlockWitnessLimit: number; + currentElidedRoot?: Buffer; + currentSignBlockScriptFull?: Buffer; + currentSignBlockWitnessLimitFull: number; + currentFedpegProgram?: Buffer; + currentFedpegScript?: Buffer; + currentExtensionSpace?: Buffer[]; + proposedSignBlockScript?: Buffer; + proposedSignBlockWitnessLimit: number; + proposedElidedRoot?: Buffer; + proposedSignBlockScriptFull?: Buffer; + proposedSignBlockWitnessLimitFull: number; + proposedFedpegProgram?: Buffer; + proposedFedpegScript?: Buffer; + proposedExtensionSpace?: Buffer[]; + signBlockWitness?: Buffer[]; + challenge?: Buffer; + solution?: Buffer; + getWitnessCommit(): Buffer | null; + hasWitnessCommit(): boolean; + hasWitness(): boolean; + weight(): number; + byteLength(headersOnly?: boolean, allowWitness?: boolean): number; + getHash(): Buffer; + getId(): string; + getUTCDate(): Date; + toBuffer(headersOnly?: boolean): Buffer; + toHex(headersOnly?: boolean): string; + checkTxRoots(): boolean; + checkProofOfWork(): boolean; + private __checkMerkleRoot; + private __checkWitnessCommit; +} diff --git a/src/block.js b/src/block.js index 86d202e7d..6de4b7a9f 100644 --- a/src/block.js +++ b/src/block.js @@ -1,4 +1,36 @@ 'use strict'; +var __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if ( + !desc || + ('get' in desc ? !m.__esModule : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k]; + }, + }; + } + Object.defineProperty(o, k2, desc); + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; + }); +var __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, 'default', { enumerable: true, value: v }); + } + : function (o, v) { + o['default'] = v; + }); var __importStar = (this && this.__importStar) || function (mod) { @@ -6,11 +38,13 @@ var __importStar = var result = {}; if (mod != null) for (var k in mod) - if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result['default'] = mod; + if (k !== 'default' && Object.prototype.hasOwnProperty.call(mod, k)) + __createBinding(result, mod, k); + __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, '__esModule', { value: true }); +exports.Block = void 0; const bufferutils_1 = require('./bufferutils'); const bcrypto = __importStar(require('./crypto')); const transaction_1 = require('./transaction'); @@ -281,7 +315,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 @@ -334,7 +368,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; } From 68531acca440f7fdbb3c61ef48759eca5ab69abb Mon Sep 17 00:00:00 2001 From: sekulicd Date: Fri, 3 Mar 2023 13:03:54 +0100 Subject: [PATCH 12/14] build fix --- package.json | 3 +- yarn.lock | 127 ++++++++++++++++++++++++++------------------------- 2 files changed, 68 insertions(+), 62 deletions(-) diff --git a/package.json b/package.json index bca25cbfd..6198896eb 100644 --- a/package.json +++ b/package.json @@ -49,9 +49,9 @@ ], "dependencies": { "@types/jest": "^27.0.2", - "@vulpemventures/secp256k1-zkp": "^3.0.0", "@types/randombytes": "^2.0.0", "@types/wif": "^2.0.2", + "@vulpemventures/secp256k1-zkp": "^3.0.0", "axios": "^0.21.1", "bech32": "^2.0.0", "bip174-liquid": "^1.0.3", @@ -63,6 +63,7 @@ "bs58check": "^2.0.0", "create-hash": "^1.2.0", "ecpair": "^2.0.1", + "merkle-lib": "^2.0.10", "slip77": "^0.2.0", "typeforce": "^1.11.3", "varuint-bitcoin": "^1.1.2", diff --git a/yarn.lock b/yarn.lock index 269fa4b5e..07ba76eef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -185,7 +185,7 @@ "@cspotcode/source-map-support@^0.8.0": version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz" integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== dependencies: "@jridgewell/trace-mapping" "0.3.9" @@ -240,7 +240,7 @@ "@jridgewell/trace-mapping@0.3.9": version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== dependencies: "@jridgewell/resolve-uri" "^3.0.3" @@ -256,22 +256,22 @@ "@tsconfig/node10@^1.0.7": version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz" integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== "@tsconfig/node12@^1.0.7": version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== "@tsconfig/node14@^1.0.0": version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== "@tsconfig/node16@^1.0.2": version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz" integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== "@types/bs58@^4.0.0": @@ -297,7 +297,7 @@ "@types/jest@^27.0.2": version "27.5.2" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.2.tgz#ec49d29d926500ffb9fd22b84262e862049c026c" + resolved "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz" integrity sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA== dependencies: jest-matcher-utils "^27.0.0" @@ -349,7 +349,7 @@ "@vulpemventures/secp256k1-zkp@^3.0.0": version "3.0.0" - resolved "https://registry.yarnpkg.com/@vulpemventures/secp256k1-zkp/-/secp256k1-zkp-3.0.0.tgz#96b7db89cbc3308c44414ad136a8f2b93c5f1872" + resolved "https://registry.npmjs.org/@vulpemventures/secp256k1-zkp/-/secp256k1-zkp-3.0.0.tgz" integrity sha512-63SSi3QxROV3N8ZjEwloPHwp/t2LygsTXwl+t7U36DPp6yROetZFe8SquKwbOKeQMM0aYDQWtMWpgqRRXdrldg== dependencies: "@types/node" "^13.9.2" @@ -357,12 +357,12 @@ acorn-walk@^8.1.1: version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== acorn@^8.4.1: version "8.8.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz" integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA== aggregate-error@^3.0.0: @@ -375,7 +375,7 @@ aggregate-error@^3.0.0: ansi-colors@4.1.1: version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== ansi-regex@^5.0.1: @@ -399,7 +399,7 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: ansi-styles@^5.0.0: version "5.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== anymatch@~3.1.2: @@ -429,14 +429,14 @@ arg@^4.1.0: argparse@^1.0.7: version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + resolved "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" argparse@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== axios@^0.21.1: @@ -563,7 +563,7 @@ brace-expansion@^1.1.7: brace-expansion@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz" integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== dependencies: balanced-match "^1.0.0" @@ -629,7 +629,7 @@ camelcase@^5.0.0, camelcase@^5.3.1: camelcase@^6.0.0: version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001349: @@ -648,7 +648,7 @@ chalk@^2.0.0, chalk@^2.3.0: chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -656,7 +656,7 @@ chalk@^4.0.0, chalk@^4.1.0: chokidar@3.5.3: version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" @@ -693,7 +693,7 @@ cliui@^6.0.0: cliui@^7.0.2: version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== dependencies: string-width "^4.2.0" @@ -771,7 +771,7 @@ create-hmac@^1.1.4, create-hmac@^1.1.7: create-require@^1.1.0: version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== cross-spawn@^7.0.0, cross-spawn@^7.0.3: @@ -797,7 +797,7 @@ decamelize@^1.2.0: decamelize@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + resolved "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== default-require-extensions@^3.0.0: @@ -816,12 +816,12 @@ dhttp@^3.0.0, dhttp@^3.0.3: diff-sequences@^27.5.1: version "27.5.1" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz" integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ== diff@5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + resolved "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== diff@^4.0.1: @@ -860,7 +860,7 @@ escalade@^3.1.1: escape-string-regexp@4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== escape-string-regexp@^1.0.5: @@ -870,7 +870,7 @@ escape-string-regexp@^1.0.5: esprima@^4.0.0: version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== fill-keys@^1.0.2: @@ -899,7 +899,7 @@ find-cache-dir@^3.2.0: find-up@5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" @@ -915,7 +915,7 @@ find-up@^4.0.0, find-up@^4.1.0: flat@^5.0.2: version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== follow-redirects@^1.14.0: @@ -943,7 +943,7 @@ fs.realpath@^1.0.0: fsevents@~2.3.2: version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== function-bind@^1.1.1: @@ -975,7 +975,7 @@ glob-parent@~5.1.2: glob@7.2.0: version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== dependencies: fs.realpath "^1.0.0" @@ -1134,7 +1134,7 @@ is-object@~1.0.1: is-plain-obj@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== is-stream@^2.0.0: @@ -1149,7 +1149,7 @@ is-typedarray@^1.0.0: is-unicode-supported@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== is-windows@^1.0.2: @@ -1224,7 +1224,7 @@ istanbul-reports@^3.0.2: jest-diff@^27.5.1: version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz" integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw== dependencies: chalk "^4.0.0" @@ -1234,12 +1234,12 @@ jest-diff@^27.5.1: jest-get-type@^27.5.1: version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== jest-matcher-utils@^27.0.0: version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz" integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw== dependencies: chalk "^4.0.0" @@ -1254,7 +1254,7 @@ js-tokens@^4.0.0: js-yaml@4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" @@ -1286,7 +1286,7 @@ locate-path@^5.0.0: locate-path@^6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" @@ -1298,7 +1298,7 @@ lodash.flattendeep@^4.4.0: log-symbols@4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: chalk "^4.1.0" @@ -1335,6 +1335,11 @@ merge-descriptors@~1.0.0: resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz" integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merkle-lib@^2.0.10: + version "2.0.10" + resolved "https://registry.npmjs.org/merkle-lib/-/merkle-lib-2.0.10.tgz" + integrity sha512-XrNQvUbn1DL5hKNe46Ccs+Tu3/PYOlrcZILuGUhb95oKBPjc/nmIC8D462PQkipVDGKRvwhn+QFg2cCdIvmDJA== + minimaldata@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/minimaldata/-/minimaldata-1.0.2.tgz" @@ -1345,7 +1350,7 @@ minimaldata@^1.0.2: minimatch@5.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz" integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== dependencies: brace-expansion "^2.0.1" @@ -1371,14 +1376,14 @@ minimist@^1.2.6: mkdirp@^0.5.3: version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== dependencies: minimist "^1.2.6" mocha@^10.1.0: version "10.1.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.1.0.tgz#dbf1114b7c3f9d0ca5de3133906aea3dfc89ef7a" + resolved "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz" integrity sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg== dependencies: ansi-colors "4.1.1" @@ -1415,12 +1420,12 @@ ms@2.1.2: ms@2.1.3: version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== nanoid@3.3.3: version "3.3.3" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz" integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== node-preload@^0.2.1: @@ -1494,7 +1499,7 @@ p-limit@^2.2.0: p-limit@^3.0.2: version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" @@ -1508,7 +1513,7 @@ p-locate@^4.1.0: p-locate@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" @@ -1590,7 +1595,7 @@ prettier@^2.7.1: pretty-format@^27.0.0, pretty-format@^27.5.1: version "27.5.1" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz" integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== dependencies: ansi-regex "^5.0.1" @@ -1629,7 +1634,7 @@ randombytes@^2.0.1, randombytes@^2.1.0: react-is@^17.0.1: version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== readable-stream@^3.6.0: @@ -1643,7 +1648,7 @@ readable-stream@^3.6.0: readdirp@~3.6.0: version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" @@ -1732,7 +1737,7 @@ semver@^6.0.0, semver@^6.3.0: serialize-javascript@6.0.0: version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + resolved "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz" integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== dependencies: randombytes "^2.1.0" @@ -1795,7 +1800,7 @@ spawn-wrap@^2.0.0: sprintf-js@~1.0.2: version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== statuses@^1.5.0: @@ -1833,12 +1838,12 @@ strip-bom@^4.0.0: strip-json-comments@3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== supports-color@8.1.1: version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== dependencies: has-flag "^4.0.0" @@ -1892,7 +1897,7 @@ to-regex-range@^5.0.1: ts-node@^10.9.1: version "10.9.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz" integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== dependencies: "@cspotcode/source-map-support" "^0.8.0" @@ -1979,7 +1984,7 @@ uuid@^8.3.2: v8-compile-cache-lib@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== varuint-bitcoin@^1.1.2: @@ -2010,7 +2015,7 @@ wif@^2.0.1, wif@^2.0.6: workerpool@6.2.1: version "6.2.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== wrap-ansi@^6.2.0: @@ -2024,7 +2029,7 @@ wrap-ansi@^6.2.0: wrap-ansi@^7.0.0: version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== dependencies: ansi-styles "^4.0.0" @@ -2053,12 +2058,12 @@ y18n@^4.0.0: y18n@^5.0.5: version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== yargs-parser@20.2.4: version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== yargs-parser@^18.1.2: @@ -2071,12 +2076,12 @@ yargs-parser@^18.1.2: yargs-parser@^20.2.2: version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== yargs-unparser@2.0.0: version "2.0.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + resolved "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz" integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== dependencies: camelcase "^6.0.0" @@ -2086,7 +2091,7 @@ yargs-unparser@2.0.0: yargs@16.2.0: version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== dependencies: cliui "^7.0.2" @@ -2121,5 +2126,5 @@ yn@3.1.1: yocto-queue@^0.1.0: version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From a0edc3802e1752878eed943cd4da9d9e409eda68 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Tue, 7 Mar 2023 18:09:04 +0100 Subject: [PATCH 13/14] block serialization --- test/block.spec.ts | 6 +- test/fixtures/block_deserialize.json | 1 + ts_src/block.ts | 224 ++++++++++++++++++++++++--- 3 files changed, 208 insertions(+), 23 deletions(-) diff --git a/test/block.spec.ts b/test/block.spec.ts index b6144f1d5..8a9552c5f 100644 --- a/test/block.spec.ts +++ b/test/block.spec.ts @@ -2,14 +2,16 @@ import * as assert from 'assert'; import { describe } from 'mocha'; import * as fixtures from './fixtures/block_deserialize.json'; import { Block } from '../ts_src/block'; +import { reverseBuffer } from '../src/bufferutils'; describe('block deserialization ', () => { fixtures.test.forEach((f) => { it(f.name, () => { const block = Block.fromBuffer(Buffer.from(f.hex, 'hex')); + assert.strictEqual(block.toBuffer().toString('hex'), f.hex); if (f.name.includes('compact current')) { - assert.strictEqual(block.getHash().toString('hex'), f.hash); + assert.strictEqual(reverseBuffer(block.getHash()).toString('hex'), f.hash); assert.strictEqual(block.version, parseInt(f.version || '', 16)); assert.strictEqual( block.currentSignBlockWitnessLimit, @@ -18,7 +20,7 @@ describe('block deserialization ', () => { } if (f.name.includes('full current')) { - assert.strictEqual(block.getHash().toString('hex'), f.hash); + assert.strictEqual(reverseBuffer(block.getHash()).toString('hex'), f.hash); } assert.strictEqual(block.transactions!.length, f.numOfTx); diff --git a/test/fixtures/block_deserialize.json b/test/fixtures/block_deserialize.json index 35b072ad9..5b1912d80 100644 --- a/test/fixtures/block_deserialize.json +++ b/test/fixtures/block_deserialize.json @@ -17,6 +17,7 @@ "name": "dynamic federation - full current and proposed", "numOfTx": 1, "hash": "e9a5176b1690a448f76fb691ab4d516e60e13a6e7a49454c62dbf0d611ffcce7", + "version": "0x20000000", "hex": "000000a01ecf88cda4d9e6339109c685417c526e8316fe0d3ea058765634dcbb205d3081bd83073b1f1793154ab820c70a1fda32a0d45bb0e1f40c0c61ae03507f49c293debcc45d1400000002220020a6794de47a1612cc94c1b978d5bd1b25873f4cab0b1a76260b0b8af9ad954dc74b0100002200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260015100022200204cb40d59d6e1bbe963f3a63021b0d7d5474b87206978a1129fbffc4d1c1cf7e44b0100002200204ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc332600151000500483045022100ab2203a8a68d7eca8a3a0fac91e7c7802656d937535800da82f7102e1c06f7b80220576f004cb14b95178c71e36bf947bfea496b00b66c18d2eb8febf46362d50e2b0147304402202d4630887d661a50b76b7b32555fd76906ad298ce24483df42310ffbf62d451802200e0d64069e58047c271c1b4051c1ff3d1cba7d32e56abb0d8b8bc30e1bed075b01483045022100c6b196967c661c4543802a895ae731af44862e75d9e3c65b8efdd668727a34af022041ff4d67029052eb6305d25d0fc4813d21a939ff5316a12562d0c9038976f8e1016953210296db75c11ea3a292a372f6c94f5013eaeb379f701857a702f3b83f88da21be6f21021f0d8638c413ef7769cd711ce84c8f192f5a85f0fd6d8e63ddb4d2cf6740b23b2103cadff18e928133df2e670a3715c4e7a81d357de36ddaa5016628e70a3e6a452f53ae010200000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401140101ffffffff020137c495f58d698979ff9124e8c7455fe79b13ddb96afa25c45894eb059868a8c001000000000000000000016a0137c495f58d698979ff9124e8c7455fe79b13ddb96afa25c45894eb059868a8c001000000000000000000266a24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e047d45000000000000012000000000000000000000000000000000000000000000000000000000000000000000000000" }, { diff --git a/ts_src/block.ts b/ts_src/block.ts index 461428358..0e61d2776 100644 --- a/ts_src/block.ts +++ b/ts_src/block.ts @@ -48,10 +48,11 @@ export class Block { const isDyna = block.version >>> 31 === 1; if (isDyna) { block.version &= 0x7fff_ffff; + block.isDyna = true; } block.prevHash = readSlice(32); block.merkleRoot = readSlice(32); - block.timestamp = readUInt32(); + block.timestamp = readUInt32(); block.blockHeight = readUInt32(); if (isDyna) { @@ -71,6 +72,8 @@ export class Block { block.currentSignBlockScript = signBlockScriptCompact; block.currentSignBlockWitnessLimit = signBlockWitnessLimitCompact; block.currentElidedRoot = elidedRootCompact; + block.isCurrent = true; + block.isCurrentCompact = true; break; case 2: // full params const signBlockScriptLengthFull = readVarInt(); @@ -86,13 +89,15 @@ export class Block { for (let i = 0; i < extensionSpaceLength; i++) { const tmpLen = readVarInt(); const tmp = readSlice(tmpLen); - extensionSpace.unshift(tmp); + extensionSpace.push(tmp); } block.currentSignBlockScriptFull = signBlockScriptFull; block.currentSignBlockWitnessLimitFull = signBlockWitnessLimitFull; block.currentFedpegProgram = fedpegProgram; block.currentFedpegScript = fedpegScript; block.currentExtensionSpace = extensionSpace; + block.isCurrent = true; + block.isCurrentFull = true; break; default: throw new Error('bad serialize type for dynafed parameters'); @@ -114,6 +119,8 @@ export class Block { block.proposedSignBlockScript = signBlockScriptCompact; block.proposedSignBlockWitnessLimit = signBlockWitnessLimitCompact; block.proposedElidedRoot = elidedRootCompact; + block.isProposed = true; + block.isProposedCompact = true; break; case 2: // full params const signBlockScriptLengthFull = readVarInt(); @@ -129,13 +136,15 @@ export class Block { for (let i = 0; i < extensionSpaceLength; i++) { const tmpLen = readVarInt(); const tmp = readSlice(tmpLen); - extensionSpace.unshift(tmp); + extensionSpace.push(tmp); } block.proposedSignBlockScriptFull = signBlockScriptFull; block.proposedSignBlockWitnessLimitFull = signBlockWitnessLimitFull; block.proposedFedpegProgram = fedpegProgram; block.proposedFedpegScript = fedpegScript; block.proposedExtensionSpace = extensionSpace; + block.isProposed = true; + block.isProposedFull = true; break; default: throw new Error('bad serialize type for dynafed parameters'); @@ -145,7 +154,7 @@ export class Block { for (let i = 0; i < signBlockWitnessLength; i++) { const tmpLen = readVarInt(); const tmp = readSlice(tmpLen); - signBlockWitness.unshift(tmp); + signBlockWitness.push(tmp); } block.signBlockWitness = signBlockWitness; } else { @@ -225,21 +234,28 @@ export class Block { blockHeight: number = 0; // DYNAMIC FEDERATION PARAMS + isCurrent: boolean = false; // current compact params + isCurrentCompact: boolean = false; currentSignBlockScript?: Buffer = undefined; currentSignBlockWitnessLimit: number = 0; currentElidedRoot?: Buffer = undefined; // current full param + isCurrentFull: boolean = false; currentSignBlockScriptFull?: Buffer = undefined; currentSignBlockWitnessLimitFull: number = 0; currentFedpegProgram?: Buffer = undefined; currentFedpegScript?: Buffer = undefined; currentExtensionSpace?: Buffer[] = undefined; + + isProposed: boolean = false; // proposed compact params + isProposedCompact: boolean = false; proposedSignBlockScript?: Buffer = undefined; proposedSignBlockWitnessLimit: number = 0; proposedElidedRoot?: Buffer = undefined; // proposed full param + isProposedFull: boolean = false; proposedSignBlockScriptFull?: Buffer = undefined; proposedSignBlockWitnessLimitFull: number = 0; proposedFedpegProgram?: Buffer = undefined; @@ -247,6 +263,7 @@ export class Block { proposedExtensionSpace?: Buffer[] = undefined; // SignBlockWitness signBlockWitness?: Buffer[] = undefined; + isDyna: boolean = false; challenge?: Buffer = undefined; solution?: Buffer = undefined; @@ -289,16 +306,6 @@ export class Block { return base * 3 + total; } - byteLength(headersOnly?: boolean, allowWitness: boolean = true): number { - if (headersOnly || !this.transactions) return 80; - - return ( - 80 + - varuint.encodingLength(this.transactions.length) + - this.transactions.reduce((a, x) => a + x.byteLength(allowWitness), 0) - ); - } - getHash(): Buffer { return bcrypto.hash256(this.toBuffer(true)); } @@ -323,7 +330,6 @@ export class Block { slice.copy(buffer, offset); offset += slice.length; }; - const writeInt32 = (i: number): void => { buffer.writeInt32LE(i, offset); offset += 4; @@ -332,19 +338,104 @@ export class Block { buffer.writeUInt32LE(i, offset); offset += 4; }; + const writeUInt8 = (i: number): void => { + buffer.writeUInt8(i, offset); + offset += 1; + }; + const writeVarInt = (i: number): void => { + varuint.encode(i, buffer, offset); + offset += varuint.encode.bytes; + } + + let version: number = this.version; + if (this.isDyna) { + const mask: number = 1 << 31; + version |= mask; + } - writeInt32(this.version); + writeInt32(version); writeSlice(this.prevHash!); writeSlice(this.merkleRoot!); writeUInt32(this.timestamp); - writeUInt32(this.bits); - writeUInt32(this.nonce); + writeUInt32(this.blockHeight); + + if (this.isDyna) { + if (this.isCurrent) { + if (this.isCurrentCompact == null && this.isCurrentFull == null) { + writeUInt8(0); + } + if (this.isCurrentCompact) { + writeUInt8(1); + writeVarInt(this.currentSignBlockScript!.length); + writeSlice(this.currentSignBlockScript!); + writeUInt32(this.currentSignBlockWitnessLimit); + writeSlice(this.currentElidedRoot!); + } + if (this.isCurrentFull) { + writeUInt8(2); + writeVarInt(this.currentSignBlockScriptFull!.length); + writeSlice(this.currentSignBlockScriptFull!); + writeUInt32(this.currentSignBlockWitnessLimitFull); + writeVarInt(this.currentFedpegProgram!.length); + writeSlice(this.currentFedpegProgram!); + writeVarInt(this.currentFedpegScript!.length); + writeSlice(this.currentFedpegScript!); + writeVarInt(this.currentExtensionSpace!.length); + this.currentExtensionSpace!.forEach((item) => { + writeVarInt(item!.length); + writeSlice(item); + }); + } + } else { + writeUInt8(0); + } + if (this.isProposed) { + if (this.isProposedCompact) { + writeUInt8(1); + writeVarInt(this.proposedSignBlockScript!.length); + writeSlice(this.proposedSignBlockScript!); + writeUInt32(this.proposedSignBlockWitnessLimit); + writeSlice(this.proposedElidedRoot!); + } + if (this.isProposedFull) { + writeUInt8(2); + writeVarInt(this.proposedSignBlockScriptFull!.length); + writeSlice(this.proposedSignBlockScriptFull!); + writeUInt32(this.proposedSignBlockWitnessLimitFull); + writeVarInt(this.proposedFedpegProgram!.length); + writeSlice(this.proposedFedpegProgram!); + writeVarInt(this.proposedFedpegScript!.length); + writeSlice(this.proposedFedpegScript!); + writeVarInt(this.proposedExtensionSpace!.length); + this.proposedExtensionSpace!.forEach((item) => { + writeVarInt(item!.length); + writeSlice(item); + }); + } else { + writeUInt8(0); + } + } else { + writeUInt8(0); + } + if (!headersOnly) { + writeVarInt(this.signBlockWitness!.length); + this.signBlockWitness!.forEach((item) => { + writeVarInt(item!.length); + writeSlice(item); + }); + } + } else { + writeVarInt(this.challenge!.length); + writeSlice(this.challenge!); + if (!headersOnly) { + writeVarInt(this.solution!.length); + writeSlice(this.solution!); + } + } if (headersOnly || !this.transactions) return buffer; - varuint.encode(this.transactions.length, buffer, offset); - offset += varuint.encode.bytes; - + writeVarInt(this.transactions.length); this.transactions.forEach((tx) => { const txSize = tx.byteLength(); // TODO: extract from toBuffer? tx.toBuffer(buffer, offset); @@ -393,6 +484,97 @@ export class Block { ); return this.witnessCommit!.compare(actualWitnessCommit) === 0; } + + byteLength(forHash?: boolean, allowWitness: boolean = true): number { + let size = 0; + size += 4; // version + size += 32; // prevHash + size += 32; // merkleRoot + size += 4; // timestamp + size += 4; // height + + if (this.isDyna) { + size += 2; // dyna params type current/propose + if (this.isCurrent) { + if (this.isCurrentCompact) { + size += getNumberMinByteSize(this.currentSignBlockScript!.length); // currentSignBlockScript length + size += this.currentSignBlockScript!.length; // currentSignBlockScript + size += 4; // currentSignBlockWitnessLimit + size += 32; // currentElidedRoot + } + if (this.isCurrentFull) { + size += getNumberMinByteSize(this.currentSignBlockScriptFull!.length); // currentSignBlockScriptFull length + size += this.currentSignBlockScriptFull!.length; // currentSignBlockScriptFull + size += 4; // currentSignBlockWitnessLimitFull + size += getNumberMinByteSize(this.currentFedpegProgram!.length); // currentFedpegProgram length + size += this.currentFedpegProgram!.length; // currentFedpegProgram + size += getNumberMinByteSize(this.currentFedpegScript!.length); // currentFedpegScript length + size += this.currentFedpegScript!.length; // currentFedpegScript + size += getNumberMinByteSize(this.currentExtensionSpace!.length); // currentExtensionSpace length + this.currentExtensionSpace!.forEach((item) => { + size += getNumberMinByteSize(item!.length); // currentExtensionSpace item length + size += item!.length; // currentExtensionSpace item + }); + } + } + if (this.isProposed) { + if (this.isProposedCompact) { + size += getNumberMinByteSize(this.proposedSignBlockScript!.length); // proposedSignBlockScript length + size += this.proposedSignBlockScript!.length; // proposedSignBlockScript + size += 4; // proposedSignBlockWitnessLimit + size += 32; // proposedElidedRoot + } + if (this.isProposedFull) { + size += getNumberMinByteSize(this.proposedSignBlockScriptFull!.length); // proposedSignBlockScriptFull length + size += this.proposedSignBlockScriptFull!.length; // proposedSignBlockScriptFull + size += 4; // proposedSignBlockWitnessLimitFull + size += getNumberMinByteSize(this.proposedFedpegProgram!.length); // proposedFedpegProgram length + size += this.proposedFedpegProgram!.length; // proposedFedpegProgram + size += getNumberMinByteSize(this.proposedFedpegScript!.length); // proposedFedpegScript length + size += this.proposedFedpegScript!.length; // proposedFedpegScript + size += getNumberMinByteSize(this.proposedExtensionSpace!.length); // proposedExtensionSpace length + this.proposedExtensionSpace!.forEach((item) => { + size += getNumberMinByteSize(item!.length); // proposedExtensionSpace item length + size += item!.length; // proposedExtensionSpace item + }); + } + } + if (!forHash) { + size += getNumberMinByteSize(this.signBlockWitness!.length); + this.signBlockWitness!.forEach((item) => { + size += getNumberMinByteSize(item!.length); // signBlockWitness item length + size += item!.length; // signBlockWitness item + }); + } + }else { + const challengeLength = this.challenge?.length ?? 0 + size += getNumberMinByteSize(challengeLength); + size += challengeLength;// challenge + if (!forHash) { + const solutionLength = this.solution?.length ?? 0 + size += getNumberMinByteSize(solutionLength); + size += solutionLength;// solution + } + } + if (!forHash) { + size += this.transactions?.length ? varuint.encodingLength(this.transactions.length) : 0; + size += this.transactions?.reduce((a, x) => a + x.byteLength(allowWitness), 0) ?? 0; + } + return size; + } +} + +function getNumberMinByteSize(num: number): number { + if (num < 0) { + throw new Error('negative numbers are not supported.'); + } + for (let i = 1; i <= 8; i++) { + const maxVal = Math.pow(2, i * 8) - 1; + if (num <= maxVal) { + return i; + } + } + throw new Error('number is too large to be represented as a JavaScript number.'); } function txesHaveWitnessCommit(transactions: Transaction[]): boolean { From 5cb0d9cc0d8d00a766f8d46c5499a7bb7b837431 Mon Sep 17 00:00:00 2001 From: sekulicd Date: Tue, 7 Mar 2023 18:36:24 +0100 Subject: [PATCH 14/14] prettier --- src/block.d.ts | 9 +- src/block.js | 220 +++++++++++++++++++++++++++++++++++++++++---- test/block.spec.ts | 10 ++- ts_src/block.ts | 122 +++++++++++++------------ 4 files changed, 286 insertions(+), 75 deletions(-) diff --git a/src/block.d.ts b/src/block.d.ts index 56a2e77d4..6485f7232 100644 --- a/src/block.d.ts +++ b/src/block.d.ts @@ -14,30 +14,36 @@ export declare class Block { nonce: number; transactions?: Transaction[]; blockHeight: number; + isCurrent: boolean; + isCurrentCompact: boolean; currentSignBlockScript?: Buffer; currentSignBlockWitnessLimit: number; currentElidedRoot?: Buffer; + isCurrentFull: boolean; currentSignBlockScriptFull?: Buffer; currentSignBlockWitnessLimitFull: number; currentFedpegProgram?: Buffer; currentFedpegScript?: Buffer; currentExtensionSpace?: Buffer[]; + isProposed: boolean; + isProposedCompact: boolean; proposedSignBlockScript?: Buffer; proposedSignBlockWitnessLimit: number; proposedElidedRoot?: Buffer; + isProposedFull: boolean; proposedSignBlockScriptFull?: Buffer; proposedSignBlockWitnessLimitFull: number; proposedFedpegProgram?: Buffer; proposedFedpegScript?: Buffer; proposedExtensionSpace?: Buffer[]; signBlockWitness?: Buffer[]; + isDyna: boolean; challenge?: Buffer; solution?: Buffer; getWitnessCommit(): Buffer | null; hasWitnessCommit(): boolean; hasWitness(): boolean; weight(): number; - byteLength(headersOnly?: boolean, allowWitness?: boolean): number; getHash(): Buffer; getId(): string; getUTCDate(): Date; @@ -47,4 +53,5 @@ export declare class Block { checkProofOfWork(): boolean; private __checkMerkleRoot; private __checkWitnessCommit; + byteLength(forHash?: boolean, allowWitness?: boolean): number; } diff --git a/src/block.js b/src/block.js index 6de4b7a9f..90ea61163 100644 --- a/src/block.js +++ b/src/block.js @@ -70,21 +70,27 @@ class Block { this.transactions = undefined; this.blockHeight = 0; // DYNAMIC FEDERATION PARAMS + this.isCurrent = false; // current compact params + this.isCurrentCompact = false; this.currentSignBlockScript = undefined; this.currentSignBlockWitnessLimit = 0; this.currentElidedRoot = undefined; // current full param + this.isCurrentFull = false; this.currentSignBlockScriptFull = undefined; this.currentSignBlockWitnessLimitFull = 0; this.currentFedpegProgram = undefined; this.currentFedpegScript = undefined; this.currentExtensionSpace = undefined; + this.isProposed = false; // proposed compact params + this.isProposedCompact = false; this.proposedSignBlockScript = undefined; this.proposedSignBlockWitnessLimit = 0; this.proposedElidedRoot = undefined; // proposed full param + this.isProposedFull = false; this.proposedSignBlockScriptFull = undefined; this.proposedSignBlockWitnessLimitFull = 0; this.proposedFedpegProgram = undefined; @@ -92,6 +98,7 @@ class Block { this.proposedExtensionSpace = undefined; // SignBlockWitness this.signBlockWitness = undefined; + this.isDyna = false; this.challenge = undefined; this.solution = undefined; } @@ -122,6 +129,7 @@ class Block { const isDyna = block.version >>> 31 === 1; if (isDyna) { block.version &= 2147483647; + block.isDyna = true; } block.prevHash = readSlice(32); block.merkleRoot = readSlice(32); @@ -143,6 +151,8 @@ class Block { block.currentSignBlockScript = signBlockScriptCompact; block.currentSignBlockWitnessLimit = signBlockWitnessLimitCompact; block.currentElidedRoot = elidedRootCompact; + block.isCurrent = true; + block.isCurrentCompact = true; break; case 2: // full params const signBlockScriptLengthFull = readVarInt(); @@ -157,13 +167,15 @@ class Block { for (let i = 0; i < extensionSpaceLength; i++) { const tmpLen = readVarInt(); const tmp = readSlice(tmpLen); - extensionSpace.unshift(tmp); + extensionSpace.push(tmp); } block.currentSignBlockScriptFull = signBlockScriptFull; block.currentSignBlockWitnessLimitFull = signBlockWitnessLimitFull; block.currentFedpegProgram = fedpegProgram; block.currentFedpegScript = fedpegScript; block.currentExtensionSpace = extensionSpace; + block.isCurrent = true; + block.isCurrentFull = true; break; default: throw new Error('bad serialize type for dynafed parameters'); @@ -183,6 +195,8 @@ class Block { block.proposedSignBlockScript = signBlockScriptCompact; block.proposedSignBlockWitnessLimit = signBlockWitnessLimitCompact; block.proposedElidedRoot = elidedRootCompact; + block.isProposed = true; + block.isProposedCompact = true; break; case 2: // full params const signBlockScriptLengthFull = readVarInt(); @@ -197,13 +211,15 @@ class Block { for (let i = 0; i < extensionSpaceLength; i++) { const tmpLen = readVarInt(); const tmp = readSlice(tmpLen); - extensionSpace.unshift(tmp); + extensionSpace.push(tmp); } block.proposedSignBlockScriptFull = signBlockScriptFull; block.proposedSignBlockWitnessLimitFull = signBlockWitnessLimitFull; block.proposedFedpegProgram = fedpegProgram; block.proposedFedpegScript = fedpegScript; block.proposedExtensionSpace = extensionSpace; + block.isProposed = true; + block.isProposedFull = true; break; default: throw new Error('bad serialize type for dynafed parameters'); @@ -213,7 +229,7 @@ class Block { for (let i = 0; i < signBlockWitnessLength; i++) { const tmpLen = readVarInt(); const tmp = readSlice(tmpLen); - signBlockWitness.unshift(tmp); + signBlockWitness.push(tmp); } block.signBlockWitness = signBlockWitness; } else { @@ -303,14 +319,6 @@ class Block { const total = this.byteLength(false, true); return base * 3 + total; } - byteLength(headersOnly, allowWitness = true) { - if (headersOnly || !this.transactions) return 80; - return ( - 80 + - varuint.encodingLength(this.transactions.length) + - this.transactions.reduce((a, x) => a + x.byteLength(allowWitness), 0) - ); - } getHash() { return bcrypto.hash256(this.toBuffer(true)); } @@ -338,15 +346,99 @@ class Block { buffer.writeUInt32LE(i, offset); offset += 4; }; - writeInt32(this.version); + const writeUInt8 = (i) => { + buffer.writeUInt8(i, offset); + offset += 1; + }; + const writeVarInt = (i) => { + varuint.encode(i, buffer, offset); + offset += varuint.encode.bytes; + }; + let version = this.version; + if (this.isDyna) { + const mask = 1 << 31; + version |= mask; + } + writeInt32(version); writeSlice(this.prevHash); writeSlice(this.merkleRoot); writeUInt32(this.timestamp); - writeUInt32(this.bits); - writeUInt32(this.nonce); + writeUInt32(this.blockHeight); + if (this.isDyna) { + if (this.isCurrent) { + if (this.isCurrentCompact == null && this.isCurrentFull == null) { + writeUInt8(0); + } + if (this.isCurrentCompact) { + writeUInt8(1); + writeVarInt(this.currentSignBlockScript.length); + writeSlice(this.currentSignBlockScript); + writeUInt32(this.currentSignBlockWitnessLimit); + writeSlice(this.currentElidedRoot); + } + if (this.isCurrentFull) { + writeUInt8(2); + writeVarInt(this.currentSignBlockScriptFull.length); + writeSlice(this.currentSignBlockScriptFull); + writeUInt32(this.currentSignBlockWitnessLimitFull); + writeVarInt(this.currentFedpegProgram.length); + writeSlice(this.currentFedpegProgram); + writeVarInt(this.currentFedpegScript.length); + writeSlice(this.currentFedpegScript); + writeVarInt(this.currentExtensionSpace.length); + this.currentExtensionSpace.forEach((item) => { + writeVarInt(item.length); + writeSlice(item); + }); + } + } else { + writeUInt8(0); + } + if (this.isProposed) { + if (this.isProposedCompact) { + writeUInt8(1); + writeVarInt(this.proposedSignBlockScript.length); + writeSlice(this.proposedSignBlockScript); + writeUInt32(this.proposedSignBlockWitnessLimit); + writeSlice(this.proposedElidedRoot); + } + if (this.isProposedFull) { + writeUInt8(2); + writeVarInt(this.proposedSignBlockScriptFull.length); + writeSlice(this.proposedSignBlockScriptFull); + writeUInt32(this.proposedSignBlockWitnessLimitFull); + writeVarInt(this.proposedFedpegProgram.length); + writeSlice(this.proposedFedpegProgram); + writeVarInt(this.proposedFedpegScript.length); + writeSlice(this.proposedFedpegScript); + writeVarInt(this.proposedExtensionSpace.length); + this.proposedExtensionSpace.forEach((item) => { + writeVarInt(item.length); + writeSlice(item); + }); + } else { + writeUInt8(0); + } + } else { + writeUInt8(0); + } + if (!headersOnly) { + writeVarInt(this.signBlockWitness.length); + this.signBlockWitness.forEach((item) => { + writeVarInt(item.length); + writeSlice(item); + }); + } + } else { + writeVarInt(this.challenge.length); + writeSlice(this.challenge); + if (!headersOnly) { + writeVarInt(this.solution.length); + writeSlice(this.solution); + } + } if (headersOnly || !this.transactions) return buffer; - varuint.encode(this.transactions.length, buffer, offset); - offset += varuint.encode.bytes; + writeVarInt(this.transactions.length); this.transactions.forEach((tx) => { const txSize = tx.byteLength(); // TODO: extract from toBuffer? tx.toBuffer(buffer, offset); @@ -386,8 +478,104 @@ class Block { ); return this.witnessCommit.compare(actualWitnessCommit) === 0; } + byteLength(forHash, allowWitness = true) { + let size = 0; + size += 4; // version + size += 32; // prevHash + size += 32; // merkleRoot + size += 4; // timestamp + size += 4; // height + if (this.isDyna) { + size += 2; // dyna params type current/propose + if (this.isCurrent) { + if (this.isCurrentCompact) { + size += getNumberMinByteSize(this.currentSignBlockScript.length); // currentSignBlockScript length + size += this.currentSignBlockScript.length; // currentSignBlockScript + size += 4; // currentSignBlockWitnessLimit + size += 32; // currentElidedRoot + } + if (this.isCurrentFull) { + size += getNumberMinByteSize(this.currentSignBlockScriptFull.length); // currentSignBlockScriptFull length + size += this.currentSignBlockScriptFull.length; // currentSignBlockScriptFull + size += 4; // currentSignBlockWitnessLimitFull + size += getNumberMinByteSize(this.currentFedpegProgram.length); // currentFedpegProgram length + size += this.currentFedpegProgram.length; // currentFedpegProgram + size += getNumberMinByteSize(this.currentFedpegScript.length); // currentFedpegScript length + size += this.currentFedpegScript.length; // currentFedpegScript + size += getNumberMinByteSize(this.currentExtensionSpace.length); // currentExtensionSpace length + this.currentExtensionSpace.forEach((item) => { + size += getNumberMinByteSize(item.length); // currentExtensionSpace item length + size += item.length; // currentExtensionSpace item + }); + } + } + if (this.isProposed) { + if (this.isProposedCompact) { + size += getNumberMinByteSize(this.proposedSignBlockScript.length); // proposedSignBlockScript length + size += this.proposedSignBlockScript.length; // proposedSignBlockScript + size += 4; // proposedSignBlockWitnessLimit + size += 32; // proposedElidedRoot + } + if (this.isProposedFull) { + size += getNumberMinByteSize(this.proposedSignBlockScriptFull.length); // proposedSignBlockScriptFull length + size += this.proposedSignBlockScriptFull.length; // proposedSignBlockScriptFull + size += 4; // proposedSignBlockWitnessLimitFull + size += getNumberMinByteSize(this.proposedFedpegProgram.length); // proposedFedpegProgram length + size += this.proposedFedpegProgram.length; // proposedFedpegProgram + size += getNumberMinByteSize(this.proposedFedpegScript.length); // proposedFedpegScript length + size += this.proposedFedpegScript.length; // proposedFedpegScript + size += getNumberMinByteSize(this.proposedExtensionSpace.length); // proposedExtensionSpace length + this.proposedExtensionSpace.forEach((item) => { + size += getNumberMinByteSize(item.length); // proposedExtensionSpace item length + size += item.length; // proposedExtensionSpace item + }); + } + } + if (!forHash) { + size += getNumberMinByteSize(this.signBlockWitness.length); + this.signBlockWitness.forEach((item) => { + size += getNumberMinByteSize(item.length); // signBlockWitness item length + size += item.length; // signBlockWitness item + }); + } + } else { + const challengeLength = this.challenge?.length ?? 0; + size += getNumberMinByteSize(challengeLength); + size += challengeLength; // challenge + if (!forHash) { + const solutionLength = this.solution?.length ?? 0; + size += getNumberMinByteSize(solutionLength); + size += solutionLength; // solution + } + } + if (!forHash) { + size += this.transactions?.length + ? varuint.encodingLength(this.transactions.length) + : 0; + size += + this.transactions?.reduce( + (a, x) => a + x.byteLength(allowWitness), + 0, + ) ?? 0; + } + return size; + } } exports.Block = Block; +function getNumberMinByteSize(num) { + if (num < 0) { + throw new Error('negative numbers are not supported.'); + } + for (let i = 1; i <= 8; i++) { + const maxVal = Math.pow(2, i * 8) - 1; + if (num <= maxVal) { + return i; + } + } + throw new Error( + 'number is too large to be represented as a JavaScript number.', + ); +} function txesHaveWitnessCommit(transactions) { return ( transactions instanceof Array && diff --git a/test/block.spec.ts b/test/block.spec.ts index 8a9552c5f..ee9a3a23a 100644 --- a/test/block.spec.ts +++ b/test/block.spec.ts @@ -11,7 +11,10 @@ describe('block deserialization ', () => { assert.strictEqual(block.toBuffer().toString('hex'), f.hex); if (f.name.includes('compact current')) { - assert.strictEqual(reverseBuffer(block.getHash()).toString('hex'), f.hash); + assert.strictEqual( + reverseBuffer(block.getHash()).toString('hex'), + f.hash, + ); assert.strictEqual(block.version, parseInt(f.version || '', 16)); assert.strictEqual( block.currentSignBlockWitnessLimit, @@ -20,7 +23,10 @@ describe('block deserialization ', () => { } if (f.name.includes('full current')) { - assert.strictEqual(reverseBuffer(block.getHash()).toString('hex'), f.hash); + assert.strictEqual( + reverseBuffer(block.getHash()).toString('hex'), + f.hash, + ); } assert.strictEqual(block.transactions!.length, f.numOfTx); diff --git a/ts_src/block.ts b/ts_src/block.ts index 0e61d2776..8042ce6bb 100644 --- a/ts_src/block.ts +++ b/ts_src/block.ts @@ -52,7 +52,7 @@ export class Block { } block.prevHash = readSlice(32); block.merkleRoot = readSlice(32); - block.timestamp = readUInt32(); + block.timestamp = readUInt32(); block.blockHeight = readUInt32(); if (isDyna) { @@ -345,7 +345,7 @@ export class Block { const writeVarInt = (i: number): void => { varuint.encode(i, buffer, offset); offset += varuint.encode.bytes; - } + }; let version: number = this.version; if (this.isDyna) { @@ -496,69 +496,77 @@ export class Block { if (this.isDyna) { size += 2; // dyna params type current/propose if (this.isCurrent) { - if (this.isCurrentCompact) { - size += getNumberMinByteSize(this.currentSignBlockScript!.length); // currentSignBlockScript length - size += this.currentSignBlockScript!.length; // currentSignBlockScript - size += 4; // currentSignBlockWitnessLimit - size += 32; // currentElidedRoot - } - if (this.isCurrentFull) { - size += getNumberMinByteSize(this.currentSignBlockScriptFull!.length); // currentSignBlockScriptFull length - size += this.currentSignBlockScriptFull!.length; // currentSignBlockScriptFull - size += 4; // currentSignBlockWitnessLimitFull - size += getNumberMinByteSize(this.currentFedpegProgram!.length); // currentFedpegProgram length - size += this.currentFedpegProgram!.length; // currentFedpegProgram - size += getNumberMinByteSize(this.currentFedpegScript!.length); // currentFedpegScript length - size += this.currentFedpegScript!.length; // currentFedpegScript - size += getNumberMinByteSize(this.currentExtensionSpace!.length); // currentExtensionSpace length - this.currentExtensionSpace!.forEach((item) => { - size += getNumberMinByteSize(item!.length); // currentExtensionSpace item length - size += item!.length; // currentExtensionSpace item - }); - } + if (this.isCurrentCompact) { + size += getNumberMinByteSize(this.currentSignBlockScript!.length); // currentSignBlockScript length + size += this.currentSignBlockScript!.length; // currentSignBlockScript + size += 4; // currentSignBlockWitnessLimit + size += 32; // currentElidedRoot + } + if (this.isCurrentFull) { + size += getNumberMinByteSize(this.currentSignBlockScriptFull!.length); // currentSignBlockScriptFull length + size += this.currentSignBlockScriptFull!.length; // currentSignBlockScriptFull + size += 4; // currentSignBlockWitnessLimitFull + size += getNumberMinByteSize(this.currentFedpegProgram!.length); // currentFedpegProgram length + size += this.currentFedpegProgram!.length; // currentFedpegProgram + size += getNumberMinByteSize(this.currentFedpegScript!.length); // currentFedpegScript length + size += this.currentFedpegScript!.length; // currentFedpegScript + size += getNumberMinByteSize(this.currentExtensionSpace!.length); // currentExtensionSpace length + this.currentExtensionSpace!.forEach((item) => { + size += getNumberMinByteSize(item!.length); // currentExtensionSpace item length + size += item!.length; // currentExtensionSpace item + }); } - if (this.isProposed) { - if (this.isProposedCompact) { - size += getNumberMinByteSize(this.proposedSignBlockScript!.length); // proposedSignBlockScript length - size += this.proposedSignBlockScript!.length; // proposedSignBlockScript - size += 4; // proposedSignBlockWitnessLimit - size += 32; // proposedElidedRoot - } - if (this.isProposedFull) { - size += getNumberMinByteSize(this.proposedSignBlockScriptFull!.length); // proposedSignBlockScriptFull length - size += this.proposedSignBlockScriptFull!.length; // proposedSignBlockScriptFull - size += 4; // proposedSignBlockWitnessLimitFull - size += getNumberMinByteSize(this.proposedFedpegProgram!.length); // proposedFedpegProgram length - size += this.proposedFedpegProgram!.length; // proposedFedpegProgram - size += getNumberMinByteSize(this.proposedFedpegScript!.length); // proposedFedpegScript length - size += this.proposedFedpegScript!.length; // proposedFedpegScript - size += getNumberMinByteSize(this.proposedExtensionSpace!.length); // proposedExtensionSpace length - this.proposedExtensionSpace!.forEach((item) => { - size += getNumberMinByteSize(item!.length); // proposedExtensionSpace item length - size += item!.length; // proposedExtensionSpace item - }); - } + } + if (this.isProposed) { + if (this.isProposedCompact) { + size += getNumberMinByteSize(this.proposedSignBlockScript!.length); // proposedSignBlockScript length + size += this.proposedSignBlockScript!.length; // proposedSignBlockScript + size += 4; // proposedSignBlockWitnessLimit + size += 32; // proposedElidedRoot } - if (!forHash) { - size += getNumberMinByteSize(this.signBlockWitness!.length); - this.signBlockWitness!.forEach((item) => { - size += getNumberMinByteSize(item!.length); // signBlockWitness item length - size += item!.length; // signBlockWitness item + if (this.isProposedFull) { + size += getNumberMinByteSize( + this.proposedSignBlockScriptFull!.length, + ); // proposedSignBlockScriptFull length + size += this.proposedSignBlockScriptFull!.length; // proposedSignBlockScriptFull + size += 4; // proposedSignBlockWitnessLimitFull + size += getNumberMinByteSize(this.proposedFedpegProgram!.length); // proposedFedpegProgram length + size += this.proposedFedpegProgram!.length; // proposedFedpegProgram + size += getNumberMinByteSize(this.proposedFedpegScript!.length); // proposedFedpegScript length + size += this.proposedFedpegScript!.length; // proposedFedpegScript + size += getNumberMinByteSize(this.proposedExtensionSpace!.length); // proposedExtensionSpace length + this.proposedExtensionSpace!.forEach((item) => { + size += getNumberMinByteSize(item!.length); // proposedExtensionSpace item length + size += item!.length; // proposedExtensionSpace item }); } - }else { - const challengeLength = this.challenge?.length ?? 0 + } + if (!forHash) { + size += getNumberMinByteSize(this.signBlockWitness!.length); + this.signBlockWitness!.forEach((item) => { + size += getNumberMinByteSize(item!.length); // signBlockWitness item length + size += item!.length; // signBlockWitness item + }); + } + } else { + const challengeLength = this.challenge?.length ?? 0; size += getNumberMinByteSize(challengeLength); - size += challengeLength;// challenge + size += challengeLength; // challenge if (!forHash) { - const solutionLength = this.solution?.length ?? 0 + const solutionLength = this.solution?.length ?? 0; size += getNumberMinByteSize(solutionLength); - size += solutionLength;// solution + size += solutionLength; // solution } } if (!forHash) { - size += this.transactions?.length ? varuint.encodingLength(this.transactions.length) : 0; - size += this.transactions?.reduce((a, x) => a + x.byteLength(allowWitness), 0) ?? 0; + size += this.transactions?.length + ? varuint.encodingLength(this.transactions.length) + : 0; + size += + this.transactions?.reduce( + (a, x) => a + x.byteLength(allowWitness), + 0, + ) ?? 0; } return size; } @@ -574,7 +582,9 @@ function getNumberMinByteSize(num: number): number { return i; } } - throw new Error('number is too large to be represented as a JavaScript number.'); + throw new Error( + 'number is too large to be represented as a JavaScript number.', + ); } function txesHaveWitnessCommit(transactions: Transaction[]): boolean {