├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE-APACHE2.md ├── LICENSE-MIT.md ├── README.md ├── bin ├── .eslintrc.js ├── README.md ├── cli ├── package-lock.json ├── package.json └── vendor │ └── bcfg ├── c ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Makefile ├── README.md ├── build │ └── .gitkeep ├── csrc │ ├── btcspv.c │ ├── btcspv.h │ ├── check_btcspv.c │ ├── evalspv.c │ ├── evalspv.h │ ├── swap-demo.c │ └── test_utils.h ├── deps │ ├── jsmn.h │ ├── molecule │ │ ├── VERSION │ │ ├── molecule_builder.h │ │ └── molecule_reader.h │ ├── rmd160.h │ └── sha256.h ├── risc.Makefile └── src │ ├── lib.rs │ └── test_utils.rs ├── golang ├── .gitignore ├── LICENSE ├── README.md ├── btcspv │ ├── bitcoin_spv.go │ ├── bitcoin_spv_test.go │ ├── test_utils │ │ ├── bitcoin_spv_test_types.go │ │ ├── utils_test_types.go │ │ └── validate_spv_test_types.go │ ├── types.go │ ├── types_test.go │ ├── utils.go │ ├── utils_test.go │ ├── validate_spv.go │ └── validate_spv_test.go ├── cli │ ├── header.go │ ├── prove.go │ ├── spvcli.go │ ├── vin.go │ └── vout.go ├── go.mod └── go.sum ├── js ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── README.md ├── babel.config.js ├── clients │ ├── .eslintignore │ ├── .eslintrc.js │ ├── README.md │ ├── lib │ │ ├── BcoinClient.js │ │ ├── index.js │ │ └── vendor │ │ │ ├── abstractblock.js │ │ │ ├── bclient.js │ │ │ ├── block.js │ │ │ ├── bn.js │ │ │ ├── bsert.js │ │ │ ├── bufio.js │ │ │ ├── common.js │ │ │ ├── consensus.js │ │ │ ├── hash256.js │ │ │ ├── headers.js │ │ │ ├── input.js │ │ │ ├── merkle.js │ │ │ ├── n64.js │ │ │ ├── opcode.js │ │ │ ├── outpoint.js │ │ │ ├── output.js │ │ │ ├── script.js │ │ │ ├── scriptnum.js │ │ │ ├── sha256.js │ │ │ ├── stack.js │ │ │ ├── tx.js │ │ │ ├── util.js │ │ │ └── witness.js │ ├── package-lock.json │ ├── package.json │ └── test │ │ └── BcoinClient-test.js ├── package-lock.json ├── package.json ├── src │ ├── BTCUtils.js │ ├── ValidateSPV.js │ ├── index.js │ ├── lib │ │ ├── ripemd160.js │ │ └── sha256.js │ ├── ser.js │ ├── sighash.js │ └── utils.js └── test │ ├── BTCUtils.test.js │ ├── ValidateSPV.test.js │ ├── constants.js │ ├── ser.test.js │ ├── sighash.test.js │ └── utils.test.js ├── logo-group.jpg ├── logo-summa-ccg.jpg ├── python ├── .coveragerc ├── .gitignore ├── Pipfile ├── Pipfile.lock ├── README.md ├── btcspv │ ├── __init__.py │ ├── py.typed │ ├── ser.py │ ├── test │ │ ├── test_ser.py │ │ ├── test_utils.py │ │ └── test_validate_spv.py │ ├── types.py │ ├── utils.py │ └── validate_spv.py ├── scripts │ └── run_tests.sh └── setup.py ├── run_tests.sh ├── rust ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md └── src │ ├── btcspv.rs │ ├── lib.rs │ ├── macros.rs │ ├── std_types.rs │ ├── types.rs │ ├── utils.rs │ └── validatespv.rs ├── solidity ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .solcover.js ├── .soliumignore ├── .soliumrc.json ├── README.md ├── contracts │ ├── CheckBitcoinSigs.sol │ ├── Migrations.sol │ ├── SafeMath.sol │ ├── ViewBTC.sol │ ├── ViewSPV.sol │ └── test │ │ ├── CheckBitcoinSigsTest.sol │ │ ├── ViewBTCTest.sol │ │ └── ViewSPVtest.sol ├── migrations │ └── 1_initial_migration.js ├── package-lock.json ├── package.json ├── test │ ├── CheckBitcoinSigs.test.js │ ├── ViewBTC.test.js │ ├── ViewSPV.test.js │ ├── constants.js │ └── utils.js └── truffle-config.js ├── testProofs.json └── testVectors.json /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | .pytest_cache 3 | build/ 4 | blocks/ 5 | __pycache__/ 6 | node_modules/ 7 | .mypy_cache 8 | 9 | # coverage 10 | scTopics 11 | coverage/ 12 | coverageEnv/ 13 | coverage.json 14 | target/ 15 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "c/deps/ckb-c-stdlib"] 2 | path = c/deps/ckb-c-stdlib 3 | url = https://github.com/nervosnetwork/ckb-c-stdlib.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | matrix: 2 | include: 3 | - language: go 4 | go: 1.13.1 5 | env: 6 | - GO111MODULE=on 7 | before_script: 8 | - cd golang 9 | - curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(go env GOPATH)/bin v1.20.0 10 | script: 11 | - go test ./btcspv 12 | 13 | - language: node_js 14 | node_js: 10.14.2 15 | before_script: 16 | - cp testVectors.json solidity/test 17 | - cd solidity 18 | - npm install -g truffle 19 | - npm install 20 | script: 21 | - npm run lint 22 | - npm run test 23 | 24 | - language: node_js 25 | node_js: 10.14.2 26 | before_script: 27 | - cd js 28 | - npm install 29 | script: 30 | - npm run lint 31 | - npm run test 32 | 33 | - language: python 34 | python: 35 | - "3.6" 36 | before_install: 37 | - cd python 38 | - pip install pipenv 39 | install: 40 | - pipenv install --dev 41 | script: 42 | - pipenv run test 43 | 44 | - language: rust 45 | rust: stable 46 | cache: 47 | directories: 48 | - ./target 49 | - /home/travis/.cargo 50 | before_install: 51 | - cd c 52 | - sudo apt-get install build-essential 53 | - sudo apt-get install check 54 | install: 55 | - make setup 56 | script: 57 | - make 58 | 59 | - language: rust 60 | rust: stable 61 | cache: 62 | directories: 63 | - ./target 64 | - /home/travis/.cargo 65 | before_script: 66 | - cd rust 67 | - cargo install cargo-nono 68 | script: 69 | - cargo nono check --no-default-features 70 | - cargo test 71 | - cargo test --lib --no-default-features 72 | -------------------------------------------------------------------------------- /LICENSE-APACHE2.md: -------------------------------------------------------------------------------- 1 | Copyright 2020 Indefinite Integral Incorporated 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT.md: -------------------------------------------------------------------------------- 1 | Copyright 2020 Indefinite Integral Incorporated 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## bitcoin-spv 2 | 3 | `bitcoin-spv` is a low-level toolkit for working with Bitcoin from other 4 | blockchains. It supplies a set of pure functions that can be used to validate 5 | almost all Bitcoin transactions and headers, as well as higher-level 6 | functions that can evaluate header chains and transaction inclusion proofs. 7 | 8 | It also supplies a standardized JSON format for proofs. Currently off-chain 9 | proof (de)serialization is supported in Golang, Python, and JS. 10 | 11 | ## Supported by 12 | 13 | ![Binance X Fellowship, Interchain Foundation, Summa, Cross Chain Group](./logo-group.jpg) 14 | 15 | - [Binance X Fellowship](https://binancex.dev/fellowship.html) 16 | - [Interchain Foundation](https://interchain.io/) 17 | - [Nervos Foundation](https://www.nervos.org/) 18 | - [Summa](https://summa.one) 19 | - [Cross Chain Group](https://crosschain.group/) 20 | ---------- 21 | 22 | ### What smart contract chains are supported? 23 | 24 | We have well-tested implementations in Solidty, ES6+ (JS), and golang. 25 | These support any EVM-based chain (Ethereum, Celo, and others), as well as 26 | projects based on [Lotion](https://github.com/nomic-io/lotion) and the 27 | [Cosmos SDK](https://github.com/cosmos/cosmos-sdk/). Our ES6+ work will also 28 | work on [Agoric](https://agoric.com/)'s SES-based smart contract system at 29 | launch. 30 | 31 | ### Quickstart guide: 32 | 33 | There really isn't one. Using these tools requires in-depth knowledge of the 34 | Bitcoin transaction format. The implementations include setup and development 35 | instructions. If you have a project in mind, feel free to reach out and ask 36 | questions. 37 | 38 | ### IMPORTANT WARNING 39 | 40 | It is extremely easy to write insecure code using these libraries. We do not 41 | recommend a specific security model. Any SPV verification involves complex 42 | security assumptions. Please seek external review for your design before 43 | building with these libraries. 44 | 45 | ### A note about versioning 46 | 47 | Implementations are versioned separately. I.e. there is no consistent feature 48 | set for a given version number. Wherever possible we use SemVer. Because go's 49 | versioning system is ridiculous, all releases are minor bumps, even when they 50 | should be major bumps. 51 | 52 | This may change in future releases. 53 | 54 | At time of writing the following versions are roughly equivalent: 55 | - Go v1.4.0 56 | - JS v4.0.0 57 | - rust v3.0.0 58 | - py v3.0.0 59 | 60 | Versions older than these have incompatible JSON Proof and Header formats. 61 | 62 | 63 | ### Bitcoin Endianness Gotchas 64 | Block explorers tend to show txids and merkle roots in big-endian (BE) format. 65 | Most human-facing apps do this as well. However, due to Satoshi's inscrutable 66 | wisdom, almost all in-protocol data structures use little-endian (LE) byte 67 | order. 68 | 69 | When pulling txids and merkle nodes, make sure the endianness is correct 70 | 71 | 1. They should be in LE for the proof construction 72 | 1. They need to be in LE for hashing 73 | 1. They are in LE in the merkle tree 74 | -------------------------------------------------------------------------------- /bin/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es6: true, 6 | }, 7 | extends: [ 8 | 'airbnb-base', 9 | ], 10 | globals: { 11 | Atomics: 'readonly', 12 | SharedArrayBuffer: 'readonly', 13 | }, 14 | parserOptions: { 15 | ecmaVersion: 2018, 16 | }, 17 | rules: { 18 | "comma-dangle": 'off', 19 | "no-bitwise": 'off' 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /bin/README.md: -------------------------------------------------------------------------------- 1 | # Bitcoin Stateless SPV Proof CLI 2 | 3 | Create Bitcoin Stateless SPV Proofs using this 4 | command line tool. 5 | 6 | ## Usage 7 | 8 | Must sync a `bcoin` full node. All proofs are 9 | created using `bcoin` as a backend. 10 | See https://github.com/bcoin-org/bcoin for configuration 11 | information. Be sure to build `bitcoin-spv/js` before 12 | trying to use this CLI tool. 13 | 14 | ```bash 15 | $ ./cli --help 16 | Bitcoin SPV Proof Builder 17 | Version: 0.0.1 Author: Mark Tyneway 18 | Commands: 19 | $ proof [txid]: Get SPV Proof 20 | $ headers [count]: Create Header By Count 21 | $ info: Get Node Info 22 | Flags: 23 | --network/-n {main|testnet|regtest} 24 | --url/u 25 | --api-key/-k 26 | --ssl/-s {true|false} 27 | --http-host/-h 28 | --http-port/-p 29 | --height/-e 30 | ``` 31 | 32 | Note that any flags can be set as environment variables. 33 | Use the prefix `BITCOIN_VERIFIER_SES_` with a flag name 34 | to set it. For example, to set the api key, use the 35 | environment variable `BITCOIN_VERIFIER_SES_API_KEY`. 36 | 37 | ### Proof 38 | 39 | Use the `proof` command to create a stateless SPV Proof. 40 | The argument is the txid. 41 | 42 | ```bash 43 | $ ./bin/cli proof fff0d18db9f52e6bff445c26cb8bb9658882c8045997a74be26003225713e762 44 | 45 | { 46 | "version": "01000000", 47 | "vin": "0168fe12339c9eb3ceaf21899b775a53dc10901f88f57ba7b360bbd285fe1b8731000000006a47304402206f33a57c88e473a26eca617c21aa9545101b18df83b127bc485bf0fea1a831b702204d56bc0e39fa14ff254d0677f46c3f7f26f6dea8e6a9c5ba90aace3c8476f1d401210396dd84815a4f121bf29b882c283ec1cd8b5bfa92773008a79cb44d981821a399ffffffff", 48 | "vout": "0230182b00000000001976a91452ada19e1305964e80a1a1cbbafea97f6b632fae88aca7ec3703000000001976a914f9da3787e63ad9261517a6d4d9dabd2cf40fb80988ac", 49 | "locktime": "00000000", 50 | "tx_id": "fff0d18db9f52e6bff445c26cb8bb9658882c8045997a74be26003225713e762", 51 | "tx_id_le": "62e71357220360e24ba7975904c8828865b98bcb265c44ff6b2ef5b98dd1f0ff", 52 | "index": 6, 53 | "confirming_header": { 54 | "raw": "00e0002074859a363c5a885b262722d5c5c5cd912e2cd1a53d0c0c000000000000000000a0c7ea544dcc99af8af5415d4293856da7c07f9f1a3b145d2498b39bf5fa5e36fbd1d45dd1201617d64b8b4c", 55 | "hash": "00000000000000000003e7124509796d4f15ef47fedc71c79cea60d1ff503410", 56 | "hash_le": "103450ffd160ea9cc771dcfe47ef154f6d79094512e703000000000000000000", 57 | "height": 604596, 58 | "prevhash": "0000000000000000000c0c3da5d12c2e91cdc5c5d52227265b885a3c369a8574", 59 | "prevhash_le": "74859a363c5a885b262722d5c5c5cd912e2cd1a53d0c0c000000000000000000", 60 | "merkle_root": "365efaf59bb398245d143b1a9f7fc0a76d8593425d41f58aaf99cc4d54eac7a0", 61 | "merkle_root_le": "a0c7ea544dcc99af8af5415d4293856da7c07f9f1a3b145d2498b39bf5fa5e36" 62 | }, 63 | "intermediate_nodes": "c7ca43c16c6a587c3554d45a1c0d56e801ef2dc929fae3cc95bb604b3f566de1f6e9750121695b84989ba819f6cea180315f8a3ae71d644f1bb90379577dbc6d7167faee2d76d172fcec41469346d3d731a901fa86e71528a8569a414ef42ed5585623d28015eb91eb1dda03615196918e49e5f1ff0ef229748389698c91c8a5c61d721352a53cea50a25cbff4002b96d0cd93f0fe48d4e7c8f27a9bd4c54ade1c12d25788981d287caac5ee10094957a15d5cc4f65885ff97e292f0512942eba083dcbcab960639f672f7d10e745da5e7b271abca32a9e2060ea8ddbf545db2b89a2ecad745853bab40a383f612ae5ab94815f55b9dd26aa199d39fb7cde85326bce3f94cdad64b6470397f2aa5ff57126a2f343c5d133510f96bda999ee69d99ced303a1076d415b9c404c8fb73a36ae142b359e7106ab73da4862064f472e1459b5d923cb387b83559c100195428443bb8211ab2b57a6dd28c8fd5a44e1a1" 64 | } 65 | ``` 66 | 67 | 68 | ### Headers 69 | 70 | User the `headers` command to create a chain of headers. 71 | The `--height` flag is used here to specify the starting 72 | height. 73 | 74 | ```bash 75 | $ ./bin/cli headers 2 --height 10 76 | { 77 | "headers": "010000000508085c47cc849eb80ea905cc7800a3be674ffc57263cf210c59d8d00000000112ba175a1e04b14ba9e7ea5f76ab640affeef5ec98173ac9799a852fa39add320cd6649ffff001d1e2de565" 78 | } 79 | ``` 80 | -------------------------------------------------------------------------------- /bin/cli: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable no-console */ 3 | /** 4 | * 5 | * @file Part of the [bitcoin-spv]{@link https://github.com/summa-tx/bitcoin-spv} project 6 | * 7 | * @title cli 8 | * @summary bitcoin spv proofs on the cli. Uses BcoinClient. 9 | * @author Mark Tyneway 10 | * @copyright (c) [Summa]{@link https://summa.one}. 2019 11 | * @module bin 12 | * 13 | */ 14 | 15 | 16 | const { BcoinClient } = require('@summa-tx/bitcoin-spv-js-clients'); 17 | const Config = require('./vendor/bcfg'); 18 | 19 | const pkg = { 20 | author: 'Mark Tyneway ', 21 | version: '0.0.1' 22 | }; 23 | 24 | // bcoin HTTP server ports 25 | // by network. 26 | const ports = { 27 | main: 8332, 28 | testnet: 18332, 29 | regtest: 48332, 30 | simnet: 18556 31 | }; 32 | 33 | /** 34 | * Fetch proofs to create an SPVProof object 35 | * or a chain of headers. 36 | */ 37 | 38 | class CLI { 39 | constructor() { 40 | this.config = new Config('bitcoin-spv-cli', { 41 | alias: { 42 | t: 'txid', 43 | n: 'network', 44 | e: 'height', 45 | u: 'url', 46 | k: 'apikey', 47 | s: 'ssl', 48 | h: 'httphost', 49 | p: 'httpport', 50 | x: 'encoding' 51 | } 52 | }); 53 | 54 | this.config.load({ 55 | argv: true, 56 | env: true 57 | }); 58 | 59 | if (this.config.has('help')) { 60 | this.log(this.help()); 61 | process.exit(0); 62 | } 63 | 64 | this.argv = this.config.argv; 65 | this.network = this.config.str('network', 'main'); 66 | 67 | this.client = new BcoinClient({ 68 | url: this.config.str('url'), 69 | apiKey: this.config.str('api-key'), 70 | ssl: this.config.bool('ssl'), 71 | host: this.config.str('http-host'), 72 | port: this.config.uint('http-port') 73 | || ports[this.network] 74 | || ports.main 75 | }); 76 | } 77 | 78 | async open() { 79 | this.cmd = this.argv.shift(); 80 | switch (this.cmd) { 81 | case 'proof': 82 | await this.getProof(); 83 | break; 84 | case 'headers': 85 | await this.getHeaders(); 86 | break; 87 | case 'info': 88 | await this.getInfo(); 89 | break; 90 | default: 91 | this.log(this.help(true)); 92 | } 93 | } 94 | 95 | async destroy() { 96 | if (this.client && this.client.opened) await this.client.close(); 97 | } 98 | 99 | log(json) { 100 | if (typeof json === 'string') return console.log.apply(console, arguments); 101 | return console.log(JSON.stringify(json, null, 2)); 102 | } 103 | 104 | async getInfo() { 105 | const info = await this.client.getInfo(); 106 | this.log(info); 107 | } 108 | 109 | async getProof() { 110 | const txid = this.config.str(0); 111 | 112 | if (!txid) throw new Error('Must pass txid'); 113 | 114 | const proof = await this.client.getProof(txid, 'hex'); 115 | 116 | this.log(proof); 117 | } 118 | 119 | async getHeaders() { 120 | const enc = this.config.str('encoding', 'btcspv'); 121 | let height = this.config.uint('height'); 122 | const count = this.config.uint(0, 0); 123 | 124 | if (!height) { 125 | const info = await this.client.getInfo(); 126 | if (!info) throw new Error('Must pass --height'); 127 | 128 | height = parseInt(info.chain.height, 10); 129 | } 130 | 131 | const headers = await this.client.getHeaderChainByCount(height, count, enc); 132 | 133 | this.log(headers); 134 | } 135 | 136 | help(err) { 137 | let str = ''; 138 | if (err) str += `Unrecognized command: ${this.cmd}\n`; 139 | 140 | return `${str 141 | }Bitcoin SPV Proof Builder\n` 142 | + `Version: ${pkg.version} Author: ${pkg.author}\n` 143 | + 'Commands:\n' 144 | + ' $ proof [txid]: Get SPV Proof\n' 145 | + ' $ headers [count]: Create Header By Count\n' 146 | + ' $ info: Get Node Info\n' 147 | + 'Flags:\n' 148 | + ' --network/-n {main|testnet|regtest}\n' 149 | + ' --url/u \n' 150 | + ' --api-key/-k \n' 151 | + ' --ssl/-s {true|false}\n' 152 | + ' --http-host/-h \n' 153 | + ' --http-port/-p ' 154 | + ' --height/-e \n'; 155 | } 156 | } 157 | 158 | (async () => { 159 | const cli = new CLI(); 160 | await cli.open(); 161 | await cli.destroy(); 162 | })().catch((error) => { 163 | console.log(error); 164 | process.exit(1); 165 | }); 166 | -------------------------------------------------------------------------------- /bin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitcoin-spv-cli", 3 | "version": "0.1.0", 4 | "description": "Create Bitcoin Stateless SPV Proofs using this command line tool.", 5 | "main": "cli", 6 | "engines": { 7 | "node": ">10.0.0" 8 | }, 9 | "engineStrict": true, 10 | "scripts": { 11 | "lint": "eslint ./cli", 12 | "lint:fix": "eslint --fix ./cli", 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "keywords": [ 16 | "bitcoin", 17 | "verification", 18 | "cryptocurrency", 19 | "utilities", 20 | "cli" 21 | ], 22 | "author": "Summa", 23 | "contributors": [ 24 | "Mark Tyneway " 25 | ], 26 | "license": "(MIT OR Apache-2.0)", 27 | "dependencies": { 28 | "@summa-tx/bitcoin-spv-js-clients": "file:../js/clients" 29 | }, 30 | "devDependencies": { 31 | "eslint": "^6.8.0", 32 | "eslint-config-airbnb-base": "^14.1.0", 33 | "eslint-plugin-import": "^2.20.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /c/.gitignore: -------------------------------------------------------------------------------- 1 | *.gcda 2 | *.gcno 3 | *.gcov 4 | -------------------------------------------------------------------------------- /c/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoin-spv-ckb" 3 | version = "0.1.0" 4 | authors = ["James Prestwich ", "Summa "] 5 | edition = "2018" 6 | license = "MIT OR Apache-2.0" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | 12 | [dev-dependencies] 13 | byteorder = "1.3.1" 14 | ckb-types = { git = "https://github.com/nervosnetwork/ckb.git", rev = "d75e4c5" } 15 | ckb-script = { git = "https://github.com/nervosnetwork/ckb.git", rev = "d75e4c5" } 16 | ckb-crypto = { git = "https://github.com/nervosnetwork/ckb.git", rev = "d75e4c5" } 17 | ckb-dao-utils = { git = "https://github.com/nervosnetwork/ckb.git", rev = "d75e4c5" } 18 | ckb-hash = { git = "https://github.com/nervosnetwork/ckb.git", rev = "d75e4c5" } 19 | ckb-error = { git = "https://github.com/nervosnetwork/ckb.git", rev = "d75e4c5" } 20 | hex = "0.4.0" 21 | lazy_static = "1.3.0" 22 | -------------------------------------------------------------------------------- /c/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-I deps 3 | CFLAGS+=-Wall -Werror 4 | CFLAGS+=-fprofile-arcs -ftest-coverage 5 | LIBS=$(shell pkg-config --cflags --libs check) 6 | 7 | all: clean test sort coverage risc risc-test 8 | 9 | # add new test executable prerequesites here, then execute them 10 | test: build/check_btcspv 11 | @echo "\n\n" 12 | @./build/check_btcspv 13 | @echo "\n\n" 14 | 15 | build/check_btcspv: csrc/btcspv.c csrc/evalspv.c csrc/check_btcspv.c 16 | @echo "Building new test executable $@" 17 | @$(CC) $(CFLAGS) -o $@ $^ $(LIBS) 18 | 19 | sort: 20 | @mv *.gcda csrc/ 21 | @mv *.gcno csrc/ 22 | 23 | coverage: 24 | @gcov csrc/btcspv.c 25 | @gcov csrc/evalspv.c 26 | 27 | risc-test: 28 | @cargo test -- --nocapture 29 | 30 | clean: 31 | @rm -f build/main-risc 32 | @rm -f build/check_btcspv 33 | @rm -f build/btcspv.o 34 | @rm -f build/check_btcspv.o 35 | @rm -f build/check_evalspv 36 | @rm -f build/evalspv.o 37 | @rm -f build/check_evalspv.o 38 | @rm -f csrc/*.gcda 39 | @rm -f csrc/*.gcno 40 | 41 | fmt: 42 | clang-format -i -style=Google $(wildcard csrc/*.h csrc/*.c) 43 | git diff --exit-code $(wildcard csrc/*.h csrc/*.c) 44 | 45 | risc: 46 | @make -f risc.Makefile clean --no-print-directory 47 | @make -f risc.Makefile all-via-docker --no-print-directory 48 | 49 | setup: 50 | @git submodule update --init 51 | @make -f risc.Makefile install-tools 52 | @make -f risc.Makefile build/blockchain.h 53 | 54 | .PHONY: all risc 55 | -------------------------------------------------------------------------------- /c/README.md: -------------------------------------------------------------------------------- 1 | ## bitcoin-spv-c 2 | 3 | `bitcoin-spv` is a low-level toolkit for working with Bitcoin from other 4 | blockchains. It supplies a set of pure functions that can be used to validate 5 | almost all Bitcoin transactions and headers, as well as higher-level 6 | functions that can evaluate header chains and transaction inclusion proofs. 7 | 8 | Check the documentation in `btcspv.h` and `evalspv.h`. :) 9 | 10 | ### Contribution Ideas 11 | 12 | Some things that would be cool: 13 | 1. Fix `swap-demo.c` and use it to make a (testnet) cross-chain swap. 14 | 1. Write a function (in JS or Rust) that translates JSON proofs 15 | to the format used in `swap-demo.c`. 16 | 1. Extend the `cli` in `../bin` to make proofs formatted for `swap-demo.c`. 17 | 1. Make a Nervos Type Script that [relays](https://github.com/summa-tx/relays) 18 | Bitcoin headers. 19 | 20 | ### Important Notes 21 | 22 | `btcspv` is a low-level toolkit. It usually **does NOT check bounds**, and 23 | **MAY read past the end of a view** if the input is bad. In order to prevent 24 | this, ALWAYS verify the input using tools in `evalspv` BEFORE passing it to the 25 | functions in `btcspv`. 26 | 27 | **It is extremely easy to write insecure code using these libraries.** 28 | We do not recommend a specific security model. Any SPV verification involves 29 | complex security assumptions. Please seek external review for your design 30 | before building with these libraries. 31 | 32 | ### Notes on project structure: 33 | 34 | `csrc` contains the C source code. `deps` contains vendored dependencies. `src` 35 | contains Rust tests that build a Nervos transaction and a sample lockscript. 36 | The sample script can be found in `csrc/main.c`. 37 | 38 | Unit tests are in `check_btcspv.c` in the `csrc` directory. To add tests, 39 | implement a new test function, then add it to the running suite using 40 | `tcase_add_test`. The tests pull vectors from `../testVectors.json`, and new 41 | vectors will automatically be run if properly formatted. 42 | 43 | We implement a simple byte view type, consisting of a pointer (`loc`) and a 44 | length (`len`). This allows access to memory without having to copy it. 45 | Functions MUST NOT modify the contents of the view, the compiler should enforce 46 | this for `const_view_t`. 47 | 48 | For functions that should return a bytearray that is not a view into one of the 49 | arguments, the first function argument should be a pointer to an allocated 50 | array into which to write the result. If the output is variable-length, the 51 | function MUST return the number of bytes written. See the 4 hash function 52 | implementations for an example. 53 | 54 | 55 | #### Why two Makefiles? 56 | 57 | It's easier for me to compartmentalize the build process this way. We want the 58 | standard x86 build for running `check` and then the risc process to build the 59 | Nervos-targeted ELF. Currently the risc Makefile builds the molecule files and 60 | the sample script executable. 61 | 62 | 63 | ## Setup 64 | 65 | Dependencies: `docker`, `libcheck`, Rust 66 | 67 | * [Installing Check](https://libcheck.github.io/check/web/install.html) 68 | * [Installing Rust Toolchains via Rustup](https://rustup.rs/) 69 | * [Installing Docker](https://docs.docker.com/install/) 70 | 71 | ``` 72 | (DEB) $ sudo apt-get install build-essential 73 | $ docker pull nervos/ckb-riscv-gnu-toolchain:gnu-bionic-20191012 74 | 75 | (OSX) $ brew install check 76 | (DEB) $ sudo apt install check 77 | 78 | $ make setup 79 | ``` 80 | 81 | ### Build and run tests 82 | 83 | ``` 84 | $ make 85 | ``` 86 | 87 | This will print the coverage report and generate the gcov files. Coverage 88 | details can be viewed via `$ cat btcspv.c.gcov` and `$ cat evalspv.c.gcov`. 89 | Running `make` also builds the RISC-V exectuable, and runs the Rust tests. 90 | -------------------------------------------------------------------------------- /c/build/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summa-tx/bitcoin-spv/fb2a61e7a941d421ae833789d97ed10d2ad79cfe/c/build/.gitkeep -------------------------------------------------------------------------------- /c/csrc/evalspv.c: -------------------------------------------------------------------------------- 1 | #include "evalspv.h" 2 | 3 | const uint64_t BTCSPV_ERR_BAD_LENGTH = 0xffffffffffffffff; 4 | const uint64_t BTCSPV_ERR_INVALID_CHAIN = 0xfffffffffffffffe; 5 | const uint64_t BTCSPV_ERR_LOW_WORK = 0xfffffffffffffffd; 6 | 7 | bool evalspv_prove(const uint256 txid, const uint256 root, 8 | const_merkle_array_t *intermediate_nodes, uint32_t index) { 9 | const uint32_t nodes_len = intermediate_nodes->len; 10 | if (UINT256_EQ(txid, root) && index == 0 && nodes_len == 0) { 11 | return true; 12 | } 13 | 14 | uint8_t *proof = malloc(sizeof(uint8_t) * 64 + nodes_len); 15 | memcpy(proof, txid, 32); 16 | memcpy(proof + 32, intermediate_nodes->loc, nodes_len); 17 | memcpy(proof + 32 + nodes_len, root, 32); 18 | 19 | const_view_t proof_view = {proof, 64 + nodes_len}; 20 | 21 | bool result = btcspv_verify_hash256_merkle(&proof_view, index); 22 | 23 | free(proof); 24 | 25 | return result; 26 | } 27 | 28 | void evalspv_calculate_txid(uint256 txid, const_view_t *version, 29 | const_vin_t *vin, const_vout_t *vout, 30 | const_view_t *locktime) { 31 | uint32_t tx_size = (4 + vin->len + vout->len + 4); 32 | 33 | uint8_t *tx = malloc(sizeof(uint8_t) * tx_size); 34 | memcpy(tx, version->loc, 4); 35 | memcpy(tx + 4, vin->loc, vin->len); 36 | memcpy(tx + 4 + vin->len, vout->loc, vout->len); 37 | memcpy(tx + tx_size - 4, locktime->loc, 4); 38 | 39 | const_view_t tx_view = {tx, tx_size}; 40 | 41 | btcspv_hash256(txid, &tx_view); 42 | 43 | free(tx); 44 | } 45 | 46 | // digest is LE, target is BE. this seems weird :/ 47 | bool evalspv_validate_header_work(const uint256 digest, const uint256 target) { 48 | uint256 zero = {0}; 49 | if (UINT256_EQ(digest, zero)) { 50 | return false; 51 | } 52 | 53 | uint256 digest_be = {0}; 54 | btcspv_buf_rev(digest_be, digest, 32); 55 | return (UINT256_LT(digest_be, target)); 56 | } 57 | 58 | bool evalspv_validate_header_prev_hash(const_header_t *header, 59 | const uint256 prev_hash) { 60 | const_view_t actual = btcspv_extract_prev_block_hash_le(header); 61 | return btcspv_view_eq_buf(&actual, prev_hash, 32); 62 | } 63 | 64 | uint64_t evalspv_validate_header_chain(const_header_array_t *headers) { 65 | if (headers->len % 80 != 0) { 66 | return BTCSPV_ERR_BAD_LENGTH; 67 | } 68 | uint32_t offset; 69 | uint256 digest = {0}; 70 | uint64_t accumulated_work = 0x00; 71 | uint32_t num_headers = headers->len / 80; 72 | for (int i = 0; i < num_headers; i++) { 73 | offset = i * 80; 74 | const_header_t header = {headers->loc + offset, 80}; 75 | 76 | // skip on first header 77 | if (i != 0 && !evalspv_validate_header_prev_hash(&header, digest)) { 78 | return BTCSPV_ERR_INVALID_CHAIN; 79 | } 80 | 81 | uint256 target = {0}; 82 | btcspv_extract_target(target, &header); 83 | uint64_t header_work = btcspv_calculate_difficulty(target); 84 | 85 | const_view_t preimage = {header.loc, header.len}; 86 | btcspv_hash256(digest, &preimage); 87 | if (header_work == 0 || !(evalspv_validate_header_work(digest, target))) { 88 | return BTCSPV_ERR_LOW_WORK; 89 | } 90 | accumulated_work += header_work; 91 | } 92 | return accumulated_work; 93 | } 94 | -------------------------------------------------------------------------------- /c/csrc/evalspv.h: -------------------------------------------------------------------------------- 1 | #ifndef SUMMA_CKB_EVALSPV_H_ 2 | #define SUMMA_CKB_EVALSPV_H_ 3 | 4 | #include "stdbool.h" 5 | #include "stdint.h" 6 | #include "stdlib.h" 7 | #include "string.h" 8 | 9 | #include "btcspv.h" 10 | 11 | const uint64_t BTCSPV_ERR_BAD_LENGTH; 12 | const uint64_t BTCSPV_ERR_INVALID_CHAIN; 13 | const uint64_t BTCSPV_ERR_LOW_WORK; 14 | 15 | /// @brief Evaluates a Bitcoin merkle inclusion proof. 16 | /// 17 | /// @warning The index may be malleated. It is NOT necessarily the index of the 18 | /// tx in the block's transaction vector. 19 | /// 20 | /// @param txid The txid (LE) 21 | /// @param merkle_root The merkle root (as in the block header) 22 | /// @param intermediate_nodes The proof's intermediate nodes (digests between 23 | /// leaf and root) 24 | /// @param index The leaf's index in the tree (0-indexed) 25 | /// 26 | /// @return true if a valid proof, otherwise false. 27 | bool evalspv_prove(const uint256 txid, const uint256 root, 28 | const_merkle_array_t *intermediate_nodes, uint32_t index); 29 | 30 | /// @brief Hashes transaction to get txid 31 | /// @note Supports Legacy and Witness 32 | /// @param _version 4-bytes version 33 | /// @param _vin Raw bytes length-prefixed input vector 34 | /// @param _vout Raw bytes length-prefixed output vector 35 | /// @param _locktime 4-byte tx locktime 36 | /// @warning overwrites `txid` with the transaction ID 37 | /// @warning caller must ensure `txid` is allocated and can hold 32 bytes 38 | void evalspv_calculate_txid(uint256 txid, const_view_t *version, 39 | const_vin_t *vin, const_vout_t *vout, 40 | const_view_t *locktime); 41 | 42 | /// @brief Checks validity of header work 43 | /// @param _digest Header digest 44 | /// @param _target The target threshold 45 | /// @return true if header work is valid, false otherwise 46 | bool evalspv_validate_header_work(const uint256 header_digest, 47 | const uint256 target); 48 | 49 | /// @brief Checks validity of header chain 50 | /// @note Compares current header prevHash to previous 51 | /// header's digest 52 | /// @param _header The raw bytes header 53 | /// @param _prevHeaderDigest The previous header's digest 54 | /// @return true if the connection is valid, false otherwise 55 | bool evalspv_validate_header_prev_hash(const_header_t *header, 56 | const uint256 prev_hash); 57 | 58 | /// @brief Checks validity of header chain 59 | /// @note Checks work of each header, connection, 60 | /// @param _headers Raw byte array of header chain 61 | /// @return The total accumulated difficulty of the header chain, 62 | /// or an error code 63 | /// @warning Caller must check response to ensure it is not an error 64 | /// code 65 | uint64_t evalspv_validate_header_chain(const_header_array_t *headers); 66 | 67 | #endif /* SUMMA_CKB_EVALSPV_H_ */ 68 | -------------------------------------------------------------------------------- /c/csrc/test_utils.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "jsmn.h" 3 | 4 | #define TEST_LOOP_START(vec_key) \ 5 | size_t vec_pos = test_vec_pos_by_key(vec_key); \ 6 | size_t cases = test_vec_tokens[vec_pos].size; \ 7 | size_t case_pos = vec_pos + 1; \ 8 | for (int _test_counter = 0; _test_counter < cases; _test_counter++) { \ 9 | jsmntok_t *input_tok __attribute__((unused)) = input_val(case_pos); \ 10 | jsmntok_t *output_tok __attribute__((unused)) = output_val(case_pos); 11 | 12 | #define TEST_LOOP_END \ 13 | case_pos = after(case_pos); \ 14 | } 15 | 16 | // Globals for test vectors 17 | char *test_vec_js = NULL; 18 | jsmntok_t *test_vec_tokens = NULL; 19 | 20 | void print_as_hex(const uint8_t *buf, uint32_t len) { 21 | for (int i = 0; i < len; i++) { 22 | printf("%02x", buf[i]); 23 | } 24 | printf("\n"); 25 | } 26 | 27 | // read in the test vectors to the 28 | void read_test_vectors() { 29 | FILE *fp = fopen("../testVectors.json", "r"); 30 | if (fp != NULL) { 31 | /* Go to the end of the file. */ 32 | if (fseek(fp, 0L, SEEK_END) == 0) { 33 | /* Get the size of the file. */ 34 | long bufsize = ftell(fp); 35 | if (bufsize == -1) { /* Error */ 36 | } 37 | 38 | /* Allocate our buffer to that size. */ 39 | test_vec_js = malloc(sizeof(char) * (bufsize + 1)); 40 | 41 | /* Go back to the start of the file. */ 42 | if (fseek(fp, 0L, SEEK_SET) != 0) { /* Error */ 43 | } 44 | 45 | /* Read the entire file into memory. */ 46 | size_t newLen = fread(test_vec_js, sizeof(char), bufsize, fp); 47 | if (ferror(fp) != 0) { 48 | fputs("Error reading file", stderr); 49 | } else { 50 | test_vec_js[newLen++] = '\0'; /* Just to be safe. */ 51 | } 52 | } 53 | fclose(fp); 54 | } 55 | } 56 | 57 | // parse tokens from the buffer 58 | void parse_test_vectors() { 59 | jsmn_parser parser; 60 | jsmn_init(&parser); 61 | 62 | unsigned int n = 256; 63 | jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * n); 64 | 65 | size_t buf_size = strlen(test_vec_js); 66 | 67 | // allocate more memory until we can fit all tokens 68 | int ret = jsmn_parse(&parser, test_vec_js, buf_size, tokens, n); 69 | while (ret == JSMN_ERROR_NOMEM) { 70 | n = n * 2 + 1; 71 | tokens = realloc(tokens, sizeof(jsmntok_t) * n); 72 | ret = jsmn_parse(&parser, test_vec_js, buf_size, tokens, n); 73 | } 74 | 75 | test_vec_tokens = tokens; 76 | } 77 | 78 | // compare token to a string 79 | bool token_streq(size_t pos, char *s) { 80 | jsmntok_t *t = &test_vec_tokens[pos]; 81 | return (strncmp(test_vec_js + t->start, s, t->end - t->start) == 0 && 82 | strlen(s) == (size_t)(t->end - t->start)); 83 | } 84 | 85 | char *token_as_string(jsmntok_t *tok) { 86 | test_vec_js[tok->end] = '\0'; 87 | return test_vec_js + tok->start; 88 | } 89 | 90 | // return a null-terminated string representing the token 91 | // THIS MODIFIES THE BUFFER, BE CAREFUL 92 | char *pos_as_string(size_t pos) { 93 | jsmntok_t *tok = &test_vec_tokens[pos]; 94 | return token_as_string(tok); 95 | } 96 | 97 | // Get the token that is logically _after_ another token. 98 | // For a key, this will be its value. for a value, this will be 99 | // the next key 100 | // For a list element, this will be the next list element 101 | size_t after(size_t pos) { 102 | jsmntok_t *obj = &test_vec_tokens[pos]; 103 | size_t next = pos + 1; 104 | 105 | for (;; next++) { 106 | jsmntok_t *tok = &test_vec_tokens[next]; 107 | if (tok->start > obj->end) { 108 | return next; 109 | } 110 | if (tok->type == JSMN_UNDEFINED) { 111 | return 0; 112 | } 113 | } 114 | } 115 | 116 | // find the position of a key within an object 117 | // pass the object's start position 118 | size_t pos_by_key(size_t pos, char *key) { 119 | jsmntok_t *obj = &test_vec_tokens[pos]; 120 | size_t key_loc = pos + 1; 121 | 122 | if (obj->type != JSMN_OBJECT) { 123 | return 0; 124 | } 125 | 126 | for (int i = 0; i < obj->size; i++) { 127 | if (token_streq(key_loc, key) == true) { 128 | return key_loc; 129 | } 130 | key_loc = after(key_loc + 1); 131 | } 132 | return 0; 133 | } 134 | 135 | size_t val_pos_by_key(size_t pos, char *key) { 136 | return 1 + pos_by_key(pos, key); 137 | } 138 | 139 | size_t test_vec_pos_by_key(char *key) { return val_pos_by_key(0, key); } 140 | 141 | // Pass in the case position 142 | jsmntok_t *input_val(size_t pos) { 143 | size_t val_pos = val_pos_by_key(pos, "input"); 144 | return &test_vec_tokens[val_pos]; 145 | } 146 | 147 | // pass in the case position 148 | jsmntok_t *output_val(size_t pos) { 149 | size_t val_pos = val_pos_by_key(pos, "output"); 150 | return &test_vec_tokens[val_pos]; 151 | } 152 | 153 | long token_as_long(jsmntok_t *tok) { 154 | char *tok_str = token_as_string(tok); 155 | return strtol(tok_str, NULL, 0); 156 | } 157 | 158 | long pos_as_long(size_t pos) { return token_as_long(&test_vec_tokens[pos]); } 159 | 160 | bool token_as_bool(jsmntok_t *tok) { 161 | char *tok_str = token_as_string(tok); 162 | return tok_str[0] == 't'; 163 | } 164 | 165 | bool pos_as_bool(size_t pos) { return token_as_bool(&test_vec_tokens[pos]); } 166 | 167 | // Overwrites `res`, returns number of bytes 168 | // CALLER MUST FREE 169 | uint32_t token_as_hex_buf(uint8_t **res, jsmntok_t *tok) { 170 | uint32_t buf_size = (tok->end - (tok->start + 2)) / 2; 171 | uint32_t pos = tok->start + 2; 172 | // overwrite the pointer pointed to by buf 173 | uint8_t *buf = malloc(sizeof(uint8_t) * buf_size); 174 | 175 | for (size_t count = 0; count < buf_size; count++) { 176 | sscanf(test_vec_js + pos + count * 2, "%2hhx", &buf[count]); 177 | } 178 | 179 | *res = buf; 180 | return buf_size; 181 | } 182 | 183 | // Overwrites `res`, returns number of bytes 184 | // CALLER MUST FREE 185 | uint32_t pos_as_hex_buf(uint8_t **res, size_t pos) { 186 | return token_as_hex_buf(res, &test_vec_tokens[pos]); 187 | } 188 | -------------------------------------------------------------------------------- /c/deps/molecule/VERSION: -------------------------------------------------------------------------------- 1 | 0.4.1 2 | -------------------------------------------------------------------------------- /c/risc.Makefile: -------------------------------------------------------------------------------- 1 | TARGET := riscv64-unknown-linux-gnu 2 | CC := $(TARGET)-gcc 3 | LD := $(TARGET)-gcc 4 | OBJCOPY := $(TARGET)-objcopy 5 | CFLAGS := -fPIC -O3 -nostdinc -nostdlib -nostartfiles -fvisibility=hidden 6 | CFLAGS += -I deps -I deps/ckb-c-stdlib -I deps/ckb-c-stdlib/libc -I deps/molecule -I build 7 | CFLAGS += -Wall -Werror -Wno-nonnull -Wno-nonnull-compare -Wno-unused-function -g -DTARGETING_NERVOS_CKB 8 | MOLC := moleculec 9 | MOLC_VERSION := 0.4.1 10 | PROTOCOL_HEADER := build/blockchain.h 11 | PROTOCOL_SCHEMA := build/blockchain.mol 12 | PROTOCOL_VERSION := d75e4c56ffa40e17fd2fe477da3f98c5578edcd1 13 | PROTOCOL_URL := https://raw.githubusercontent.com/nervosnetwork/ckb/${PROTOCOL_VERSION}/util/types/schemas/blockchain.mol 14 | 15 | # docker pull nervos/ckb-riscv-gnu-toolchain:gnu-bionic-20191012 16 | BUILDER_DOCKER := nervos/ckb-riscv-gnu-toolchain@sha256:aae8a3f79705f67d505d1f1d5ddc694a4fd537ed1c7e9622420a470d59ba2ec3 17 | 18 | all: build/swap-demo-risc 19 | 20 | all-via-docker: ${PROTOCOL_HEADER} 21 | @docker run --rm -v `pwd`:/code ${BUILDER_DOCKER} bash -c "cd /code && make -f risc.Makefile" 22 | 23 | build/swap-demo-risc: csrc/swap-demo.c csrc/btcspv.c csrc/evalspv.c 24 | @$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $< 25 | @$(OBJCOPY) --only-keep-debug $@ $@.debug 26 | @$(OBJCOPY) --strip-debug --strip-all $@ 27 | 28 | generate-protocol: check-moleculec-version ${PROTOCOL_HEADER} 29 | 30 | check-moleculec-version: 31 | test "$$(${MOLC} --version | awk '{ print $$2 }' | tr -d ' ')" = ${MOLC_VERSION} 32 | 33 | ${PROTOCOL_HEADER}: ${PROTOCOL_SCHEMA} 34 | ${MOLC} --language c --schema-file $< > $@ 35 | 36 | ${PROTOCOL_SCHEMA}: 37 | mkdir -p build 38 | curl -L -o $@ ${PROTOCOL_URL} 39 | 40 | install-tools: 41 | if [ ! -x "$$(command -v "${MOLC}")" ] \ 42 | || [ "$$(${MOLC} --version | awk '{ print $$2 }' | tr -d ' ')" != "${MOLC_VERSION}" ]; then \ 43 | cargo install --force --version "${MOLC_VERSION}" "${MOLC}"; \ 44 | fi 45 | 46 | clean: 47 | @rm -f build/*-risc.o 48 | @rm -f build/*-risc.o.debug 49 | @rm -f build/*-risc.so 50 | 51 | .PHONY: all all-via-docker dist clean fmt 52 | .PHONY: generate-protocol check-moleculec-version install-tools 53 | -------------------------------------------------------------------------------- /c/src/test_utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2019 "Nervos Core Dev " 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of this software 4 | // and associated documentation files (the "Software"), to deal in the Software without 5 | // restriction, including without limitation the rights to use, copy, modify, merge, publish, 6 | // distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the 7 | // Software is furnished to do so, subject to the following conditions: 8 | // 9 | // The above copyright notice and this permission notice shall be included in all copies or 10 | // substantial portions of the Software. 11 | // 12 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING 13 | // BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 14 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 15 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | 18 | 19 | use std::collections::HashMap; 20 | use ckb_script::DataLoader; 21 | use ckb_types::{ 22 | bytes::Bytes, 23 | core::{ 24 | BlockExt, EpochExt, HeaderView, TransactionView, 25 | cell::{CellMeta, CellMetaBuilder, ResolvedTransaction} 26 | }, 27 | packed::{Byte32, OutPoint, CellOutput} 28 | }; 29 | 30 | 31 | #[derive(Default)] 32 | pub struct DummyDataLoader { 33 | pub cells: HashMap, 34 | pub headers: HashMap, 35 | pub epoches: HashMap, 36 | } 37 | 38 | impl DummyDataLoader { 39 | pub fn new() -> Self { 40 | Self::default() 41 | } 42 | } 43 | 44 | impl DataLoader for DummyDataLoader { 45 | // load Cell Data 46 | fn load_cell_data(&self, cell: &CellMeta) -> Option<(Bytes, Byte32)> { 47 | cell.mem_cell_data.clone().or_else(|| { 48 | self.cells 49 | .get(&cell.out_point) 50 | .map(|(_, data)| (data.clone(), CellOutput::calc_data_hash(&data))) 51 | }) 52 | } 53 | // load BlockExt 54 | fn get_block_ext(&self, _hash: &Byte32) -> Option { 55 | unreachable!() 56 | } 57 | 58 | // load header 59 | fn get_header(&self, block_hash: &Byte32) -> Option { 60 | self.headers.get(block_hash).cloned() 61 | } 62 | 63 | // load EpochExt 64 | fn get_block_epoch(&self, block_hash: &Byte32) -> Option { 65 | self.epoches.get(block_hash).cloned() 66 | } 67 | } 68 | 69 | pub fn build_resolved_tx(data_loader: &DummyDataLoader, tx: &TransactionView) -> ResolvedTransaction { 70 | let resolved_cell_deps = tx 71 | .cell_deps() 72 | .into_iter() 73 | .map(|dep| { 74 | let deps_out_point = dep.clone(); 75 | let (dep_output, dep_data) = 76 | data_loader.cells.get(&deps_out_point.out_point()).unwrap(); 77 | CellMetaBuilder::from_cell_output(dep_output.to_owned(), dep_data.to_owned()) 78 | .out_point(deps_out_point.out_point().clone()) 79 | .build() 80 | }) 81 | .collect(); 82 | 83 | let mut resolved_inputs = Vec::new(); 84 | for i in 0..tx.inputs().len() { 85 | let previous_out_point = tx.inputs().get(i).unwrap().previous_output(); 86 | let (input_output, input_data) = data_loader.cells.get(&previous_out_point).unwrap(); 87 | resolved_inputs.push( 88 | CellMetaBuilder::from_cell_output(input_output.to_owned(), input_data.to_owned()) 89 | .out_point(previous_out_point) 90 | .build(), 91 | ); 92 | } 93 | 94 | ResolvedTransaction { 95 | transaction: tx.clone(), 96 | resolved_cell_deps, 97 | resolved_inputs, 98 | resolved_dep_groups: vec![], 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /golang/.gitignore: -------------------------------------------------------------------------------- 1 | spvcli 2 | -------------------------------------------------------------------------------- /golang/btcspv/test_utils/bitcoin_spv_test_types.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" 6 | ) 7 | 8 | // Hash256Digest 32-byte double-sha2 digest 9 | type Hash256Digest = btcspv.Hash256Digest 10 | 11 | // Hash160Digest is a 20-byte ripemd160+sha2 hash 12 | type Hash160Digest = btcspv.Hash160Digest 13 | 14 | // RawHeader is an 80-byte raw header 15 | type RawHeader = btcspv.RawHeader 16 | 17 | // HexBytes is a type alias to make JSON hex ser/deser easier 18 | type HexBytes = btcspv.HexBytes 19 | 20 | // BitcoinHeader is a parsed Bitcoin header 21 | type BitcoinHeader = btcspv.BitcoinHeader 22 | 23 | // SPVProof is the base struct for an SPV proof 24 | type SPVProof = btcspv.SPVProof 25 | 26 | type ExtractSequenceWitnessTC struct { 27 | Input HexBytes `json:"input"` 28 | Output uint32 `json:"output"` 29 | } 30 | 31 | type ExtractSequenceLEWitnessTC struct { 32 | Input HexBytes `json:"input"` 33 | Output HexBytes `json:"output"` 34 | } 35 | 36 | type ExtractSequenceLegacyTC struct { 37 | Input HexBytes `json:"input"` 38 | Output uint32 `json:"output"` 39 | } 40 | 41 | type ExtractSequenceLegacyError struct { 42 | Input HexBytes `json:"input"` 43 | ErrorMessage string `json:"golangError"` 44 | } 45 | 46 | type ExtractSequenceLELegacyTC struct { 47 | Input HexBytes `json:"input"` 48 | Output HexBytes `json:"output"` 49 | } 50 | 51 | type ExtractSequenceLELegacyError struct { 52 | Input HexBytes `json:"input"` 53 | ErrorMessage string `json:"golangError"` 54 | } 55 | 56 | type Hash160TC struct { 57 | Input HexBytes `json:"input"` 58 | Output Hash160Digest `json:"output"` 59 | } 60 | 61 | type Hash256TC struct { 62 | Input HexBytes `json:"input"` 63 | Output Hash256Digest `json:"output"` 64 | } 65 | 66 | type BytesToBigUintTC struct { 67 | Input HexBytes `json:"input"` 68 | Output uint `json:"output"` 69 | } 70 | 71 | type ExtractOutpointTC struct { 72 | Input HexBytes `json:"input"` 73 | Output HexBytes `json:"output"` 74 | } 75 | 76 | type ExtractHashTC struct { 77 | Input HexBytes `json:"input"` 78 | Output HexBytes `json:"output"` 79 | } 80 | 81 | type ExtractHashError struct { 82 | Input HexBytes `json:"input"` 83 | ErrorMessage string `json:"golangError"` 84 | } 85 | 86 | type ExtractValueTC struct { 87 | Input HexBytes `json:"input"` 88 | Output uint `json:"output"` 89 | } 90 | 91 | type ExtractValueLETC struct { 92 | Input HexBytes `json:"input"` 93 | Output HexBytes `json:"output"` 94 | } 95 | 96 | type ExtractOpReturnDataTC struct { 97 | Input HexBytes `json:"input"` 98 | Output HexBytes `json:"output"` 99 | } 100 | 101 | type ExtractOpReturnDataError struct { 102 | Input HexBytes `json:"input"` 103 | ErrorMessage string `json:"golangError"` 104 | } 105 | 106 | type ExtractInputAtIndexInput struct { 107 | Vin HexBytes `json:"vin"` 108 | Index uint `json:"index"` 109 | } 110 | 111 | type ExtractInputAtIndexTC struct { 112 | Input ExtractInputAtIndexInput `json:"input"` 113 | Output HexBytes `json:"output"` 114 | } 115 | 116 | type ExtractInputAtIndexError struct { 117 | Input ExtractInputAtIndexInput `json:"input"` 118 | ErrorMessage string `json:"golangError"` 119 | } 120 | 121 | type IsLegacyInputTC struct { 122 | Input HexBytes `json:"input"` 123 | Output bool `json:"output"` 124 | } 125 | 126 | type DetermineInputLengthTC struct { 127 | Input HexBytes `json:"input"` 128 | Output uint64 `json:"output"` 129 | } 130 | 131 | type ExtractScriptSigTC struct { 132 | Input HexBytes `json:"input"` 133 | Output HexBytes `json:"output"` 134 | } 135 | 136 | type ExtractScriptSigError struct { 137 | Input HexBytes `json:"input"` 138 | ErrorMessage string `json:"golangError"` 139 | } 140 | 141 | type ExtractScriptSigLenTC struct { 142 | Input HexBytes `json:"input"` 143 | Output []uint64 `json:"output"` 144 | } 145 | 146 | type ValidateVinTC struct { 147 | Input HexBytes `json:"input"` 148 | Output bool `json:"output"` 149 | } 150 | 151 | type ValidateVoutTC struct { 152 | Input HexBytes `json:"input"` 153 | Output bool `json:"output"` 154 | } 155 | 156 | type ExtractInputTxIDLETC struct { 157 | Input HexBytes `json:"input"` 158 | Output Hash256Digest `json:"output"` 159 | } 160 | 161 | /****** NEW *******/ 162 | 163 | type ExtractTxIndexLETC struct { 164 | Input HexBytes `json:"input"` 165 | Output HexBytes `json:"output"` 166 | } 167 | 168 | type ExtractTxIndexTC struct { 169 | Input HexBytes `json:"input"` 170 | Output uint `json:"output"` 171 | } 172 | 173 | type DetermineOutputLengthTC struct { 174 | Input HexBytes `json:"input"` 175 | Output uint64 `json:"output"` 176 | } 177 | 178 | type DetermineOutputLengthError struct { 179 | Input HexBytes `json:"input"` 180 | ErrorMessage string `json:"golangError"` 181 | } 182 | 183 | type ExtractOutputAtIndexInput struct { 184 | Vout HexBytes `json:"vout"` 185 | Index uint `json:"index"` 186 | } 187 | type ExtractOutputAtIndexTC struct { 188 | Input ExtractOutputAtIndexInput `json:"input"` 189 | Output HexBytes `json:"output"` 190 | } 191 | 192 | type ExtractOutputAtIndexError struct { 193 | Input ExtractOutputAtIndexInput `json:"input"` 194 | ErrorMessage string `json:"golangError"` 195 | } 196 | 197 | type ExtractTargetTC struct { 198 | Input RawHeader `json:"input"` 199 | Output HexBytes `json:"output"` 200 | } 201 | 202 | type ExtractTimestampTC struct { 203 | Input RawHeader `json:"input"` 204 | Output uint `json:"output"` 205 | } 206 | 207 | type Hash256MerkleStepTC struct { 208 | Input []HexBytes `json:"input"` 209 | Output Hash256Digest `json:"output"` 210 | } 211 | 212 | type VerifyHash256MerkleInput struct { 213 | Proof HexBytes `json:"proof"` 214 | Index uint `json:"index"` 215 | } 216 | 217 | type VerifyHash256MerkleTC struct { 218 | Input VerifyHash256MerkleInput `json:"input"` 219 | Output bool `json:"output"` 220 | } 221 | 222 | type Retarget struct { 223 | Hash Hash256Digest `json:"hash"` 224 | Version uint `json:"version"` 225 | PrevBlock Hash256Digest `json:"prev_block"` 226 | MerkleRoot Hash256Digest `json:"merkle_root"` 227 | Timestamp uint `json:"timestamp"` 228 | Nbits HexBytes `json:"nbits"` 229 | Nonce HexBytes `json:"nonce"` 230 | Difficulty uint64 `json:"difficulty"` 231 | Hex RawHeader `json:"hex"` 232 | Height uint32 `json:"height"` 233 | } 234 | 235 | type RetargetAlgorithmTC struct { 236 | Input []Retarget `json:"input"` 237 | Output uint64 `json:"output"` 238 | } 239 | 240 | type CalculateDifficultyTC struct { 241 | Input sdk.Uint `json:"input"` 242 | Output sdk.Int `json:"output"` 243 | } 244 | -------------------------------------------------------------------------------- /golang/btcspv/test_utils/utils_test_types.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | type EncodeP2SHTC struct { 4 | Input HexBytes `json:"input"` 5 | Output string `json:"output"` 6 | } 7 | 8 | type EncodeP2PKHTC struct { 9 | Input HexBytes `json:"input"` 10 | Output string `json:"output"` 11 | } 12 | 13 | type EncodeP2WSHTC struct { 14 | Input Hash256Digest `json:"input"` 15 | Output string `json:"output"` 16 | } 17 | 18 | type EncodeP2WPKHTC struct { 19 | Input HexBytes `json:"input"` 20 | Output string `json:"output"` 21 | } 22 | -------------------------------------------------------------------------------- /golang/btcspv/test_utils/validate_spv_test_types.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import sdk "github.com/cosmos/cosmos-sdk/types" 4 | 5 | type ProveInput struct { 6 | TxIdLE Hash256Digest `json:"txIdLE"` 7 | MerkleRootLE Hash256Digest `json:"merkleRootLE"` 8 | Proof HexBytes `json:"proof"` 9 | Index uint `json:"index"` 10 | } 11 | 12 | type ProveTC struct { 13 | Input ProveInput `json:"input"` 14 | Output bool `json:"output"` 15 | } 16 | 17 | type CalculateTxIDTC struct { 18 | Input SPVProof `json:"input"` 19 | Output Hash256Digest `json:"output"` 20 | } 21 | 22 | type ValidateHeaderWorkInput struct { 23 | Digest Hash256Digest `json:"digest"` 24 | Target sdk.Uint `json:"target"` 25 | } 26 | 27 | type ValidateHeaderWorkTC struct { 28 | Input ValidateHeaderWorkInput `json:"input"` 29 | Output bool `json:"output"` 30 | } 31 | 32 | type ValidateHeaderPrevHashInput struct { 33 | Header RawHeader `json:"header"` 34 | PrevHash Hash256Digest `json:"prevHash"` 35 | } 36 | 37 | type ValidateHeaderPrevHashTC struct { 38 | Input ValidateHeaderPrevHashInput `json:"input"` 39 | Output bool `json:"output"` 40 | } 41 | 42 | type ValidateHeaderChainTC struct { 43 | Input HexBytes `json:"input"` 44 | Output uint64 `json:"output"` 45 | } 46 | 47 | type ValidateHeaderChainError struct { 48 | Input HexBytes `json:"input"` 49 | ErrorMessage string `json:"golangError"` 50 | } 51 | -------------------------------------------------------------------------------- /golang/btcspv/types.go: -------------------------------------------------------------------------------- 1 | package btcspv 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | ) 7 | 8 | // Hash160Digest is a 20-byte ripemd160+sha2 hash 9 | type Hash160Digest [20]byte 10 | 11 | // Hash256Digest is a 32-byte double-sha2 hash 12 | type Hash256Digest [32]byte 13 | 14 | // RawHeader is an 80-byte raw header 15 | type RawHeader [80]byte 16 | 17 | // HexBytes is a type alias to make JSON hex ser/deser easier 18 | type HexBytes []byte 19 | 20 | // BitcoinHeader is a parsed Bitcoin header, values are LE 21 | type BitcoinHeader struct { 22 | Raw RawHeader `json:"raw"` 23 | Hash Hash256Digest `json:"hash"` 24 | Height uint32 `json:"height"` 25 | PrevHash Hash256Digest `json:"prevhash"` 26 | MerkleRoot Hash256Digest `json:"merkle_root"` 27 | } 28 | 29 | // SPVProof is the base struct for an SPV proof 30 | type SPVProof struct { 31 | Version HexBytes `json:"version"` 32 | Vin HexBytes `json:"vin"` 33 | Vout HexBytes `json:"vout"` 34 | Locktime HexBytes `json:"locktime"` 35 | TxID Hash256Digest `json:"tx_id"` 36 | Index uint32 `json:"index"` 37 | ConfirmingHeader BitcoinHeader `json:"confirming_header"` 38 | IntermediateNodes HexBytes `json:"intermediate_nodes"` 39 | } 40 | 41 | // NewHash160Digest instantiates a Hash160Digest from a byte slice 42 | func NewHash160Digest(b []byte) (Hash160Digest, error) { 43 | var h Hash160Digest 44 | copied := copy(h[:], b) 45 | if copied != 20 { 46 | return Hash160Digest{}, fmt.Errorf("Expected 20 bytes in a Hash160Digest, got %d", copied) 47 | } 48 | return h, nil 49 | } 50 | 51 | // NewHash256Digest instantiates a Hash256Digest from a byte slice 52 | func NewHash256Digest(b []byte) (Hash256Digest, error) { 53 | var h Hash256Digest 54 | copied := copy(h[:], b) 55 | if copied != 32 { 56 | return Hash256Digest{}, fmt.Errorf("Expected 32 bytes in a Hash256Digest, got %d", copied) 57 | } 58 | return h, nil 59 | } 60 | 61 | // NewRawHeader instantiates a RawHeader from a byte slice 62 | func NewRawHeader(b []byte) (RawHeader, error) { 63 | var h RawHeader 64 | copied := copy(h[:], b) 65 | if copied != 80 { 66 | return RawHeader{}, fmt.Errorf("Expected 80 bytes in a RawHeader got %d", copied) 67 | } 68 | return h, nil 69 | } 70 | 71 | // HeaderFromRaw builds a BitcoinHeader from a raw bytestring and height 72 | func HeaderFromRaw(raw RawHeader, height uint32) BitcoinHeader { 73 | digest := Hash256(raw[:]) 74 | prevhash := ExtractPrevBlockHashLE(raw) 75 | merkleRoot := ExtractMerkleRootLE(raw) 76 | 77 | return BitcoinHeader{ 78 | raw, 79 | digest, 80 | height, 81 | prevhash, 82 | merkleRoot, 83 | } 84 | } 85 | 86 | // HeaderFromHex buidls a BitcoinHeader from a hex string and height 87 | func HeaderFromHex(s string, height uint32) (BitcoinHeader, error) { 88 | var raw RawHeader 89 | 90 | buf, err := hex.DecodeString(Strip0xPrefix(s)) 91 | if err != nil { 92 | return BitcoinHeader{}, err 93 | } 94 | 95 | copied := copy(raw[:], buf) 96 | if copied != 80 { 97 | return BitcoinHeader{}, fmt.Errorf("Expected 80 bytes in a Hash256 digest, got %d", copied) 98 | } 99 | 100 | return HeaderFromRaw(raw, height), nil 101 | } 102 | 103 | // UnmarshalJSON unmarshalls 32 byte digests 104 | func (h *HexBytes) UnmarshalJSON(b []byte) error { 105 | // Have to trim quotation marks off byte array 106 | end := len(b) - 1 107 | buf, err := hex.DecodeString(Strip0xPrefix(string(b[1:end:end]))) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | *h = append(*h, buf...) 113 | return nil 114 | } 115 | 116 | // MarshalJSON marashalls bytestrings as 0x-prepended hex 117 | func (h HexBytes) MarshalJSON() ([]byte, error) { 118 | encoded := "\"0x" + hex.EncodeToString(h[:]) + "\"" 119 | return []byte(encoded), nil 120 | } 121 | 122 | // UnmarshalJSON unmarshalls 32 byte digests 123 | func (h *Hash256Digest) UnmarshalJSON(b []byte) error { 124 | // Have to trim quotation marks off byte array 125 | end := len(b) - 1 126 | buf, err := hex.DecodeString(Strip0xPrefix(string(b[1:end:end]))) 127 | if err != nil { 128 | return err 129 | } 130 | if len(buf) != 32 { 131 | return fmt.Errorf("Expected 32 bytes, got %d bytes", len(buf)) 132 | } 133 | 134 | copy(h[:], buf) 135 | 136 | return nil 137 | } 138 | 139 | // MarshalJSON marashalls 32 byte digests as 0x-prepended hex 140 | func (h Hash256Digest) MarshalJSON() ([]byte, error) { 141 | encoded := "\"0x" + hex.EncodeToString(h[:]) + "\"" 142 | return []byte(encoded), nil 143 | } 144 | 145 | // UnmarshalJSON unmarshalls 32 byte digests 146 | func (h *RawHeader) UnmarshalJSON(b []byte) error { 147 | // Have to trim quotation marks off byte array 148 | end := len(b) - 1 149 | buf, err := hex.DecodeString(Strip0xPrefix(string(b[1:end:end]))) 150 | if err != nil { 151 | return err 152 | } 153 | if len(buf) != 80 { 154 | return fmt.Errorf("Expected 80 bytes, got %d bytes", len(buf)) 155 | } 156 | 157 | copy(h[:], buf) 158 | 159 | return nil 160 | } 161 | 162 | // UnmarshalJSON unmarshalls 32 byte digests 163 | func (h *Hash160Digest) UnmarshalJSON(b []byte) error { 164 | // Have to trim quotation marks off byte array 165 | end := len(b) - 1 166 | buf, err := hex.DecodeString(Strip0xPrefix(string(b[1:end:end]))) 167 | if err != nil { 168 | return err 169 | } 170 | if len(buf) != 20 { 171 | return fmt.Errorf("Expected 20 bytes, got %d bytes", len(buf)) 172 | } 173 | 174 | copy(h[:], buf) 175 | 176 | return nil 177 | } 178 | 179 | // MarshalJSON marashalls 32 byte digests as 0x-prepended hex 180 | func (h Hash160Digest) MarshalJSON() ([]byte, error) { 181 | encoded := "\"0x" + hex.EncodeToString(h[:]) + "\"" 182 | return []byte(encoded), nil 183 | } 184 | 185 | // MarshalJSON marashalls 32 byte digests as 0x-prepended hex 186 | func (h RawHeader) MarshalJSON() ([]byte, error) { 187 | encoded := "\"0x" + hex.EncodeToString(h[:]) + "\"" 188 | return []byte(encoded), nil 189 | } 190 | -------------------------------------------------------------------------------- /golang/btcspv/utils.go: -------------------------------------------------------------------------------- 1 | package btcspv 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "errors" 7 | "fmt" 8 | 9 | "github.com/btcsuite/btcutil/base58" 10 | "github.com/btcsuite/btcutil/bech32" 11 | ) 12 | 13 | // ZeroBytesError is the error returned when attempting to encode 14 | // an empty bytestring 15 | const ZeroBytesError = "Attempting to encode empty bytestring. " + 16 | "Hint: your payload may not be properly initialized" 17 | 18 | // Strip0xPrefix removes the 0x prefix from a hex string 19 | func Strip0xPrefix(s string) string { 20 | if len(s) < 2 { 21 | return s 22 | } 23 | if s[0:2] == "0x" { 24 | return s[2:] 25 | } 26 | return s 27 | } 28 | 29 | // DecodeIfHex decodes a hex string into a byte array 30 | func DecodeIfHex(s string) []byte { 31 | res, err := hex.DecodeString(Strip0xPrefix(s)) 32 | if err != nil { 33 | return []byte(s) 34 | } 35 | return res 36 | } 37 | 38 | // EncodeP2SH turns a scripthash into an address 39 | func EncodeP2SH(sh []byte) (string, error) { 40 | if len(sh) != 20 { 41 | return "", fmt.Errorf("SH must be 20 bytes, got %d bytes", len(sh)) 42 | } 43 | if bytes.Equal(sh, make([]byte, len(sh))) { 44 | return "", errors.New(ZeroBytesError) 45 | } 46 | return base58.CheckEncode(sh, 5), nil 47 | } 48 | 49 | // EncodeP2PKH turns a pubkey hash into an address 50 | func EncodeP2PKH(pkh []byte) (string, error) { 51 | if len(pkh) != 20 { 52 | return "", fmt.Errorf("PKH must be 20 bytes, got %d bytes", len(pkh)) 53 | } 54 | if bytes.Equal(pkh, make([]byte, len(pkh))) { 55 | return "", errors.New(ZeroBytesError) 56 | 57 | } 58 | return base58.CheckEncode(pkh, 0), nil 59 | } 60 | 61 | func encodeSegWit(payload []byte, version int) (string, error) { 62 | if bytes.Equal(payload, make([]byte, len(payload))) { 63 | return "", errors.New(ZeroBytesError) 64 | } 65 | adj, _ := bech32.ConvertBits(payload, 8, 5, true) 66 | combined := []byte{0x00} 67 | combined = append(combined, adj...) 68 | res, _ := bech32.Encode("bc", combined) 69 | return res, nil 70 | } 71 | 72 | // EncodeP2WSH turns a scripthash into an address 73 | func EncodeP2WSH(sh Hash256Digest) (string, error) { 74 | addr, err := encodeSegWit(sh[:], 0) 75 | if err != nil { 76 | return "", err 77 | } 78 | return addr, nil 79 | } 80 | 81 | // EncodeP2WPKH turns a pubkey hash into an address 82 | func EncodeP2WPKH(pkh []byte) (string, error) { 83 | if len(pkh) != 20 { 84 | return "", fmt.Errorf("WPKH must be 20 bytes, got %d bytes", len(pkh)) 85 | } 86 | addr, err := encodeSegWit(pkh, 0) 87 | if err != nil { 88 | return "", err 89 | } 90 | return addr, nil 91 | } 92 | -------------------------------------------------------------------------------- /golang/btcspv/utils_test.go: -------------------------------------------------------------------------------- 1 | package btcspv_test 2 | 3 | import btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" 4 | 5 | func (suite *UtilsSuite) TestStrip0xPrefix() { 6 | suite.Equal("", btcspv.Strip0xPrefix("")) 7 | suite.Equal("333", btcspv.Strip0xPrefix("0x333")) 8 | } 9 | 10 | func (suite *UtilsSuite) TestDecodeIfHex() { 11 | var expected []byte 12 | var actual []byte 13 | 14 | expected = []byte{0} 15 | actual = btcspv.DecodeIfHex("0x00") 16 | suite.Equal(expected, actual) 17 | 18 | expected = []byte{0} 19 | actual = btcspv.DecodeIfHex("00") 20 | suite.Equal(expected, actual) 21 | 22 | expected = []byte{0, 1, 2, 42, 100, 101, 102, 255} 23 | actual = btcspv.DecodeIfHex("0x0001022a646566ff") 24 | suite.Equal(expected, actual) 25 | 26 | suite.Equal([]byte{0xab, 0xcd}, btcspv.DecodeIfHex("abcd")) 27 | suite.Equal([]byte("qqqq"), btcspv.DecodeIfHex("qqqq")) 28 | suite.Equal([]byte("foo"), btcspv.DecodeIfHex("foo")) 29 | suite.Equal([]byte("d"), btcspv.DecodeIfHex("d")) 30 | suite.Equal([]byte(""), btcspv.DecodeIfHex("")) 31 | 32 | } 33 | 34 | func (suite *UtilsSuite) TestEncodeP2SH() { 35 | fixture := suite.Fixtures.EncodeP2SH 36 | 37 | for i := range fixture { 38 | testCase := fixture[i] 39 | expected := testCase.Output 40 | actual, err := btcspv.EncodeP2SH(testCase.Input) 41 | suite.Nil(err) 42 | suite.Equal(expected, actual) 43 | } 44 | } 45 | 46 | func (suite *UtilsSuite) TestEncodeP2PKH() { 47 | fixture := suite.Fixtures.EncodeP2PKH 48 | 49 | for i := range fixture { 50 | testCase := fixture[i] 51 | expected := testCase.Output 52 | actual, err := btcspv.EncodeP2PKH(testCase.Input) 53 | suite.Nil(err) 54 | suite.Equal(expected, actual) 55 | } 56 | } 57 | 58 | func (suite *UtilsSuite) TestEncodeP2WSH() { 59 | fixture := suite.Fixtures.EncodeP2WSH 60 | 61 | for i := range fixture { 62 | testCase := fixture[i] 63 | 64 | expected := testCase.Output 65 | actual, err := btcspv.EncodeP2WSH(testCase.Input) 66 | suite.Nil(err) 67 | suite.Equal(expected, actual) 68 | } 69 | } 70 | 71 | func (suite *UtilsSuite) TestEncodeP2WPKH() { 72 | fixture := suite.Fixtures.EncodeP2WPKH 73 | 74 | for i := range fixture { 75 | testCase := fixture[i] 76 | expected := testCase.Output 77 | actual, err := btcspv.EncodeP2WPKH(testCase.Input) 78 | suite.Nil(err) 79 | suite.Equal(expected, actual) 80 | } 81 | } 82 | 83 | func (suite *UtilsSuite) TestEncodeSegwitErrors() { 84 | // All 0s 85 | input := make([]byte, 20) 86 | actual, err := btcspv.EncodeP2PKH(input) 87 | suite.Equal("", actual) 88 | suite.EqualError(err, btcspv.ZeroBytesError) 89 | 90 | actual, err = btcspv.EncodeP2SH(input) 91 | suite.Equal("", actual) 92 | suite.EqualError(err, btcspv.ZeroBytesError) 93 | 94 | actual, err = btcspv.EncodeP2WPKH(input) 95 | suite.Equal("", actual) 96 | suite.EqualError(err, btcspv.ZeroBytesError) 97 | 98 | WSH, _ := btcspv.NewHash256Digest(make([]byte, 32)) 99 | actual, err = btcspv.EncodeP2WSH(WSH) 100 | suite.Equal("", actual) 101 | suite.EqualError(err, btcspv.ZeroBytesError) 102 | 103 | // Wrong Length 104 | input = make([]byte, 1) 105 | actual, err = btcspv.EncodeP2PKH(input) 106 | suite.Equal("", actual) 107 | suite.EqualError(err, "PKH must be 20 bytes, got 1 bytes") 108 | 109 | actual, err = btcspv.EncodeP2SH(input) 110 | suite.Equal("", actual) 111 | suite.EqualError(err, "SH must be 20 bytes, got 1 bytes") 112 | 113 | actual, err = btcspv.EncodeP2WPKH(input) 114 | suite.Equal("", actual) 115 | suite.EqualError(err, "WPKH must be 20 bytes, got 1 bytes") 116 | } 117 | -------------------------------------------------------------------------------- /golang/btcspv/validate_spv.go: -------------------------------------------------------------------------------- 1 | package btcspv 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | 7 | sdk "github.com/cosmos/cosmos-sdk/types" 8 | ) 9 | 10 | // Prove checks the validity of a merkle proof 11 | func Prove(txid Hash256Digest, merkleRoot Hash256Digest, intermediateNodes []byte, index uint) bool { 12 | // Shortcut the empty-block case 13 | if bytes.Equal(txid[:], merkleRoot[:]) && index == 0 && len(intermediateNodes) == 0 { 14 | return true 15 | } 16 | 17 | proof := []byte{} 18 | proof = append(proof, txid[:]...) 19 | proof = append(proof, intermediateNodes...) 20 | proof = append(proof, merkleRoot[:]...) 21 | 22 | return VerifyHash256Merkle(proof, index) 23 | } 24 | 25 | // CalculateTxID hashes transaction to get txid 26 | func CalculateTxID(version, vin, vout, locktime []byte) Hash256Digest { 27 | txid := []byte{} 28 | txid = append(txid, version...) 29 | txid = append(txid, vin...) 30 | txid = append(txid, vout...) 31 | txid = append(txid, locktime...) 32 | return Hash256(txid) 33 | } 34 | 35 | // ValidateHeaderWork checks validity of header work 36 | func ValidateHeaderWork(digest Hash256Digest, target sdk.Uint) bool { 37 | if bytes.Equal(digest[:], bytes.Repeat([]byte{0}, 32)) { 38 | return false 39 | } 40 | return BytesToBigUint(ReverseEndianness(digest[:])).LT(target) 41 | } 42 | 43 | // ValidateHeaderPrevHash checks validity of header chain 44 | func ValidateHeaderPrevHash(header RawHeader, prevHeaderDigest Hash256Digest) bool { 45 | // Extract prevHash of current header 46 | prevHash := ExtractPrevBlockHashLE(header) 47 | 48 | return bytes.Equal(prevHash[:], prevHeaderDigest[:]) 49 | } 50 | 51 | // ValidateHeaderChain checks validity of header chain 52 | func ValidateHeaderChain(headers []byte) (sdk.Uint, error) { 53 | // Check header chain length 54 | if len(headers)%80 != 0 { 55 | return sdk.ZeroUint(), errors.New("Header bytes not multiple of 80") 56 | } 57 | 58 | var digest Hash256Digest 59 | totalDifficulty := sdk.ZeroUint() 60 | 61 | for i := 0; i < len(headers)/80; i++ { 62 | start := i * 80 63 | end := start + 80 64 | header, _ := NewRawHeader(headers[start:end:end]) 65 | 66 | // After the first header, check that headers are in a chain 67 | if i != 0 { 68 | if !ValidateHeaderPrevHash(header, digest) { 69 | return sdk.ZeroUint(), errors.New("Header bytes not a valid chain") 70 | } 71 | } 72 | 73 | // ith header target 74 | target := ExtractTarget(header) 75 | 76 | // Require that the header has sufficient work 77 | digest = Hash256(header[:]) 78 | if !ValidateHeaderWork(digest, target) { 79 | return sdk.ZeroUint(), errors.New("Header does not meet its own difficulty target") 80 | } 81 | 82 | totalDifficulty = totalDifficulty.Add(CalculateDifficulty(target)) 83 | } 84 | return totalDifficulty, nil 85 | } 86 | 87 | // Validate checks validity of all the elements in a BitcoinHeader 88 | func (b BitcoinHeader) Validate() (bool, error) { 89 | // Check that HashLE is the correct hash of the raw header 90 | headerHash := Hash256(b.Raw[:]) 91 | if !bytes.Equal(headerHash[:], b.Hash[:]) { 92 | return false, errors.New("Hash is not the correct hash of the header") 93 | } 94 | 95 | // Check that the MerkleRootLE is the correct MerkleRoot for the header 96 | extractedMerkleRootLE := ExtractMerkleRootLE(b.Raw) 97 | if !bytes.Equal(extractedMerkleRootLE[:], b.MerkleRoot[:]) { 98 | return false, errors.New("MerkleRoot is not the correct merkle root of the header") 99 | } 100 | 101 | // Check that PrevHash is the correct PrevHash for the header 102 | extractedPrevHashLE := ExtractPrevBlockHashLE(b.Raw) 103 | if bytes.Compare(extractedPrevHashLE[:], b.PrevHash[:]) != 0 { 104 | return false, errors.New("Prevhash is not the correct parent hash of the header") 105 | } 106 | 107 | return true, nil 108 | } 109 | 110 | // Validate checks validity of all the elements in an SPVProof 111 | func (s SPVProof) Validate() (bool, error) { 112 | intermediateNodes := s.IntermediateNodes 113 | index := uint(s.Index) 114 | 115 | validVin := ValidateVin(s.Vin) 116 | if !validVin { 117 | return false, errors.New("Vin is not valid") 118 | } 119 | validVout := ValidateVout(s.Vout) 120 | if !validVout { 121 | return false, errors.New("Vout is not valid") 122 | } 123 | 124 | // Calculate the Tx ID and compare it to the one in SPVProof 125 | txid := CalculateTxID(s.Version, s.Vin, s.Vout, s.Locktime) 126 | if !bytes.Equal(txid[:], s.TxID[:]) { 127 | return false, errors.New("Version, Vin, Vout and Locktime did not yield correct TxID") 128 | } 129 | 130 | // Validate all the fields in ConfirmingHeader 131 | _, err := s.ConfirmingHeader.Validate() 132 | if err != nil { 133 | return false, err 134 | } 135 | 136 | // Check that the proof is valid 137 | validProof := Prove(s.TxID, s.ConfirmingHeader.MerkleRoot, intermediateNodes, index) 138 | if !validProof { 139 | return false, errors.New("Merkle Proof is not valid") 140 | } 141 | 142 | // If there are no errors, return true 143 | return true, nil 144 | } 145 | -------------------------------------------------------------------------------- /golang/btcspv/validate_spv_test.go: -------------------------------------------------------------------------------- 1 | package btcspv_test 2 | 3 | import ( 4 | sdk "github.com/cosmos/cosmos-sdk/types" 5 | btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" 6 | ) 7 | 8 | func (suite *UtilsSuite) TestProve() { 9 | fixture := suite.Fixtures.Prove 10 | 11 | for i := range fixture { 12 | testCase := fixture[i] 13 | 14 | txIDLE := testCase.Input.TxIdLE 15 | merkleRootLE := testCase.Input.MerkleRootLE 16 | proof := testCase.Input.Proof 17 | index := testCase.Input.Index 18 | 19 | expected := testCase.Output 20 | actual := btcspv.Prove(txIDLE, merkleRootLE, proof, index) 21 | suite.Equal(expected, actual) 22 | } 23 | } 24 | 25 | func (suite *UtilsSuite) TestCalculateTxID() { 26 | fixture := suite.Fixtures.CalculateTxID 27 | 28 | for i := range fixture { 29 | testCase := fixture[i] 30 | 31 | version := testCase.Input.Version 32 | vin := testCase.Input.Vin 33 | vout := testCase.Input.Vout 34 | locktime := testCase.Input.Locktime 35 | 36 | expected := testCase.Output 37 | actual := btcspv.CalculateTxID(version, vin, vout, locktime) 38 | suite.Equal(expected, actual) 39 | } 40 | } 41 | 42 | func (suite *UtilsSuite) TestValidateHeaderWork() { 43 | fixture := suite.Fixtures.ValidateHeaderWork 44 | 45 | for i := range fixture { 46 | testCase := fixture[i] 47 | 48 | digest := testCase.Input.Digest 49 | target := testCase.Input.Target 50 | 51 | expected := testCase.Output 52 | actual := btcspv.ValidateHeaderWork(digest, target) 53 | suite.Equal(expected, actual) 54 | } 55 | } 56 | 57 | func (suite *UtilsSuite) TestValidateHeaderPrevHash() { 58 | fixture := suite.Fixtures.ValidateHeaderPrevHash 59 | 60 | for i := range fixture { 61 | testCase := fixture[i] 62 | 63 | header := testCase.Input.Header 64 | prevHash := testCase.Input.PrevHash 65 | 66 | expected := testCase.Output 67 | actual := btcspv.ValidateHeaderPrevHash(header, prevHash) 68 | suite.Equal(expected, actual) 69 | } 70 | } 71 | 72 | func (suite *UtilsSuite) TestValidateHeaderChain() { 73 | fixture := suite.Fixtures.ValidateHeaderChain 74 | 75 | for i := range fixture { 76 | testCase := fixture[i] 77 | expected := sdk.NewUint(testCase.Output) 78 | actual, err := btcspv.ValidateHeaderChain(testCase.Input) 79 | suite.Nil(err) 80 | suite.Equal(expected, actual) 81 | } 82 | 83 | fixtureError := suite.Fixtures.ValidateHeaderChainError 84 | 85 | for i := range fixtureError { 86 | testCase := fixtureError[i] 87 | actual, err := btcspv.ValidateHeaderChain(testCase.Input) 88 | suite.Equal(actual, sdk.NewUint(0)) 89 | suite.EqualError(err, testCase.ErrorMessage) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /golang/cli/header.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "strconv" 7 | "time" 8 | 9 | sdk "github.com/cosmos/cosmos-sdk/types" 10 | btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" 11 | ) 12 | 13 | func prettifyHeaderData( 14 | num uint, 15 | digest btcspv.Hash256Digest, 16 | version uint, 17 | prevHash btcspv.Hash256Digest, 18 | merkleRoot btcspv.Hash256Digest, 19 | timestamp uint, 20 | target sdk.Uint, 21 | nonce uint) string { 22 | 23 | // Convert byte arrays to readable hex strings 24 | digestStr := hex.EncodeToString(digest[:]) 25 | prevHashStr := hex.EncodeToString(prevHash[:]) 26 | merkleRootStr := hex.EncodeToString(merkleRoot[:]) 27 | 28 | // Convert timestamp to readable time 29 | timestampStr := strconv.Itoa(int(timestamp)) 30 | unixIntValue, err := strconv.ParseInt(timestampStr, 10, 64) 31 | if err != nil { 32 | return fmt.Sprintf("%s\n", err) 33 | } 34 | timeStr := time.Unix(unixIntValue, 0) 35 | 36 | // Return data in a formatted string 37 | dataStr := fmt.Sprintf( 38 | "\nHeader #%d:\n Digest: %s,\n Version: %d,\n Prev Hash: %s,\n Merkle Root: %s,\n Time Stamp: %s,\n Target: %d,\n Nonce: %d\n", 39 | num, digestStr, version, prevHashStr, merkleRootStr, timeStr, target, nonce) 40 | 41 | return dataStr 42 | } 43 | 44 | // ParseHeader takes in a header and returns information about that header: digest, version, previous header hash, merkle root, timestamp, target and nonce 45 | func ParseHeader(header btcspv.RawHeader) string { 46 | // Get information about the header using ParseHeader 47 | digest, version, prevHash, merkleRoot, timestamp, target, nonce, err := parseHeader(header) 48 | // Check for errors 49 | if err != nil { 50 | return fmt.Sprintf("%s\n", err) 51 | } 52 | 53 | // Format data using prettifyHeaderData 54 | headerData := prettifyHeaderData( 55 | 0, digest, version, prevHash, merkleRoot, timestamp, target, nonce) 56 | return headerData 57 | } 58 | 59 | // ValidateHeaderChain takes in a chain of headers as a byte array, validates the chain, and returns the total difficulty 60 | func ValidateHeaderChain(headers []byte) string { 61 | // Get the total difficulty using ValidateHeaderChain 62 | totalDifficulty, err := btcspv.ValidateHeaderChain(headers) 63 | // Check for errors 64 | if err != nil { 65 | return fmt.Sprintf("%s\n", err) 66 | } 67 | 68 | // Return the total difficulty 69 | return fmt.Sprintf("\nTotal Difficulty: %d\n", totalDifficulty) 70 | } 71 | 72 | // ExtractMerkleRootBE returns the transaction merkle root from a given block header 73 | // The returned merkle root is big-endian 74 | func ExtractMerkleRootBE(header btcspv.RawHeader) btcspv.Hash256Digest { 75 | return btcspv.ReverseHash256Endianness(btcspv.ExtractMerkleRootLE(header)) 76 | } 77 | 78 | // ExtractPrevBlockHashBE returns the previous block's hash from a block header 79 | // Returns the hash as a big endian []byte 80 | func ExtractPrevBlockHashBE(header btcspv.RawHeader) btcspv.Hash256Digest { 81 | return btcspv.ReverseHash256Endianness(btcspv.ExtractPrevBlockHashLE(header)) 82 | } 83 | 84 | // ParseHeader parses a block header struct from a bytestring 85 | func parseHeader(header btcspv.RawHeader) (btcspv.Hash256Digest, uint, btcspv.Hash256Digest, btcspv.Hash256Digest, uint, sdk.Uint, uint, error) { 86 | digestLE := btcspv.Hash256(header[:]) 87 | 88 | digest := btcspv.ReverseHash256Endianness(digestLE) 89 | version := btcspv.BytesToUint(btcspv.ReverseEndianness(header[0:4:4])) 90 | prevHash := btcspv.ExtractPrevBlockHashLE(header) 91 | merkleRoot := btcspv.ExtractMerkleRootLE(header) 92 | timestamp := btcspv.ExtractTimestamp(header) 93 | target := btcspv.ExtractTarget(header) 94 | nonce := btcspv.BytesToUint(btcspv.ReverseEndianness(header[76:80:80])) 95 | 96 | return digest, version, prevHash, merkleRoot, timestamp, target, nonce, nil 97 | } 98 | -------------------------------------------------------------------------------- /golang/cli/prove.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" 7 | ) 8 | 9 | // Prove checks the validity of a merkle proof 10 | // Note that `index` is not a reliable indicator of location within a block. 11 | func Prove( 12 | version []byte, 13 | vin []byte, 14 | vout []byte, 15 | locktime []byte, 16 | merkleRoot btcspv.Hash256Digest, 17 | intermediateNodes []byte, 18 | index uint) string { 19 | // Calculate the tx id 20 | txid := btcspv.CalculateTxID(version, vin, vout, locktime) 21 | 22 | // Check if the merkle proof is valid using Prove 23 | valid := btcspv.Prove(txid, merkleRoot, intermediateNodes, index) 24 | 25 | // Returns string stating if proof is valid or not 26 | return fmt.Sprintf("\nValid proof: %t\n", valid) 27 | } 28 | -------------------------------------------------------------------------------- /golang/cli/spvcli.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | 8 | btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" 9 | ) 10 | 11 | func route(command string, arguments [][]byte) string { 12 | var result string 13 | 14 | switch command { 15 | case "parseVin": 16 | result = ParseVin(arguments[0]) 17 | case "parseVout": 18 | result = ParseVout(arguments[0]) 19 | case "parseHeader": 20 | rawHeader, _ := btcspv.NewRawHeader(arguments[0]) 21 | result = ParseHeader(rawHeader) 22 | case "validateHeaderChain": 23 | result = ValidateHeaderChain(arguments[0]) 24 | case "prove": 25 | // convert argument to a uint 26 | str := string(arguments[6]) 27 | uint64Arg, err := strconv.ParseUint(str, 10, 32) 28 | if err != nil { 29 | return fmt.Sprintf("%s\n", err) 30 | } 31 | uintArg := uint(uint64Arg) 32 | merkleRoot, _ := btcspv.NewHash256Digest(arguments[4]) 33 | result = Prove(arguments[0], arguments[1], arguments[2], arguments[3], merkleRoot, arguments[5], uintArg) 34 | default: 35 | result = fmt.Sprintf("Unknown command: %s\n", command) 36 | } 37 | 38 | return result 39 | } 40 | 41 | // Map function to slice of strings 42 | func Map(vs []string, f func(string) []byte) [][]byte { 43 | vsm := make([][]byte, len(vs)) 44 | for i, v := range vs { 45 | vsm[i] = f(v) 46 | } 47 | return vsm 48 | } 49 | 50 | func main() { 51 | var result string 52 | 53 | if len(os.Args) < 2 { 54 | fmt.Print("Not enough arguments\n") 55 | return 56 | } 57 | 58 | command := os.Args[1] 59 | arguments := Map(os.Args[2:], btcspv.DecodeIfHex) 60 | 61 | result = route(command, arguments) 62 | fmt.Print(result) 63 | } 64 | -------------------------------------------------------------------------------- /golang/cli/vin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "fmt" 7 | 8 | btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" 9 | ) 10 | 11 | // InputType an enum of types of bitcoin inputs 12 | type InputType int 13 | 14 | // possible input types 15 | const ( 16 | InputNone InputType = 0 17 | Legacy InputType = 1 18 | Compatibility InputType = 2 19 | Witness InputType = 3 20 | ) 21 | 22 | func prettifyInput(numInput int, outpoint btcspv.Hash256Digest, index uint, inputType InputType, sequence uint) string { 23 | outpointStr := hex.EncodeToString(outpoint[:]) 24 | 25 | // Get the input type in readable format 26 | inputTypeString := GetInputType(inputType) 27 | 28 | dataStr := fmt.Sprintf("\nInput #%d:\n Outpoint: %s,\n Index: %d,\n Type: %s,\n Sequence: %d\n", numInput, outpointStr, index, inputTypeString, sequence) 29 | 30 | return dataStr 31 | } 32 | 33 | // ParseVin parses an input vector from hex 34 | func ParseVin(vin []byte) string { 35 | // Validate the vin 36 | isVin := btcspv.ValidateVin(vin) 37 | if !isVin { 38 | return "Invalid Vin\n" 39 | } 40 | 41 | numInputs := int(vin[0]) 42 | var formattedInputs string 43 | for i := 0; i < numInputs; i++ { 44 | // Extract each vin at the specified index 45 | vin, _ := btcspv.ExtractInputAtIndex(vin, uint(i)) 46 | 47 | // Use ParseInput to get more information about the vin 48 | sequence, inputID, inputIndex, inputType := parseInput(vin) 49 | 50 | // Format information about the vin 51 | numInput := i + 1 52 | data := prettifyInput(numInput, inputID, inputIndex, inputType, sequence) 53 | 54 | // Concat vin information onto `formattedInputs` 55 | formattedInputs = formattedInputs + data 56 | } 57 | 58 | return formattedInputs 59 | } 60 | 61 | // ExtractInputTxID returns the input tx id bytes 62 | func ExtractInputTxID(input []byte) btcspv.Hash256Digest { 63 | LE := btcspv.ExtractInputTxIDLE(input) 64 | txID := btcspv.ReverseHash256Endianness(LE) 65 | return txID 66 | } 67 | 68 | // ParseInput returns human-readable information about an input 69 | func parseInput(input []byte) (uint, btcspv.Hash256Digest, uint, InputType) { 70 | // NB: If the scriptsig is exactly 00, we are Witness. 71 | // Otherwise we are Compatibility or Legacy 72 | var sequence uint32 73 | var witnessTag []byte 74 | var inputType InputType 75 | 76 | if input[36] != 0 { 77 | sequence, _ = btcspv.ExtractSequenceLegacy(input) 78 | witnessTag = input[36:39:39] 79 | 80 | if bytes.Equal(witnessTag, []byte{34, 0, 32}) || bytes.Equal(witnessTag, []byte{22, 0, 20}) { 81 | inputType = Compatibility 82 | } else { 83 | inputType = Legacy 84 | } 85 | } else { 86 | sequence = btcspv.ExtractSequenceWitness(input) 87 | inputType = Witness 88 | } 89 | 90 | inputID := ExtractInputTxID(input) 91 | inputIndex := btcspv.ExtractTxIndex(input) 92 | 93 | return uint(sequence), inputID, inputIndex, inputType 94 | } 95 | 96 | // GetInputType returns the name of the input type associated with the number 97 | func GetInputType(inputType InputType) string { 98 | var typeString string 99 | switch inputType { 100 | case InputNone: 101 | typeString = "Input None" 102 | case Legacy: 103 | typeString = "Legacy" 104 | case Compatibility: 105 | typeString = "Compatibility" 106 | case Witness: 107 | typeString = "Witness" 108 | } 109 | return typeString 110 | } 111 | -------------------------------------------------------------------------------- /golang/cli/vout.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "fmt" 7 | 8 | btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" 9 | ) 10 | 11 | // OutputType an enum of types of bitcoin outputs 12 | type OutputType int 13 | 14 | // possible output types 15 | const ( 16 | OutputNone OutputType = 0 17 | WPKH OutputType = 1 18 | WSH OutputType = 2 19 | OpReturn OutputType = 3 20 | PKH OutputType = 4 21 | SH OutputType = 5 22 | Nonstandard OutputType = 6 23 | ) 24 | 25 | func prettifyOutput( 26 | numOutput int, 27 | outpoint []byte, 28 | value uint, 29 | outputType OutputType) string { 30 | 31 | outpointStr := hex.EncodeToString(outpoint) 32 | 33 | // Get the output type in readable format 34 | outputTypeString := GetOutputType(outputType) 35 | 36 | // Get the address associated with the output 37 | address := getAddress(outputType, outpoint) 38 | 39 | dataStr := fmt.Sprintf( 40 | "\nOutput #%d:\n Address: %s\n Payload: %s,\n Value: %d,\n Type: %s\n", 41 | numOutput, address, outpointStr, value, outputTypeString) 42 | return dataStr 43 | } 44 | 45 | // getAddress return the address associated with the output 46 | func getAddress(outputType OutputType, outpoint []byte) string { 47 | var address string 48 | var err error 49 | 50 | switch outputType { 51 | case WPKH: 52 | address, err = btcspv.EncodeP2WPKH(outpoint) 53 | case WSH: 54 | digest, _ := btcspv.NewHash256Digest(outpoint) 55 | address, err = btcspv.EncodeP2WSH(digest) 56 | case PKH: 57 | address, err = btcspv.EncodeP2PKH(outpoint) 58 | case SH: 59 | address, err = btcspv.EncodeP2SH(outpoint) 60 | default: 61 | address = "" 62 | } 63 | 64 | if err != nil { 65 | return fmt.Sprintf("%s\n", err) 66 | } 67 | return address 68 | } 69 | 70 | // ParseVout parses an output vector from hex 71 | func ParseVout(vout []byte) string { 72 | // Validate the vout 73 | isVout := btcspv.ValidateVout(vout) 74 | if !isVout { 75 | return "Invalid Vout\n" 76 | } 77 | 78 | numOutputs := int(vout[0]) 79 | var formattedOutputs string 80 | for i := 0; i < numOutputs; i++ { 81 | // Extract each vout at the specified index 82 | vout, err := btcspv.ExtractOutputAtIndex(vout, uint(i)) 83 | if err != nil { 84 | return fmt.Sprintf("%s\n", err) 85 | } 86 | 87 | // Use ParseOutput to get more information about the vout 88 | value, outputType, payload := parseOutput(vout) 89 | 90 | // Format information about the vout 91 | numOutput := i + 1 92 | data := prettifyOutput(numOutput, payload, value, outputType) 93 | 94 | // Concat vout information onto formattedOutputs 95 | formattedOutputs = formattedOutputs + data 96 | } 97 | 98 | return formattedOutputs 99 | } 100 | 101 | // ParseOutput extracts human-readable information from an output 102 | func parseOutput(output []byte) (uint, OutputType, []byte) { 103 | value := btcspv.ExtractValue(output) 104 | var outputType OutputType 105 | var payload []byte 106 | 107 | if output[9] == 0x6a { 108 | outputType = OpReturn 109 | payload, _ = btcspv.ExtractOpReturnData(output) 110 | } else { 111 | prefixHash := output[8:10:10] 112 | if bytes.Equal(prefixHash, []byte{0x22, 0x00}) { 113 | outputType = WSH 114 | payload = output[11:43:43] 115 | } else if bytes.Equal(prefixHash, []byte{0x16, 0x00}) { 116 | outputType = WPKH 117 | payload = output[11:31:31] 118 | } else if bytes.Equal(prefixHash, []byte{0x19, 0x76}) { 119 | outputType = PKH 120 | payload = output[12:32:32] 121 | } else if bytes.Equal(prefixHash, []byte{0x17, 0xa9}) { 122 | outputType = SH 123 | payload = output[11:31:31] 124 | } else { 125 | outputType = Nonstandard 126 | payload = []byte{} 127 | } 128 | } 129 | 130 | return value, outputType, payload 131 | } 132 | 133 | // GetOutputType returns the name of the output type associated with the number 134 | func GetOutputType(outputType OutputType) string { 135 | var typeString string 136 | switch outputType { 137 | case OutputNone: 138 | typeString = "Output None" 139 | case WPKH: 140 | typeString = "WPKH" 141 | case WSH: 142 | typeString = "WSH" 143 | case OpReturn: 144 | typeString = "Op Return" 145 | case PKH: 146 | typeString = "PKH" 147 | case SH: 148 | typeString = "SH" 149 | case Nonstandard: 150 | typeString = "Nonstandard" 151 | } 152 | return typeString 153 | } 154 | -------------------------------------------------------------------------------- /golang/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/summa-tx/bitcoin-spv/golang 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a 7 | github.com/cosmos/cosmos-sdk v0.35.0 8 | github.com/gogo/protobuf v1.1.1 9 | github.com/stretchr/testify v1.3.0 10 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 11 | ) 12 | -------------------------------------------------------------------------------- /js/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es6: true, 6 | }, 7 | extends: [ 8 | 'airbnb-base', 9 | ], 10 | globals: { 11 | Atomics: 'readonly', 12 | SharedArrayBuffer: 'readonly', 13 | }, 14 | parserOptions: { 15 | ecmaVersion: 2018, 16 | }, 17 | rules: { 18 | "comma-dangle": 'off', 19 | "no-bitwise": 'off' 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /js/.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | build/ 3 | blocks/ 4 | __pycache__/ 5 | node_modules/ 6 | .mypy_cache 7 | ./golang/spvcli 8 | 9 | # coverage 10 | scTopics 11 | coverage/ 12 | coverageEnv/ 13 | coverage.json 14 | out 15 | dist/ 16 | .nyc_output/ -------------------------------------------------------------------------------- /js/.npmignore: -------------------------------------------------------------------------------- 1 | dist 2 | test -------------------------------------------------------------------------------- /js/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current' 8 | } 9 | } 10 | ] 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /js/clients/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | **/vendor/*.js 3 | -------------------------------------------------------------------------------- /js/clients/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es6: true 6 | }, 7 | extends: [ 8 | 'airbnb-base', 9 | ], 10 | globals: { 11 | Atomics: 'readonly', 12 | BigInt: 'readable', 13 | BigInt64Array: 'readable', 14 | BigUint64Array: 'readable', 15 | queueMicrotask: 'readable', 16 | SharedArrayBuffer: 'readable', 17 | TextEncoder: 'readable', 18 | TextDecoder: 'readable', 19 | mocha: true 20 | }, 21 | parserOptions: { 22 | ecmaVersion: 2018, 23 | }, 24 | rules: { 25 | "comma-dangle": 'off', 26 | "no-bitwise": 'off', 27 | 'no-use-before-define': ['error', { functions: false }], 28 | curly: ["error", "all"], 29 | 'no-underscore-dangle': ['error', { 'allowAfterThis': true }], 30 | 'no-plusplus': ['error', { 'allowForLoopAfterthoughts': true }] 31 | }, 32 | overrides: [ 33 | { 34 | files: ["**/*-test.js"], 35 | env: { jest: true } 36 | } 37 | ] 38 | }; 39 | -------------------------------------------------------------------------------- /js/clients/README.md: -------------------------------------------------------------------------------- 1 | # bitcoin-spv proof fetcher 2 | 3 | The following clients are provided to fetch Bitcoin data to create proofs. 4 | ## Clients 5 | 6 | - BcoinClient 7 | 8 | ### BcoinClient 9 | Use the BcoinClient fetch Bitcoin data from a bcoin node. See the bcoin docs for how to run your own node. 10 | 11 | #### Install 12 | 13 | Node.js: 14 | ```sh 15 | $ npm i --save @summa-tx/bitcoin-spv-js-clients 16 | ``` 17 | 18 | ```js 19 | const { BcoinClient } = require('@summa-tx/bitcoin-spv-js-clients'); 20 | ``` 21 | 22 | ES6 module: 23 | 24 | ```js 25 | import { BcoinClient } from '@summa-tx/bitcoin-spv-js-clients'; 26 | ``` 27 | 28 | ### Use 29 | 30 | The BcoinClient proof fetcher exports a single constructor function, `BcoinClient`, which accepts an options Object to configure the client. This constructor is an extension of bcoin's `NodeClient`. See [bcoin.io](https://bcoin.io/api-docs/#configuring-clients) for more information about how to configure the client. 31 | 32 | ```js 33 | const options = { 34 | network: 'testnet', 35 | port: 18332, 36 | apiKey: 'api-key' 37 | }; 38 | 39 | const client = new BcoinClient(options); 40 | ``` 41 | 42 | ### Methods 43 | 44 | #### client.getBlockHeader(block) 45 | * `@param {Hash|Number} block` 46 | * `@returns {Promise}` 47 | 48 | Retrieve a block header. 49 | 50 | ```js 51 | const client = new BcoinClient(options); 52 | const blockHeader = await client.getBlockHeader(150151); 53 | 54 | // block header 55 | // { 56 | // "hash": "6f1003edd05cad861395225415160b5236968cc223fe982796b6e959c9651d44", 57 | // "version": 536870912, 58 | // "prevBlock": "0c4ea5e675941eca1909275f21903cef755069a03c57c10f4d4dadcdd7146daf", 59 | // "merkleRoot": "9a249c682aba07943c8a1f9bd774a15d71372fcd7f3f9ee99e8c7aa022ae6aa0", 60 | // "time": 1571661863, 61 | // "bits": 545259519, 62 | // "nonce": 8, 63 | // "height": 100, 64 | // "chainwork": "00000000000000000000000000000000000000000000000000000000000000ca" 65 | // } 66 | ``` 67 | 68 | #### client.getProof(txid) 69 | * `@param {String} txid` - txid in big endian 70 | * `@returns {Object}` - proof object 71 | 72 | Get a proof for a transaction. 73 | 74 | ```js 75 | const client = new BcoinClient(options); 76 | const txid = 77 | 78 | // proof object 79 | // { 80 | // version: {String}, 81 | // vin: {String}, 82 | // vout: {String}, 83 | // locktime: {String}, 84 | // tx_id: {String}, 85 | // index: {Number}, 86 | // confirming_header: {Object}, 87 | // intermediate_nodes: {String} 88 | // } 89 | ``` 90 | 91 | #### client.getHeader(block) 92 | * `@param {Hash|Number} block` - block height or hash 93 | * `@returns {Object} header` - header object 94 | 95 | Gets header by height or hash. 96 | ```js 97 | const client = new BcoinClient(options); 98 | const block = 12345; 99 | const header = client.getHeader(block); 100 | 101 | // { 102 | // raw: {String}, 103 | // hash: {String}, 104 | // height: {Number}, 105 | // prevhash: {String}, 106 | // merkle_root: {String}, 107 | // } 108 | ``` 109 | 110 | #### client.getMerkleProof(txid, height) 111 | * `@param {String}` `txid` 112 | * `@param {Number} height` 113 | * `@returns {[][]String, Number}` - a merkle proof and the index of the leaf 114 | 115 | Validate the merkle tree of a block and then compute a merkle proof of inclusion for the txid. 116 | 117 | ```js 118 | const client = new BcoinClient(options); 119 | const height = 12345; 120 | const txid = '3a4324234' 121 | const header = client.getMerkleProof(txid, height); 122 | 123 | // merkle proof 124 | // [ 125 | // '8e2d404a039a7a3e1768b161aa23546aab0444b73905bdd3d68b3d6f1769e8c0...', 126 | // 4 127 | // ] 128 | ``` 129 | 130 | #### client.getHeaderChainByCount(height, count, enc) 131 | * `@param {Number} height` - starting block height 132 | * `@param {Number} count` - number of headers 133 | * `@param {String} enc` - 'json' or 'hex' or 'btcspv' 134 | * `@returns {Object}` 135 | 136 | Fetch a header chain by count. 137 | 138 | ```js 139 | const client = new BcoinClient(options); 140 | const height = 12345; 141 | const numOfHeaders = 2; 142 | const encoding = 'hex'; 143 | const header = client.getHeaderChainByCount(height, numOfHeaders, encoding); 144 | 145 | // headers (hex) 146 | // { 147 | // headers: '00000020a15e218f5f158a31053ea101b917a6113c807f6bcdc85a000000000000000000cc7cf9eab23c2eae050377375666cd7862c1dfeb81abd3198c3a3f8e045d91484a39225af6d00018659e5e8a0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff64030096072cfabe6d6d08d1c2f6d904f4e1cd10c6558f8e5aed5d6a89c43bb22862464ebb819dd8813404000000f09f909f104d696e6564206279206a6f73656d7372000000000000000000000000000000000000000000000000000000000000000000007f06000001807c814a000000001976a914c825a1ecf2a6830c4401620c3a16f1995057c2ab88acefebcf38...' 148 | // } 149 | ``` 150 | 151 | ```js 152 | const header = client.getHeaderChainByCount(height, numOfHeaders, 'json'); 153 | 154 | // headers (json) 155 | // { 156 | // headers: [ 157 | // { 158 | // "hash": "6f1003edd05cad861395225415160b5236968cc223fe982796b6e959c9651d44", 159 | // "version": 536870912, 160 | // "prevBlock": "0c4ea5e675941eca1909275f21903cef755069a03c57c10f4d4dadcdd7146daf", 161 | // "merkleRoot": "9a249c682aba07943c8a1f9bd774a15d71372fcd7f3f9ee99e8c7aa022ae6aa0", 162 | // "time": 1571661863, 163 | // "bits": 545259519, 164 | // "nonce": 8, 165 | // "height": 100, 166 | // "chainwork": "00000000000000000000000000000000000000000000000000000000000000ca" 167 | // }, 168 | // { 169 | // "hash": "6f1003edd05cad861395225415160b5236968cc223fe982796b6e959c9651d44", 170 | // "version": 536870912, 171 | // "prevBlock": "0c4ea5e675941eca1909275f21903cef755069a03c57c10f4d4dadcdd7146daf", 172 | // "merkleRoot": "9a249c682aba07943c8a1f9bd774a15d71372fcd7f3f9ee99e8c7aa022ae6aa0", 173 | // "time": 1571661863, 174 | // "bits": 545259519, 175 | // "nonce": 8, 176 | // "height": 100, 177 | // "chainwork": "00000000000000000000000000000000000000000000000000000000000000ca" 178 | // }, 179 | // ] 180 | // } 181 | ``` 182 | -------------------------------------------------------------------------------- /js/clients/lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @file Part of the [bitcoin-spv]{@link https://github.com/summa-tx/bitcoin-spv} project 4 | * 5 | * @title index 6 | * @summary List of clients 7 | * @author Mark Tyneway 8 | * @copyright (c) [Summa]{@link https://summa.one} 2019 9 | * @module clients 10 | * 11 | */ 12 | 13 | const BcoinClient = require('./BcoinClient'); 14 | const Block = require('./vendor/block'); 15 | const TX = require('./vendor/tx'); 16 | 17 | module.exports = { 18 | BcoinClient, 19 | Block, 20 | TX 21 | }; -------------------------------------------------------------------------------- /js/clients/lib/vendor/abstractblock.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * abstractblock.js - abstract block object for bcoin 3 | * Copyright (c) 2014-2015, Fedor Indutny (MIT License) 4 | * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). 5 | * https://github.com/bcoin-org/bcoin 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const assert = require('./bsert'); 11 | const hash256 = require('./hash256'); 12 | const bio = require('./bufio'); 13 | const util = require('./util'); 14 | const consensus = require('./consensus'); 15 | 16 | /** 17 | * Abstract Block 18 | * The class which all block-like objects inherit from. 19 | * @alias module:primitives.AbstractBlock 20 | * @abstract 21 | * @property {Number} version 22 | * @property {Hash} prevBlock 23 | * @property {Hash} merkleRoot 24 | * @property {Number} time 25 | * @property {Number} bits 26 | * @property {Number} nonce 27 | */ 28 | 29 | class AbstractBlock { 30 | /** 31 | * Create an abstract block. 32 | * @constructor 33 | */ 34 | 35 | constructor() { 36 | this.version = 1; 37 | this.prevBlock = consensus.ZERO_HASH; 38 | this.merkleRoot = consensus.ZERO_HASH; 39 | this.time = 0; 40 | this.bits = 0; 41 | this.nonce = 0; 42 | 43 | this.mutable = false; 44 | 45 | this._hash = null; 46 | this._hhash = null; 47 | } 48 | 49 | /** 50 | * Inject properties from options object. 51 | * @private 52 | * @param {Object} options 53 | */ 54 | 55 | parseOptions(options) { 56 | assert(options, 'Block data is required.'); 57 | assert((options.version >>> 0) === options.version); 58 | assert(Buffer.isBuffer(options.prevBlock)); 59 | assert(Buffer.isBuffer(options.merkleRoot)); 60 | assert((options.time >>> 0) === options.time); 61 | assert((options.bits >>> 0) === options.bits); 62 | assert((options.nonce >>> 0) === options.nonce); 63 | 64 | this.version = options.version; 65 | this.prevBlock = options.prevBlock; 66 | this.merkleRoot = options.merkleRoot; 67 | this.time = options.time; 68 | this.bits = options.bits; 69 | this.nonce = options.nonce; 70 | 71 | if (options.mutable != null) { 72 | assert(typeof options.mutable === 'boolean'); 73 | this.mutable = options.mutable; 74 | } 75 | 76 | return this; 77 | } 78 | 79 | /** 80 | * Inject properties from json object. 81 | * @private 82 | * @param {Object} json 83 | */ 84 | 85 | parseJSON(json) { 86 | assert(json, 'Block data is required.'); 87 | assert((json.version >>> 0) === json.version); 88 | assert(typeof json.prevBlock === 'string'); 89 | assert(typeof json.merkleRoot === 'string'); 90 | assert((json.time >>> 0) === json.time); 91 | assert((json.bits >>> 0) === json.bits); 92 | assert((json.nonce >>> 0) === json.nonce); 93 | 94 | this.version = json.version; 95 | this.prevBlock = util.fromRev(json.prevBlock); 96 | this.merkleRoot = util.fromRev(json.merkleRoot); 97 | this.time = json.time; 98 | this.bits = json.bits; 99 | this.nonce = json.nonce; 100 | 101 | return this; 102 | } 103 | 104 | /** 105 | * Test whether the block is a memblock. 106 | * @returns {Boolean} 107 | */ 108 | 109 | isMemory() { 110 | return false; 111 | } 112 | 113 | /** 114 | * Clear any cached values (abstract). 115 | */ 116 | 117 | _refresh() { 118 | this._hash = null; 119 | this._hhash = null; 120 | } 121 | 122 | /** 123 | * Clear any cached values. 124 | */ 125 | 126 | refresh() { 127 | return this._refresh(); 128 | } 129 | 130 | /** 131 | * Hash the block headers. 132 | * @param {String?} enc - Can be `'hex'` or `null`. 133 | * @returns {Hash|Buffer} hash 134 | */ 135 | 136 | hash(enc) { 137 | let h = this._hash; 138 | 139 | if (!h) { 140 | h = hash256.digest(this.toHead()); 141 | if (!this.mutable) 142 | this._hash = h; 143 | } 144 | 145 | if (enc === 'hex') { 146 | let hex = this._hhash; 147 | if (!hex) { 148 | hex = h.toString('hex'); 149 | if (!this.mutable) 150 | this._hhash = hex; 151 | } 152 | h = hex; 153 | } 154 | 155 | return h; 156 | } 157 | 158 | /** 159 | * Serialize the block headers. 160 | * @returns {Buffer} 161 | */ 162 | 163 | toHead() { 164 | return this.writeHead(bio.write(80)).render(); 165 | } 166 | 167 | /** 168 | * Inject properties from serialized data. 169 | * @private 170 | * @param {Buffer} data 171 | */ 172 | 173 | fromHead(data) { 174 | return this.readHead(bio.read(data)); 175 | } 176 | 177 | /** 178 | * Serialize the block headers. 179 | * @param {BufferWriter} bw 180 | */ 181 | 182 | writeHead(bw) { 183 | bw.writeU32(this.version); 184 | bw.writeHash(this.prevBlock); 185 | bw.writeHash(this.merkleRoot); 186 | bw.writeU32(this.time); 187 | bw.writeU32(this.bits); 188 | bw.writeU32(this.nonce); 189 | return bw; 190 | } 191 | 192 | /** 193 | * Parse the block headers. 194 | * @param {BufferReader} br 195 | */ 196 | 197 | readHead(br) { 198 | this.version = br.readU32(); 199 | this.prevBlock = br.readHash(); 200 | this.merkleRoot = br.readHash(); 201 | this.time = br.readU32(); 202 | this.bits = br.readU32(); 203 | this.nonce = br.readU32(); 204 | return this; 205 | } 206 | 207 | /** 208 | * Verify the block. 209 | * @returns {Boolean} 210 | */ 211 | 212 | verify() { 213 | if (!this.verifyPOW()) 214 | return false; 215 | 216 | if (!this.verifyBody()) 217 | return false; 218 | 219 | return true; 220 | } 221 | 222 | /** 223 | * Verify proof-of-work. 224 | * @returns {Boolean} 225 | */ 226 | 227 | verifyPOW() { 228 | return consensus.verifyPOW(this.hash(), this.bits); 229 | } 230 | 231 | /** 232 | * Verify the block. 233 | * @returns {Boolean} 234 | */ 235 | 236 | verifyBody() { 237 | throw new Error('Abstract method.'); 238 | } 239 | 240 | /** 241 | * Get little-endian block hash. 242 | * @returns {Hash} 243 | */ 244 | 245 | rhash() { 246 | return util.revHex(this.hash()); 247 | } 248 | } 249 | 250 | /* 251 | * Expose 252 | */ 253 | 254 | module.exports = AbstractBlock; 255 | -------------------------------------------------------------------------------- /js/clients/lib/vendor/hash256.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * hash256.js - Hash256 implementation for bcrypto 3 | * Copyright (c) 2017-2019, Christopher Jeffrey (MIT License). 4 | * https://github.com/bcoin-org/bcrypto 5 | * 6 | * Resources: 7 | * https://github.com/bitcoin/bitcoin/blob/master/src/hash.h 8 | */ 9 | 10 | 11 | const assert = require('./bsert'); 12 | const SHA256 = require('./sha256'); 13 | 14 | /** 15 | * Hash256 16 | */ 17 | 18 | class Hash256 { 19 | constructor() { 20 | this.ctx = new SHA256(); 21 | } 22 | 23 | init() { 24 | this.ctx.init(); 25 | return this; 26 | } 27 | 28 | update(data) { 29 | this.ctx.update(data); 30 | return this; 31 | } 32 | 33 | final() { 34 | const out = Buffer.allocUnsafe(32); 35 | this.ctx._final(out); 36 | this.ctx.init(); 37 | this.ctx.update(out); 38 | this.ctx._final(out); 39 | return out; 40 | } 41 | 42 | static hash() { 43 | return new Hash256(); 44 | } 45 | 46 | static digest(data) { 47 | return Hash256.ctx.init().update(data).final(); 48 | } 49 | 50 | static root(left, right) { 51 | assert(Buffer.isBuffer(left) && left.length === 32); 52 | assert(Buffer.isBuffer(right) && right.length === 32); 53 | return Hash256.ctx.init().update(left).update(right).final(); 54 | } 55 | 56 | static multi(x, y, z) { 57 | const { ctx } = Hash256; 58 | ctx.init(); 59 | ctx.update(x); 60 | ctx.update(y); 61 | if (z) { ctx.update(z); } 62 | return ctx.final(); 63 | } 64 | } 65 | 66 | /* 67 | * Static 68 | */ 69 | 70 | Hash256.native = 0; 71 | Hash256.id = 'HASH256'; 72 | Hash256.size = 32; 73 | Hash256.bits = 256; 74 | Hash256.blockSize = 64; 75 | Hash256.zero = Buffer.alloc(32, 0x00); 76 | Hash256.ctx = new Hash256(); 77 | 78 | /* 79 | * Expose 80 | */ 81 | 82 | module.exports = Hash256; 83 | -------------------------------------------------------------------------------- /js/clients/lib/vendor/headers.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * headers.js - headers object for bcoin 3 | * Copyright (c) 2014-2015, Fedor Indutny (MIT License) 4 | * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). 5 | * https://github.com/bcoin-org/bcoin 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const bio = require('./bufio'); 11 | const util = require('./util'); 12 | const AbstractBlock = require('./abstractblock'); 13 | 14 | /** 15 | * Headers 16 | * Represents block headers obtained 17 | * from the network via `headers`. 18 | * @alias module:primitives.Headers 19 | * @extends AbstractBlock 20 | */ 21 | 22 | class Headers extends AbstractBlock { 23 | /** 24 | * Create headers. 25 | * @constructor 26 | * @param {Object} options 27 | */ 28 | 29 | constructor(options) { 30 | super(); 31 | 32 | if (options) 33 | this.parseOptions(options); 34 | } 35 | 36 | /** 37 | * Perform non-contextual 38 | * verification on the headers. 39 | * @returns {Boolean} 40 | */ 41 | 42 | verifyBody() { 43 | return true; 44 | } 45 | 46 | /** 47 | * Get size of the headers. 48 | * @returns {Number} 49 | */ 50 | 51 | getSize() { 52 | return 81; 53 | } 54 | 55 | /** 56 | * Serialize the headers to a buffer writer. 57 | * @param {BufferWriter} bw 58 | */ 59 | 60 | toWriter(bw) { 61 | this.writeHead(bw); 62 | bw.writeVarint(0); 63 | return bw; 64 | } 65 | 66 | /** 67 | * Serialize the headers. 68 | * @returns {Buffer|String} 69 | */ 70 | 71 | toRaw() { 72 | const size = this.getSize(); 73 | return this.toWriter(bio.write(size)).render(); 74 | } 75 | 76 | /** 77 | * Inject properties from buffer reader. 78 | * @private 79 | * @param {Buffer} data 80 | */ 81 | 82 | fromReader(br) { 83 | this.readHead(br); 84 | br.readVarint(); 85 | return this; 86 | } 87 | 88 | /** 89 | * Inject properties from serialized data. 90 | * @private 91 | * @param {Buffer} data 92 | */ 93 | 94 | fromRaw(data) { 95 | return this.fromReader(bio.read(data)); 96 | } 97 | 98 | /** 99 | * Instantiate headers from buffer reader. 100 | * @param {BufferReader} br 101 | * @returns {Headers} 102 | */ 103 | 104 | static fromReader(br) { 105 | return new this().fromReader(br); 106 | } 107 | 108 | /** 109 | * Instantiate headers from serialized data. 110 | * @param {Buffer} data 111 | * @param {String?} enc - Encoding, can be `'hex'` or null. 112 | * @returns {Headers} 113 | */ 114 | 115 | static fromRaw(data, enc) { 116 | if (typeof data === 'string') 117 | data = Buffer.from(data, enc); 118 | return new this().fromRaw(data); 119 | } 120 | 121 | /** 122 | * Instantiate headers from serialized data. 123 | * @param {Buffer} data 124 | * @param {String?} enc - Encoding, can be `'hex'` or null. 125 | * @returns {Headers} 126 | */ 127 | 128 | static fromHead(data, enc) { 129 | if (typeof data === 'string') 130 | data = Buffer.from(data, enc); 131 | return new this().fromHead(data); 132 | } 133 | 134 | /** 135 | * Instantiate headers from a chain entry. 136 | * @param {ChainEntry} entry 137 | * @returns {Headers} 138 | */ 139 | 140 | static fromEntry(entry) { 141 | const headers = new this(); 142 | headers.version = entry.version; 143 | headers.prevBlock = entry.prevBlock; 144 | headers.merkleRoot = entry.merkleRoot; 145 | headers.time = entry.time; 146 | headers.bits = entry.bits; 147 | headers.nonce = entry.nonce; 148 | headers._hash = entry.hash; 149 | headers._hhash = entry.hash; 150 | return headers; 151 | } 152 | 153 | /** 154 | * Convert the block to a headers object. 155 | * @returns {Headers} 156 | */ 157 | 158 | toHeaders() { 159 | return this; 160 | } 161 | 162 | /** 163 | * Convert the block to a headers object. 164 | * @param {Block|MerkleBlock} block 165 | * @returns {Headers} 166 | */ 167 | 168 | static fromBlock(block) { 169 | const headers = new this(block); 170 | headers._hash = block._hash; 171 | headers._hhash = block._hhash; 172 | return headers; 173 | } 174 | 175 | /** 176 | * Convert the block to an object suitable 177 | * for JSON serialization. 178 | * @returns {Object} 179 | */ 180 | 181 | toJSON() { 182 | return this.getJSON(); 183 | } 184 | 185 | /** 186 | * Convert the block to an object suitable 187 | * for JSON serialization. Note that the hashes 188 | * will be reversed to abide by bitcoind's legacy 189 | * of little-endian uint256s. 190 | * @param {Network} network 191 | * @param {CoinView} view 192 | * @param {Number} height 193 | * @returns {Object} 194 | */ 195 | 196 | getJSON(network, view, height) { 197 | return { 198 | hash: this.rhash(), 199 | height: height, 200 | version: this.version, 201 | prevBlock: util.revHex(this.prevBlock), 202 | merkleRoot: util.revHex(this.merkleRoot), 203 | time: this.time, 204 | bits: this.bits, 205 | nonce: this.nonce 206 | }; 207 | } 208 | 209 | /** 210 | * Inject properties from json object. 211 | * @private 212 | * @param {Object} json 213 | */ 214 | 215 | fromJSON(json) { 216 | this.parseJSON(json); 217 | return this; 218 | } 219 | 220 | /** 221 | * Instantiate a merkle block from a jsonified block object. 222 | * @param {Object} json - The jsonified block object. 223 | * @returns {Headers} 224 | */ 225 | 226 | static fromJSON(json) { 227 | return new this().fromJSON(json); 228 | } 229 | 230 | /** 231 | * Test an object to see if it is a Headers object. 232 | * @param {Object} obj 233 | * @returns {Boolean} 234 | */ 235 | 236 | static isHeaders(obj) { 237 | return obj instanceof Headers; 238 | } 239 | } 240 | 241 | /* 242 | * Expose 243 | */ 244 | 245 | module.exports = Headers; 246 | -------------------------------------------------------------------------------- /js/clients/lib/vendor/merkle.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * merkle.js - merkle trees for bcrypto 3 | * Copyright (c) 2014-2015, Fedor Indutny (MIT License) 4 | * Copyright (c) 2014-2019, Christopher Jeffrey (MIT License). 5 | * https://github.com/bcoin-org/bcrypto 6 | * 7 | * Parts of this software are based on bitcoin/bitcoin: 8 | * Copyright (c) 2009-2019, The Bitcoin Core Developers (MIT License). 9 | * Copyright (c) 2009-2019, The Bitcoin Developers (MIT License). 10 | * https://github.com/bitcoin/bitcoin 11 | */ 12 | 13 | 14 | const assert = require('./bsert'); 15 | 16 | // Notes about unbalanced merkle trees: 17 | // 18 | // Bitcoin hashes odd nodes with themselves, 19 | // allowing an attacker to add a duplicate 20 | // TXID, creating an even number of leaves 21 | // and computing the same root (CVE-2012-2459). 22 | // In contrast, RFC 6962 simply propagates 23 | // odd nodes up. 24 | // 25 | // RFC 6962: 26 | // 27 | // R 28 | // / \ 29 | // / \ 30 | // / \ 31 | // / \ 32 | // / \ 33 | // k j <-- same as below 34 | // / \ | 35 | // / \ | 36 | // / \ | 37 | // h i j 38 | // / \ / \ / \ 39 | // a b c d e f 40 | // 41 | // Bitcoin Behavior: 42 | // 43 | // R 44 | // / \ 45 | // / \ 46 | // / \ 47 | // / \ 48 | // / \ 49 | // k l <-- HASH(j || j) 50 | // / \ | 51 | // / \ | 52 | // / \ | 53 | // h i j 54 | // / \ / \ / \ 55 | // a b c d e f 56 | // 57 | // This creates a situation where these leaves: 58 | // 59 | // R 60 | // / \ 61 | // / \ 62 | // / \ 63 | // d e <-- HASH(c || c) 64 | // / \ / \ 65 | // a b c c 66 | // 67 | // Compute the same root as: 68 | // 69 | // R 70 | // / \ 71 | // / \ 72 | // d e <-- HASH(c || c) 73 | // / \ | 74 | // a b c 75 | // 76 | // Why does this matter? Duplicate TXIDs are 77 | // invalid right? They're spending the same 78 | // inputs! The problem arises in certain 79 | // implementation optimizations which may 80 | // mark a block hash invalid. In other words, 81 | // an invalid block shares the same block 82 | // hash as a valid one! 83 | // 84 | // See: 85 | // https://tools.ietf.org/html/rfc6962#section-2.1 86 | // https://nvd.nist.gov/vuln/detail/CVE-2012-2459 87 | // https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-2459 88 | // https://bitcointalk.org/?topic=81749 89 | 90 | /** 91 | * Build a merkle tree from leaves. 92 | * @param {Object} alg 93 | * @param {Buffer[]} leaves 94 | * @returns {Array} [nodes, malleated] 95 | */ 96 | 97 | function createTree(alg, leaves) { 98 | assert(alg && typeof alg.root === 'function'); 99 | assert(Array.isArray(leaves)); 100 | 101 | const nodes = new Array(leaves.length); 102 | 103 | for (let i = 0; i < leaves.length; i++) { nodes[i] = leaves[i]; } 104 | 105 | let size = nodes.length; 106 | let malleated = false; 107 | let i = 0; 108 | 109 | if (size === 0) { 110 | nodes.push(alg.zero); 111 | return [nodes, malleated]; 112 | } 113 | 114 | while (size > 1) { 115 | for (let j = 0; j < size; j += 2) { 116 | const k = Math.min(j + 1, size - 1); 117 | const left = nodes[i + j]; 118 | const right = nodes[i + k]; 119 | 120 | if (k === j + 1 && k + 1 === size 121 | && left.equals(right)) { 122 | malleated = true; 123 | } 124 | 125 | const hash = alg.root(left, right); 126 | 127 | nodes.push(hash); 128 | } 129 | 130 | i += size; 131 | 132 | size = (size + 1) >>> 1; 133 | } 134 | 135 | return [nodes, malleated]; 136 | } 137 | 138 | /** 139 | * Calculate merkle root from leaves. 140 | * @param {Object} alg 141 | * @param {Buffer[]} leaves 142 | * @returns {Array} [root, malleated] 143 | */ 144 | 145 | function createRoot(alg, leaves) { 146 | assert(alg && typeof alg.root === 'function'); 147 | assert(Array.isArray(leaves)); 148 | 149 | const [nodes, malleated] = createTree(alg, leaves); 150 | const root = nodes[nodes.length - 1]; 151 | 152 | return [root, malleated]; 153 | } 154 | 155 | /** 156 | * Collect a merkle branch from vector index. 157 | * @param {Object} alg 158 | * @param {Number} index 159 | * @param {Buffer[]} leaves 160 | * @returns {Buffer[]} branch 161 | */ 162 | 163 | function createBranch(alg, index, leaves) { 164 | assert(alg && typeof alg.root === 'function'); 165 | assert((index >>> 0) === index); 166 | assert(Array.isArray(leaves)); 167 | assert(index < leaves.length); 168 | 169 | let size = leaves.length; 170 | 171 | const [nodes] = createTree(alg, leaves); 172 | const branch = []; 173 | 174 | let i = 0; 175 | 176 | while (size > 1) { 177 | const j = Math.min(index ^ 1, size - 1); 178 | 179 | branch.push(nodes[i + j]); 180 | 181 | index >>>= 1; 182 | 183 | i += size; 184 | 185 | size = (size + 1) >>> 1; 186 | } 187 | 188 | return branch; 189 | } 190 | 191 | /** 192 | * Derive merkle root from branch. 193 | * @param {Object} alg 194 | * @param {Buffer} hash 195 | * @param {Buffer[]} branch 196 | * @param {Number} index 197 | * @returns {Buffer} root 198 | */ 199 | 200 | function deriveRoot(alg, hash, branch, index) { 201 | assert(alg && typeof alg.root === 'function'); 202 | assert(Buffer.isBuffer(hash)); 203 | assert(Array.isArray(branch)); 204 | assert((index >>> 0) === index); 205 | 206 | let root = hash; 207 | 208 | for (const hash of branch) { 209 | if ((index & 1) && hash.equals(root)) { return alg.zero; } 210 | 211 | if (index & 1) { root = alg.root(hash, root); } else { root = alg.root(root, hash); } 212 | 213 | index >>>= 1; 214 | } 215 | 216 | return root; 217 | } 218 | 219 | /* 220 | * Expose 221 | */ 222 | 223 | exports.createTree = createTree; 224 | exports.createRoot = createRoot; 225 | exports.createBranch = createBranch; 226 | exports.deriveRoot = deriveRoot; 227 | -------------------------------------------------------------------------------- /js/clients/lib/vendor/scriptnum.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * scriptnum.js - script number object for bcoin. 3 | * Copyright (c) 2017, Christopher Jeffrey (MIT License). 4 | * https://github.com/bcoin-org/bcoin 5 | */ 6 | 7 | 'use strict'; 8 | 9 | const assert = require('./bsert'); 10 | const {I64} = require('./n64'); 11 | 12 | /* 13 | * Constants 14 | */ 15 | 16 | const EMPTY_ARRAY = Buffer.alloc(0); 17 | 18 | /** 19 | * Script Number 20 | * @see https://github.com/chjj/n64 21 | * @alias module:script.ScriptNum 22 | * @property {Number} hi 23 | * @property {Number} lo 24 | * @property {Number} sign 25 | */ 26 | 27 | class ScriptNum extends I64 { 28 | /** 29 | * Create a script number. 30 | * @constructor 31 | * @param {(Number|String|Buffer|Object)?} num 32 | * @param {(String|Number)?} base 33 | */ 34 | 35 | constructor(num, base) { 36 | super(num, base); 37 | } 38 | 39 | /** 40 | * Cast to int32. 41 | * @returns {Number} 42 | */ 43 | 44 | getInt() { 45 | if (this.lt(I64.INT32_MIN)) 46 | return I64.LONG_MIN; 47 | 48 | if (this.gt(I64.INT32_MAX)) 49 | return I64.LONG_MAX; 50 | 51 | return this.toInt(); 52 | } 53 | 54 | /** 55 | * Serialize script number. 56 | * @returns {Buffer} 57 | */ 58 | 59 | toRaw() { 60 | let num = this; 61 | 62 | // Zeroes are always empty arrays. 63 | if (num.isZero()) 64 | return EMPTY_ARRAY; 65 | 66 | // Need to append sign bit. 67 | let neg = false; 68 | if (num.isNeg()) { 69 | num = num.neg(); 70 | neg = true; 71 | } 72 | 73 | // Calculate size. 74 | const size = num.byteLength(); 75 | 76 | let offset = 0; 77 | 78 | if (num.testn((size * 8) - 1)) 79 | offset = 1; 80 | 81 | // Write number. 82 | const data = Buffer.allocUnsafe(size + offset); 83 | 84 | switch (size) { 85 | case 8: 86 | data[7] = (num.hi >>> 24) & 0xff; 87 | case 7: 88 | data[6] = (num.hi >> 16) & 0xff; 89 | case 6: 90 | data[5] = (num.hi >> 8) & 0xff; 91 | case 5: 92 | data[4] = num.hi & 0xff; 93 | case 4: 94 | data[3] = (num.lo >>> 24) & 0xff; 95 | case 3: 96 | data[2] = (num.lo >> 16) & 0xff; 97 | case 2: 98 | data[1] = (num.lo >> 8) & 0xff; 99 | case 1: 100 | data[0] = num.lo & 0xff; 101 | } 102 | 103 | // Append sign bit. 104 | if (data[size - 1] & 0x80) { 105 | assert(offset === 1); 106 | assert(data.length === size + offset); 107 | data[size] = neg ? 0x80 : 0; 108 | } else if (neg) { 109 | assert(offset === 0); 110 | assert(data.length === size); 111 | data[size - 1] |= 0x80; 112 | } else { 113 | assert(offset === 0); 114 | assert(data.length === size); 115 | } 116 | 117 | return data; 118 | } 119 | 120 | /** 121 | * Instantiate script number from serialized data. 122 | * @private 123 | * @param {Buffer} data 124 | * @returns {ScriptNum} 125 | */ 126 | 127 | fromRaw(data) { 128 | assert(Buffer.isBuffer(data)); 129 | 130 | // Empty arrays are always zero. 131 | if (data.length === 0) 132 | return this; 133 | 134 | // Read number (9 bytes max). 135 | switch (data.length) { 136 | case 8: 137 | this.hi |= data[7] << 24; 138 | case 7: 139 | this.hi |= data[6] << 16; 140 | case 6: 141 | this.hi |= data[5] << 8; 142 | case 5: 143 | this.hi |= data[4]; 144 | case 4: 145 | this.lo |= data[3] << 24; 146 | case 3: 147 | this.lo |= data[2] << 16; 148 | case 2: 149 | this.lo |= data[1] << 8; 150 | case 1: 151 | this.lo |= data[0]; 152 | break; 153 | default: 154 | for (let i = 0; i < data.length; i++) 155 | this.orb(i, data[i]); 156 | break; 157 | } 158 | 159 | // Remove high bit and flip sign. 160 | if (data[data.length - 1] & 0x80) { 161 | this.setn((data.length * 8) - 1, 0); 162 | this.ineg(); 163 | } 164 | 165 | return this; 166 | } 167 | 168 | /** 169 | * Serialize script number. 170 | * @returns {Buffer} 171 | */ 172 | 173 | encode() { 174 | return this.toRaw(); 175 | } 176 | 177 | /** 178 | * Decode and verify script number. 179 | * @private 180 | * @param {Buffer} data 181 | * @param {Boolean?} minimal - Require minimal encoding. 182 | * @param {Number?} limit - Size limit. 183 | * @returns {ScriptNum} 184 | */ 185 | 186 | decode(data, minimal, limit) { 187 | assert(Buffer.isBuffer(data)); 188 | 189 | if (limit != null && data.length > limit) 190 | throw new Error('UNKNOWN_ERROR', 'Script number overflow.'); 191 | 192 | if (minimal && !ScriptNum.isMinimal(data)) 193 | throw new Error('UNKNOWN_ERROR', 'Non-minimal script number.'); 194 | 195 | return this.fromRaw(data); 196 | } 197 | 198 | /** 199 | * Test wether a serialized script 200 | * number is in its most minimal form. 201 | * @param {Buffer} data 202 | * @returns {Boolean} 203 | */ 204 | 205 | static isMinimal(data) { 206 | assert(Buffer.isBuffer(data)); 207 | 208 | if (data.length === 0) 209 | return true; 210 | 211 | if ((data[data.length - 1] & 0x7f) === 0) { 212 | if (data.length === 1) 213 | return false; 214 | 215 | if ((data[data.length - 2] & 0x80) === 0) 216 | return false; 217 | } 218 | 219 | return true; 220 | } 221 | 222 | /** 223 | * Decode and verify script number. 224 | * @param {Buffer} data 225 | * @param {Boolean?} minimal - Require minimal encoding. 226 | * @param {Number?} limit - Size limit. 227 | * @returns {ScriptNum} 228 | */ 229 | 230 | static decode(data, minimal, limit) { 231 | return new this().decode(data, minimal, limit); 232 | } 233 | 234 | /** 235 | * Test whether object is a script number. 236 | * @param {Object} obj 237 | * @returns {Boolean} 238 | */ 239 | 240 | static isScriptNum(obj) { 241 | return obj instanceof ScriptNum; 242 | } 243 | } 244 | 245 | /* 246 | * Expose 247 | */ 248 | 249 | module.exports = ScriptNum; 250 | -------------------------------------------------------------------------------- /js/clients/lib/vendor/util.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * util.js - utils for bcoin 3 | * Copyright (c) 2014-2015, Fedor Indutny (MIT License) 4 | * Copyright (c) 2014-2017, Christopher Jeffrey (MIT License). 5 | * https://github.com/bcoin-org/bcoin 6 | */ 7 | 8 | 'use strict'; 9 | 10 | const assert = require('./bsert'); 11 | 12 | /** 13 | * @exports utils/util 14 | */ 15 | 16 | const util = exports; 17 | 18 | /** 19 | * Return hrtime (shim for browser). 20 | * @param {Array} time 21 | * @returns {Array} [seconds, nanoseconds] 22 | */ 23 | 24 | util.bench = function bench(time) { 25 | if (!process.hrtime) { 26 | const now = Date.now(); 27 | 28 | if (time) { 29 | const [hi, lo] = time; 30 | const start = hi * 1000 + lo / 1e6; 31 | return now - start; 32 | } 33 | 34 | const ms = now % 1000; 35 | 36 | // Seconds 37 | const hi = (now - ms) / 1000; 38 | 39 | // Nanoseconds 40 | const lo = ms * 1e6; 41 | 42 | return [hi, lo]; 43 | } 44 | 45 | if (time) { 46 | const [hi, lo] = process.hrtime(time); 47 | return hi * 1000 + lo / 1e6; 48 | } 49 | 50 | return process.hrtime(); 51 | }; 52 | 53 | /** 54 | * Get current time in unix time (seconds). 55 | * @returns {Number} 56 | */ 57 | 58 | util.now = function now() { 59 | return Math.floor(Date.now() / 1000); 60 | }; 61 | 62 | /** 63 | * Get current time in unix time (milliseconds). 64 | * @returns {Number} 65 | */ 66 | 67 | util.ms = function ms() { 68 | return Date.now(); 69 | }; 70 | 71 | /** 72 | * Create a Date ISO string from time in unix time (seconds). 73 | * @param {Number?} time - Seconds in unix time. 74 | * @returns {String} 75 | */ 76 | 77 | util.date = function date(time) { 78 | if (time == null) 79 | time = util.now(); 80 | 81 | return new Date(time * 1000).toISOString().slice(0, -5) + 'Z'; 82 | }; 83 | 84 | /** 85 | * Get unix seconds from a Date string. 86 | * @param {String?} date - Date ISO String. 87 | * @returns {Number} 88 | */ 89 | 90 | util.time = function time(date) { 91 | if (date == null) 92 | return util.now(); 93 | 94 | return new Date(date) / 1000 | 0; 95 | }; 96 | 97 | /** 98 | * Reverse a hex-string. 99 | * @param {Buffer} 100 | * @returns {String} Reversed hex string. 101 | */ 102 | 103 | util.revHex = function revHex(buf) { 104 | assert(Buffer.isBuffer(buf)); 105 | 106 | return Buffer.from(buf).reverse().toString('hex'); 107 | }; 108 | 109 | util.fromRev = function fromRev(str) { 110 | assert(typeof str === 'string'); 111 | assert((str.length & 1) === 0); 112 | 113 | return Buffer.from(str, 'hex').reverse(); 114 | }; 115 | -------------------------------------------------------------------------------- /js/clients/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@summa-tx/bitcoin-spv-js-clients", 3 | "version": "0.1.4", 4 | "description": "Clients for fetching bitcoin SPV proofs in Javascript", 5 | "main": "lib/index.js", 6 | "engines": { 7 | "node": ">10.0.0" 8 | }, 9 | "engineStrict": true, 10 | "scripts": { 11 | "lint": "eslint ./test ./lib", 12 | "lint:fix": "eslint --fix ./test ./lib", 13 | "test": "mocha test -r esm", 14 | "test:coverage": "jest --coverage" 15 | }, 16 | "keywords": [ 17 | "bitcoin", 18 | "verification", 19 | "cryptocurrency", 20 | "utilities" 21 | ], 22 | "author": "Summa", 23 | "contributors": [ 24 | "Mark Tynes ", 25 | "Barbara Liau " 26 | ], 27 | "license": "(MIT OR Apache-2.0)", 28 | "homepage": "https://github.com/summa-tx/bitcoin-spv/js/clients/", 29 | "devDependencies": { 30 | "chai": "^4.2.0", 31 | "eslint": "5.16.0", 32 | "eslint-config-airbnb-base": "13.2.0", 33 | "eslint-plugin-import": "2.18.2", 34 | "esm": "3.2.25", 35 | "jest": "^24.8.0", 36 | "mocha": "6.2.0" 37 | }, 38 | "dependencies": { 39 | "@summa-tx/bitcoin-spv-js": "^4.0.2", 40 | "bsert": "0.0.10" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /js/clients/test/BcoinClient-test.js: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai'; 2 | const { assert } = chai; 3 | 4 | const BcoinClient = require('../lib/BcoinClient'); 5 | 6 | describe('BcoinClient', () => { 7 | it('needs tests. this is a placeholder', () => { 8 | const client = new BcoinClient({}); 9 | 10 | assert.strictEqual(typeof client, 'object'); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@summa-tx/bitcoin-spv-js", 3 | "version": "4.0.1", 4 | "description": "bitcoin SPV proofs in Javascript", 5 | "main": "dist/index.js", 6 | "engines": { 7 | "node": ">10.0.0" 8 | }, 9 | "engineStrict": true, 10 | "scripts": { 11 | "lint": "eslint ./test ./src", 12 | "lint:fix": "eslint --fix ./test ./src", 13 | "test": "mocha test -r esm", 14 | "test:coverage": "jest --coverage", 15 | "build": "babel src -d dist", 16 | "prepublishOnly": "npm run build" 17 | }, 18 | "keywords": [ 19 | "bitcoin", 20 | "verification", 21 | "cryptocurrency", 22 | "utilities" 23 | ], 24 | "author": "Summa", 25 | "contributors": [ 26 | "Erin Hales ", 27 | "Dominique Liau ", 28 | "James Prestwich " 29 | ], 30 | "license": "(MIT OR Apache-2.0)", 31 | "homepage": "https://github.com/summa-tx/bitcoin-spv/tree/master/js#readme", 32 | "devDependencies": { 33 | "@babel/cli": "^7.6.0", 34 | "@babel/core": "^7.6.0", 35 | "@babel/preset-env": "^7.5.5", 36 | "babel-jest": "^24.8.0", 37 | "chai": "^4.2.0", 38 | "eslint": "^5.16.0", 39 | "eslint-config-airbnb-base": "^13.2.0", 40 | "eslint-plugin-import": "^2.18.0", 41 | "esm": "^3.2.25", 42 | "jest": "^24.8.0", 43 | "mocha": "^6.2.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /js/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @file Part of the [bitcoin-spv]{@link https://github.com/summa-tx/bitcoin-spv} project 4 | * 5 | * @title index.js 6 | * @summary Export validate tools 7 | * @author James Prestwich 8 | * @author Erin Hales 9 | * @author Dominique Liau 10 | * @copyright (c) [Summa]{@link https://summa.one} 2019 11 | * 12 | */ 13 | 14 | import * as BTCUtils from './BTCUtils'; 15 | import * as utils from './utils'; 16 | import * as ValidateSPV from './ValidateSPV'; 17 | import * as ser from './ser'; 18 | import * as sighash from './sighash'; 19 | 20 | export { 21 | BTCUtils, 22 | utils, 23 | ValidateSPV, 24 | ser, 25 | sighash 26 | }; 27 | -------------------------------------------------------------------------------- /js/src/ser.js: -------------------------------------------------------------------------------- 1 | import * as utils from './utils'; 2 | 3 | /** 4 | * 5 | * Takes a Header deserialized from JSON and returns a new object representing the Header 6 | * with deserialized Uint8Array in place of serialzed hex strings 7 | * 8 | * @param {Object} o The Header as an unprcessed object, immediately after derserialization 9 | * @returns {Object} The Header with deserialized byte arrays 10 | */ 11 | export function objectToHeader(o) { 12 | /* eslint-disable camelcase */ 13 | const raw = utils.deserializeHex(o.raw); 14 | const hash = utils.deserializeHex(o.hash); 15 | const prevhash = utils.deserializeHex(o.prevhash); 16 | const merkle_root = utils.deserializeHex(o.merkle_root); 17 | if (raw.length !== 80) { 18 | throw new TypeError(`Expected 80 bytes, got ${raw.length} bytes`); 19 | } 20 | [hash, prevhash, merkle_root].forEach((e) => { 21 | if (e.length !== 32) { 22 | throw new TypeError(`Expected 32 bytes, got ${e.length} bytes`); 23 | } 24 | }); 25 | /* eslint-enable camelcase */ 26 | 27 | return { 28 | raw, 29 | hash, 30 | height: o.height, 31 | prevhash, 32 | merkle_root, 33 | }; 34 | } 35 | 36 | /** 37 | * 38 | * Deserializes a Header object from a JSON string 39 | * 40 | * @param {string} s The Header serialized as a JSON string 41 | * @returns {Object} The Header with deserialized byte arrays 42 | */ 43 | export function deserializeHeader(s) { 44 | return objectToHeader(JSON.parse(s)); 45 | } 46 | 47 | 48 | /** 49 | * 50 | * Takes a Header and serialized each byte array. The result is suitable for JSON serialization 51 | * 52 | * @param {Object} o The Header with deserialized byte arrays 53 | * @returns {Object} The Header with byte arrays serialized as hex, suitable for serialization 54 | */ 55 | export function objectFromHeader(h) { 56 | return { 57 | raw: utils.serializeHex(h.raw), 58 | hash: utils.serializeHex(h.hash), 59 | height: h.height, 60 | prevhash: utils.serializeHex(h.prevhash), 61 | merkle_root: utils.serializeHex(h.merkle_root), 62 | }; 63 | } 64 | 65 | /** 66 | * 67 | * Serializes a Header object to a JSON string 68 | * 69 | * @param {Object} s The Header with deserialized byte arrays 70 | * @returns {string} The Header serialized as a JSON string 71 | */ 72 | export function serializeHeader(h) { 73 | return JSON.stringify(objectFromHeader(h)); 74 | } 75 | 76 | /** 77 | * 78 | * Takes a proof deserialized from JSON and returns a new object representing the proof 79 | * with deserialized Uint8Array in place of serialzed hex strings 80 | * 81 | * @param {Object} o The proof as an unprcessed object, immediately after derserialization 82 | * @returns {Object} The proof with deserialized byte arrays 83 | */ 84 | export function objectToSPVProof(o) { 85 | /* eslint-disable camelcase */ 86 | const version = utils.deserializeHex(o.version); 87 | const vin = utils.deserializeHex(o.vin); 88 | const vout = utils.deserializeHex(o.vout); 89 | const locktime = utils.deserializeHex(o.locktime); 90 | const tx_id = utils.deserializeHex(o.tx_id); 91 | const intermediate_nodes = utils.deserializeHex(o.intermediate_nodes); 92 | 93 | if (tx_id.length !== 32) { 94 | throw new TypeError(`Expected 32 bytes, got ${tx_id.length} bytes`); 95 | } 96 | 97 | return { 98 | version, 99 | vin, 100 | vout, 101 | locktime, 102 | tx_id, 103 | index: o.index, 104 | intermediate_nodes, 105 | confirming_header: objectToHeader(o.confirming_header) 106 | }; 107 | /* eslint-enable camelcase */ 108 | } 109 | 110 | /** 111 | * 112 | * Deserializes a SPVProof object from a JSON string 113 | * 114 | * @param {string} s The SPVProof serialized as a JSON string 115 | * @returns {Object} The SPVProof with deserialized byte arrays 116 | */ 117 | export function deserializeSPVProof(s) { 118 | return objectToSPVProof(JSON.parse(s)); 119 | } 120 | 121 | /** 122 | * 123 | * Takes a SPVProof and serialized each byte array. The result is suitable for JSON serialization 124 | * 125 | * @param {Object} o The SPVProof with deserialized byte arrays 126 | * @returns {Object} Object byte arrays serialized as hex, suitable for serialization as JSON 127 | */ 128 | export function objectFromSPVProof(s) { 129 | return { 130 | version: utils.serializeHex(s.version), 131 | vin: utils.serializeHex(s.vin), 132 | vout: utils.serializeHex(s.vout), 133 | locktime: utils.serializeHex(s.locktime), 134 | tx_id: utils.serializeHex(s.tx_id), 135 | index: s.index, 136 | intermediate_nodes: utils.serializeHex(s.intermediate_nodes), 137 | confirming_header: objectFromHeader(s.confirming_header) 138 | }; 139 | } 140 | 141 | /** 142 | * 143 | * Serializes a SPVProof object to a JSON string 144 | * 145 | * @param {Object} s The SPVProof with deserialized byte arrays 146 | * @returns {string} The SPVProof serialized as a JSON string 147 | */ 148 | export function serializeSPVProof(s) { 149 | return JSON.stringify(objectFromSPVProof(s)); 150 | } 151 | -------------------------------------------------------------------------------- /js/test/ValidateSPV.test.js: -------------------------------------------------------------------------------- 1 | /* global describe it BigInt */ 2 | import * as chai from 'chai'; 3 | import * as utils from '../src/utils'; 4 | import * as ser from '../src/ser'; 5 | import * as ValidateSPV from '../src/ValidateSPV'; 6 | import * as vectors from '../../testVectors.json'; 7 | import * as testProofs from '../../testProofs.json'; 8 | 9 | const { assert } = chai; 10 | 11 | const vectorObj = JSON.parse(JSON.stringify(vectors)); 12 | utils.updateJSON(vectorObj); 13 | 14 | const { 15 | prove, 16 | calculateTxId, 17 | validateHeaderChain, 18 | validateHeaderChainError, 19 | validateHeaderWork, 20 | validateHeaderPrevHash 21 | } = vectorObj; 22 | 23 | const testProofsObj = JSON.parse(JSON.stringify(testProofs)); 24 | utils.updateJSON(testProofsObj); 25 | 26 | const { 27 | valid, 28 | badHeaders, 29 | badSPVProofs 30 | } = testProofsObj; 31 | const validProof = ser.deserializeSPVProof(valid); 32 | 33 | describe('ValidateSPV', () => { 34 | describe('#prove', () => { 35 | it('returns true if proof is valid, false if otherwise', () => { 36 | for (let i = 0; i < prove.length; i += 1) { 37 | const { 38 | txIdLE, merkleRootLE, proof, index 39 | } = prove[i].input; 40 | 41 | const res = ValidateSPV.prove(txIdLE, merkleRootLE, proof, index); 42 | assert.strictEqual(res, prove[i].output); 43 | } 44 | }); 45 | }); 46 | 47 | describe('#calculateTxId', () => { 48 | it('returns the transaction hash', () => { 49 | for (let i = 0; i < calculateTxId.length; i += 1) { 50 | const { 51 | version, vin, vout, locktime 52 | } = calculateTxId[i].input; 53 | 54 | const res = ValidateSPV.calculateTxId(version, vin, vout, locktime); 55 | const arraysAreEqual = utils.typedArraysAreEqual(res, calculateTxId[i].output); 56 | assert.isTrue(arraysAreEqual); 57 | } 58 | }); 59 | }); 60 | 61 | describe('#validateHeaderChain', () => { 62 | it('returns true if header chain is valid', () => { 63 | for (let i = 0; i < validateHeaderChain.length; i += 1) { 64 | const res = ValidateSPV.validateHeaderChain(validateHeaderChain[i].input); 65 | assert.strictEqual(res, BigInt(validateHeaderChain[i].output)); 66 | } 67 | }); 68 | 69 | it('throws error if header chain is not valid', () => { 70 | for (let i = 0; i < validateHeaderChainError.length; i += 1) { 71 | try { 72 | ValidateSPV.validateHeaderChain(validateHeaderChainError[i].input); 73 | assert(false, 'expected an error'); 74 | } catch (e) { 75 | assert.include(e.message, validateHeaderChainError[i].jsError); 76 | } 77 | } 78 | }); 79 | }); 80 | 81 | describe('#validateHeaderWork', () => { 82 | it('returns true if the digest has sufficient work, false if otherwise', () => { 83 | for (let i = 0; i < validateHeaderWork.length; i += 1) { 84 | let t; 85 | if (typeof validateHeaderWork[i].target === 'number') { 86 | t = BigInt(validateHeaderWork[i].target); 87 | } else { 88 | t = utils.bytesToUint(validateHeaderWork[i].input.target); 89 | } 90 | 91 | const res = ValidateSPV.validateHeaderWork(validateHeaderWork[i].input.digest, t); 92 | assert.strictEqual(res, validateHeaderWork[i].output); 93 | } 94 | }); 95 | }); 96 | 97 | describe('#validateHeaderPrevHash', () => { 98 | it('returns true if header prevHash is valid, false if otherwise', () => { 99 | for (let i = 0; i < validateHeaderPrevHash.length; i += 1) { 100 | const res = ValidateSPV.validateHeaderPrevHashLE( 101 | validateHeaderPrevHash[i].input.header, 102 | validateHeaderPrevHash[i].input.prevHash 103 | ); 104 | assert.strictEqual(res, validateHeaderPrevHash[i].output); 105 | } 106 | }); 107 | }); 108 | 109 | describe('#validateHeader', () => { 110 | it('returns true if the header object is syntactically valid', () => { 111 | const res = ValidateSPV.validateHeader(validProof.confirming_header); 112 | assert.strictEqual(res, true); 113 | }); 114 | 115 | it('throws error if any element of the header is invalid', () => { 116 | for (let i = 0; i < badHeaders.length; i += 1) { 117 | try { 118 | ValidateSPV.validateHeader(badHeaders[i].header); 119 | assert(false, 'expected an error'); 120 | } catch (e) { 121 | assert.include(e.message, badHeaders[i].e); 122 | } 123 | } 124 | }); 125 | }); 126 | 127 | describe('#validateProof', () => { 128 | it('returns true if the SPV Proof object is syntactically valid', () => { 129 | const res = ValidateSPV.validateProof(validProof); 130 | assert.isTrue(res); 131 | }); 132 | 133 | it('throws error if any element in the SPV Proof is invalid', () => { 134 | for (let i = 0; i < badSPVProofs.length; i += 1) { 135 | try { 136 | ValidateSPV.validateProof(badSPVProofs[i].proof); 137 | assert(false, 'expected an error'); 138 | } catch (e) { 139 | assert.include(e.message, badSPVProofs[i].e); 140 | } 141 | } 142 | }); 143 | }); 144 | }); 145 | -------------------------------------------------------------------------------- /js/test/ser.test.js: -------------------------------------------------------------------------------- 1 | /* global describe it */ 2 | 3 | import * as chai from 'chai'; 4 | import * as ser from '../src/ser'; 5 | import * as utils from '../src/utils'; 6 | import * as vectors from '../../testProofs.json'; 7 | 8 | const vectorObj = JSON.parse(JSON.stringify(vectors)); 9 | const { 10 | valid, 11 | validHeader, 12 | errBadHexBytes, 13 | errBadHexHash256, 14 | errBadLenHash256, 15 | errBadHexRawHeader, 16 | errBadLenRawHeader, 17 | errBadLenHash 18 | } = vectorObj; 19 | 20 | const { assert } = chai; 21 | 22 | describe('ser', () => { 23 | it('can round-trip serialize SPV Proof', () => { 24 | const emptyDigest = new Uint8Array(32); 25 | valid.forEach((e) => { 26 | const proof = ser.deserializeSPVProof(e); 27 | 28 | // TODO: make more assertions and clean up this section 29 | assert.equal(proof.tx_id.length, 32); 30 | assert.isFalse(utils.typedArraysAreEqual(proof.tx_id, emptyDigest)); 31 | 32 | // re-serialize and re-deserialize 33 | const jsonProofString = ser.serializeSPVProof(proof); 34 | const secondProof = ser.deserializeSPVProof(jsonProofString); 35 | 36 | // TODO: make more assertions and clean up this section 37 | assert.isTrue(utils.typedArraysAreEqual( 38 | proof.intermediate_nodes, 39 | secondProof.intermediate_nodes 40 | )); 41 | assert.isTrue(utils.typedArraysAreEqual( 42 | proof.tx_id, 43 | secondProof.tx_id 44 | )); 45 | }); 46 | }); 47 | 48 | it('can round-trip serialize Bitcoin header', () => { 49 | validHeader.forEach((e) => { 50 | const header = ser.deserializeHeader(e); 51 | 52 | // length assertions 53 | assert.equal(header.raw.length, 80); 54 | assert.isFalse(utils.typedArraysAreEqual(header.hash, new Uint8Array(80))); 55 | 56 | const len32 = [header.hash, header.prevhash, header.merkle_root]; 57 | len32.forEach((f) => { 58 | assert.equal(f.length, 32); 59 | assert.isFalse(utils.typedArraysAreEqual(f, new Uint8Array(32))); 60 | }); 61 | 62 | // re-serialize and re-deserialize 63 | const jsonHeaderString = ser.serializeHeader(header); 64 | const secondHeader = ser.deserializeHeader(jsonHeaderString); 65 | 66 | // round-trip equality assertions 67 | Object.keys(header).forEach((key) => { 68 | if (header[key] instanceof Uint8Array) { 69 | assert.isTrue(utils.typedArraysAreEqual( 70 | header[key], 71 | secondHeader[key] 72 | )); 73 | } else { 74 | assert.equal( 75 | header[key], 76 | secondHeader[key] 77 | ); 78 | } 79 | }); 80 | }); 81 | }); 82 | 83 | it('errBadHexBytes', () => { 84 | try { 85 | ser.deserializeSPVProof(errBadHexBytes); 86 | assert(false, 'expected an error'); 87 | } catch (e) { 88 | assert.include(e.message, 'Error deserializing hex, got non-hex byte:'); 89 | } 90 | }); 91 | 92 | it('errBadHexHash256', () => { 93 | try { 94 | ser.deserializeSPVProof(errBadHexHash256); 95 | assert(false, 'expected an error'); 96 | } catch (e) { 97 | assert.include(e.message, 'Error deserializing hex, got non-hex byte:'); 98 | } 99 | }); 100 | 101 | it('errBadLenHash256', () => { 102 | try { 103 | ser.deserializeSPVProof(errBadLenHash256); 104 | assert(false, 'expected an error'); 105 | } catch (e) { 106 | assert.include(e.message, 'Expected 32 bytes, got 31 bytes'); 107 | } 108 | }); 109 | 110 | it('errBadHexRawHeader', () => { 111 | try { 112 | ser.deserializeSPVProof(errBadHexRawHeader); 113 | assert(false, 'expected an error'); 114 | } catch (e) { 115 | assert.include(e.message, 'Error deserializing hex, got non-hex byte:'); 116 | } 117 | }); 118 | 119 | it('errBadLenRawHeader', () => { 120 | try { 121 | ser.deserializeSPVProof(errBadLenRawHeader); 122 | assert(false, 'expected an error'); 123 | } catch (e) { 124 | assert.include(e.message, 'Expected 80 bytes, got 79 bytes'); 125 | } 126 | }); 127 | 128 | it('errBadLenHash', () => { 129 | try { 130 | ser.deserializeSPVProof(errBadLenHash); 131 | assert(false, 'expected an error'); 132 | } catch (e) { 133 | assert.include(e.message, 'Expected 32 bytes, got 31 bytes'); 134 | } 135 | }); 136 | }); 137 | -------------------------------------------------------------------------------- /js/test/sighash.test.js: -------------------------------------------------------------------------------- 1 | /* globals it describe */ 2 | 3 | import * as chai from 'chai'; 4 | import * as utils from '../src/utils'; 5 | import * as sighash from '../src/sighash'; 6 | 7 | const { assert } = chai; 8 | 9 | describe('sighash functions', () => { 10 | it('validates the sighash flag', () => { 11 | assert.isTrue(sighash.validateFlag(0x01)); 12 | assert.isTrue(sighash.validateFlag(0x81)); 13 | assert.isTrue(sighash.validateFlag(0x03)); 14 | assert.isTrue(sighash.validateFlag(0x83)); 15 | for (let i = 4; i < 0x81; i += 1) { 16 | assert.isFalse(sighash.validateFlag(i)); 17 | } 18 | }); 19 | 20 | it('does hashOutputs', () => { 21 | assert(utils.typedArraysAreEqual(sighash.hashOutputs([], 0x02), sighash.NULL_HASH)); 22 | }); 23 | 24 | it('finds possible absolute locks', () => { 25 | const blankInput = new Uint8Array(36 + 1 + 4); 26 | assert.isTrue(sighash.possibleAbsoluteLock([], [], 0x80)); 27 | assert.isFalse(sighash.possibleAbsoluteLock([], [], 0x01)); 28 | assert.isTrue(sighash.possibleAbsoluteLock( 29 | [blankInput], 30 | sighash.U32_MAX, 31 | 0x01 32 | )); 33 | assert.isTrue(sighash.possibleAbsoluteLock( 34 | [blankInput], 35 | new Uint8Array([0x80, 0xf0, 0xfa, 0x02]), 36 | 0x01 37 | )); 38 | assert.isFalse(sighash.possibleAbsoluteLock( 39 | [blankInput], 40 | new Uint8Array([0x80, 0xf0, 0, 0]), 41 | 0x01 42 | )); 43 | }); 44 | 45 | it('finds possible relative locks', () => { 46 | const blankInput = new Uint8Array(36 + 1 + 4); 47 | const withMaxSeq = utils.concatUint8Arrays( 48 | new Uint8Array(36 + 1), 49 | sighash.U32_MAX 50 | ); 51 | assert.isFalse(sighash.possibleRelativeLock([], [1])); 52 | assert.isTrue(sighash.possibleRelativeLock([blankInput], [2])); 53 | assert.isFalse(sighash.possibleRelativeLock([withMaxSeq], [2])); 54 | }); 55 | 56 | describe('sighash', () => { 57 | it('errors on invalid flags', () => { 58 | try { 59 | sighash.sighash({}, 0, 0xff); 60 | assert(false, 'expected an error'); 61 | } catch (e) { 62 | assert.include(e.message, 'Invalid sighash flag'); 63 | } 64 | }); 65 | 66 | it('errors on invalid vin', () => { 67 | try { 68 | sighash.sighash({ vin: new Uint8Array(3) }, 0, 0x01); 69 | assert(false, 'expected an error'); 70 | } catch (e) { 71 | assert.include(e.message, 'Malformatted vin'); 72 | } 73 | }); 74 | 75 | it('errors on invalid vout', () => { 76 | const blankVin = utils.concatUint8Arrays( 77 | new Uint8Array([0x01]), 78 | new Uint8Array(36 + 1 + 4) 79 | ); 80 | try { 81 | sighash.sighash({ 82 | vin: blankVin, 83 | vout: new Uint8Array(3) 84 | }, 85 | 0, 86 | 0x01); 87 | assert(false, 'expected an error'); 88 | } catch (e) { 89 | assert.include(e.message, 'Malformatted vout'); 90 | } 91 | }); 92 | }); 93 | 94 | describe('deserAndSighash', () => { 95 | it('calculates BIP143 sighash digests', () => { 96 | const tx = { 97 | version: '0x02000000', 98 | vin: '0x02ee9242c89e79ab2aa537408839329895392b97505b3496d5543d6d2f531b94d20000000000fdffffffee9242c89e79ab2aa537408839329895392b97505b3496d5543d6d2f531b94d20000000000fdffffff', 99 | vout: '0x0273d301000000000017a914bba5acbec4e6e3374a0345bf3609fa7cfea825f18773d301000000000017a914bba5acbec4e6e3374a0345bf3609fa7cfea825f187', 100 | locktime: '0xcafd0700' 101 | }; 102 | const prevoutScript = '0x160014758ce550380d964051086798d6546bebdca27a73'; 103 | const prevoutValue = '0xc0d4010000000000'; 104 | 105 | const expectedAll = '0x75385c87ece4980b581cfd71bc5814f607801a87f6e0973c63dc9fda465c19c4'; 106 | const ALL = sighash.rpcSighash({ 107 | tx, index: 1, sighashFlag: 0x01, prevoutScript, prevoutValue 108 | }); 109 | assert.strictEqual(ALL.digest, expectedAll); 110 | 111 | const expectedAllACP = '0xbc55c4303c82cdcc8e290c597a00d662ab34414d79ec15d63912b8be7fe2ca3c'; 112 | const ALL_ACP = sighash.rpcSighash({ 113 | tx, index: 1, sighashFlag: 0x81, prevoutScript, prevoutValue 114 | }); 115 | assert.strictEqual(ALL_ACP.digest, expectedAllACP); 116 | 117 | const expectedSingle = '0x9d57bf7af01a4e0baa57e749aa193d37a64e3bbc08eb88af93944f41af8dfc70'; 118 | const SINGLE = sighash.rpcSighash({ 119 | tx, index: 1, sighashFlag: 0x03, prevoutScript, prevoutValue 120 | }); 121 | assert.strictEqual(SINGLE.digest, expectedSingle); 122 | 123 | const expectedSingleACP = '0xffea9cdda07170af9bc9967cedf485e9fe15b78a622e0c196c0b6fc64f40c615'; 124 | const SINGLE_ACP = sighash.rpcSighash({ 125 | tx, index: 1, sighashFlag: 0x83, prevoutScript, prevoutValue 126 | }); 127 | assert.strictEqual(SINGLE_ACP.digest, expectedSingleACP); 128 | }); 129 | 130 | it('does another set of bip143 sighash digests', () => { 131 | const tx = { 132 | version: '0x02000000', 133 | vin: '0x01ee9242c89e79ab2aa537408839329895392b97505b3496d5543d6d2f531b94d20000000000fdffffff', 134 | vout: '0x0173d301000000000017a914bba5acbec4e6e3374a0345bf3609fa7cfea825f187', 135 | locktime: '0xcafd0700' 136 | }; 137 | const prevoutScript = '0x160014758ce550380d964051086798d6546bebdca27a73'; 138 | const prevoutValue = '0xc0d4010000000000'; 139 | 140 | const expectedAll = '0x135754ab872e4943f7a9c30d6143c4c7187e33d0f63c75ec82a7f9a15e2f2d00'; 141 | const ALL = sighash.rpcSighash({ 142 | tx, index: 0, sighashFlag: 0x01, prevoutScript, prevoutValue 143 | }); 144 | assert.strictEqual(ALL.digest, expectedAll); 145 | 146 | const expectedAllACP = '0xcc7438d5b15e93ba612dcd227cf1937c35273675b3aa7d1b771573667376ddf6'; 147 | const ALL_ACP = sighash.rpcSighash({ 148 | tx, index: 0, sighashFlag: 0x81, prevoutScript, prevoutValue 149 | }); 150 | assert.strictEqual(ALL_ACP.digest, expectedAllACP); 151 | 152 | const expectedSingle = '0xd04631d2742e6fd8e80e2e4309dece65becca41d37fd6bc0bcba041c52d824d5'; 153 | const SINGLE = sighash.rpcSighash({ 154 | tx, index: 0, sighashFlag: 0x03, prevoutScript, prevoutValue 155 | }); 156 | assert.strictEqual(SINGLE.digest, expectedSingle); 157 | 158 | const expectedSingleACP = '0xffea9cdda07170af9bc9967cedf485e9fe15b78a622e0c196c0b6fc64f40c615'; 159 | const SINGLE_ACP = sighash.rpcSighash({ 160 | tx, index: 0, sighashFlag: 0x83, prevoutScript, prevoutValue 161 | }); 162 | assert.strictEqual(SINGLE_ACP.digest, expectedSingleACP); 163 | }); 164 | }); 165 | }); 166 | -------------------------------------------------------------------------------- /logo-group.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summa-tx/bitcoin-spv/fb2a61e7a941d421ae833789d97ed10d2ad79cfe/logo-group.jpg -------------------------------------------------------------------------------- /logo-summa-ccg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summa-tx/bitcoin-spv/fb2a61e7a941d421ae833789d97ed10d2ad79cfe/logo-summa-ccg.jpg -------------------------------------------------------------------------------- /python/.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = btcspv/ 3 | omit = 4 | btcspv/tests/* 5 | 6 | [report] 7 | show_missing = True 8 | sort = Miss 9 | 10 | [html] 11 | directory = coverage/html/ 12 | -------------------------------------------------------------------------------- /python/.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | .pytest_cache 3 | .coverage 4 | btcspv.egg-info/ 5 | bitcoin_spv_py.egg-info/ 6 | dist/ 7 | -------------------------------------------------------------------------------- /python/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [scripts] 7 | test = "./scripts/run_tests.sh" 8 | 9 | [dev-packages] 10 | mypy = "*" 11 | pytest = "*" 12 | flake8 = "*" 13 | coverage = "*" 14 | pytest-cov = "*" 15 | 16 | [packages] 17 | bitcoin-spv-py = {editable = true,path = "."} 18 | riemann-tx = "==2.1.0" 19 | 20 | [requires] 21 | python_version = "3.7" 22 | -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | ## Bitcoin SPV Proofs in Python 2 | 3 | ### What is it? 4 | 5 | `bitcoin-spv` provides utilities for working with Bitcoin SPV proofs from other 6 | chains. No chain currently has Python smart contract support, although Tezos's 7 | two Pythonic languages (Pyligo and SmartPy) are approaching 8 | production-readiness. In the meantime, this Python package provides 2 standard 9 | data-structures `RelayHeader` and `SPVProof` and (de)serialization methods that 10 | are compatible with our Golang, JS, and Solidity implementations. 11 | 12 | ### IMPORTANT WARNING 13 | 14 | It is extremely easy to write insecure code using these libraries. We do not 15 | recommend a specific security model. Any SPV verification involves complex 16 | security assumptions. Please seek external review for your design before 17 | building with these libraries. 18 | 19 | ### Development Setup 20 | 21 | We use `pyenv` and `pipenv` for environment management. 22 | 23 | ```sh 24 | pipenv install -d 25 | pipenv run test 26 | ``` 27 | 28 | ### Usage Example 29 | 30 | ```Python 31 | import json 32 | 33 | from btcspv import ser 34 | 35 | p = open('../testProofs.json') 36 | proof_vectors = json.loads(p.read()) 37 | 38 | valid_proofs = [ 39 | ser.deserialize_spv_proof(p) for p in self.proof_vectors['valid'] 40 | ] 41 | ``` 42 | 43 | ## Supported by 44 | 45 | ![Summa, Cross Chain Group](../logo-summa-ccg.jpg) 46 | 47 | - [Summa](https://summa.one) 48 | - [Cross Chain Group](https://crosschain.group/) 49 | 50 | ------- 51 | -------------------------------------------------------------------------------- /python/btcspv/__init__.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | 3 | from btcspv.ser import * 4 | from btcspv.types import * 5 | -------------------------------------------------------------------------------- /python/btcspv/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summa-tx/bitcoin-spv/fb2a61e7a941d421ae833789d97ed10d2ad79cfe/python/btcspv/py.typed -------------------------------------------------------------------------------- /python/btcspv/ser.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from riemann import tx 4 | 5 | from btcspv.types import RelayHeader, SPVProof 6 | 7 | 8 | def hex_deser(h: str) -> bytes: 9 | body = h 10 | if h[:2] == '0x': 11 | body = h[2:] 12 | return bytes.fromhex(body) 13 | 14 | 15 | def hex_ser(b: bytes) -> str: 16 | return f'0x{b.hex()}' 17 | 18 | 19 | def dict_from_relay_header(r: RelayHeader) -> dict: 20 | ''' 21 | Args: 22 | r (RelayHeader): The RelayHeader to be serialized 23 | Returns: 24 | (dict): A dictionary representing the RelayHeader with serialized keys 25 | ''' 26 | return { 27 | 'raw': hex_ser(r['raw']), 28 | 'hash': hex_ser(r['hash']), 29 | 'height': r['height'], 30 | 'prevhash': hex_ser(r['prevhash']), 31 | 'merkle_root': hex_ser(r['merkle_root']), 32 | } 33 | 34 | 35 | def serialize_relay_header(r: RelayHeader) -> str: 36 | ''' 37 | Args: 38 | r (RelayHeader): The RelayHeader to be serialized 39 | Returns: 40 | (str): A JSON-serialized RelayHeader 41 | ''' 42 | return json.dumps(dict_from_relay_header(r)) 43 | 44 | 45 | def dict_to_relay_header(d: dict) -> RelayHeader: 46 | ''' 47 | Args: 48 | d (dict): The dict with serialized keys to be deserialized 49 | Returns: 50 | (RelayHeader): The RelayHeader, a TypedDict with deserialized keys 51 | ''' 52 | return RelayHeader( 53 | raw=hex_deser(d['raw']), 54 | hash=hex_deser(d['hash']), 55 | height=d['height'], 56 | prevhash=hex_deser(d['prevhash']), 57 | merkle_root=hex_deser(d['merkle_root']) 58 | ) 59 | 60 | 61 | def deserialize_relay_header(s: str) -> RelayHeader: 62 | ''' 63 | Args: 64 | s (str): A JSON-serialized RelayHeader 65 | Returns: 66 | (RelayHeader): The deserialized RelayHeader 67 | ''' 68 | return dict_to_relay_header(json.loads(s)) 69 | 70 | 71 | def dict_from_spv_proof(s: SPVProof) -> dict: 72 | ''' 73 | Args: 74 | s (SPVProof): The SPVProof to be serialized 75 | Returns: 76 | (dict): A dictionary representing the SPVProof with serialized keys 77 | ''' 78 | return { 79 | 'version': hex_ser(s['version']), 80 | 'vin': hex_ser(s['vin']), 81 | 'vout': hex_ser(s['vout']), 82 | 'locktime': hex_ser(s['locktime']), 83 | 'tx_id': hex_ser(s['tx_id']), 84 | 'index': s['index'], 85 | 'intermediate_nodes': hex_ser(s['intermediate_nodes']), 86 | 'confirming_header': dict_from_relay_header(s['confirming_header']) 87 | } 88 | 89 | 90 | def serialize_spv_proof(s: SPVProof) -> str: 91 | ''' 92 | Args: 93 | s (SPVProof): The SPVProof to be serialized 94 | Returns: 95 | (str): A JSON-serialized SPVProof 96 | ''' 97 | return json.dumps(dict_from_spv_proof(s)) 98 | 99 | 100 | def dict_to_spv_proof(d: dict) -> SPVProof: 101 | ''' 102 | Args: 103 | d (dict): The dict with serialized keys to be deserialized 104 | Returns: 105 | (SPVProof): The SPVProof, a TypedDict with deserialized keys 106 | ''' 107 | 108 | t = tx.Tx.from_bytes( 109 | hex_deser(d['version']) 110 | + hex_deser(d['vin']) 111 | + hex_deser(d['vout']) 112 | + hex_deser(d['locktime']) 113 | ) 114 | return SPVProof( 115 | tx=t, 116 | version=hex_deser(d['version']), 117 | vin=hex_deser(d['vin']), 118 | vout=hex_deser(d['vout']), 119 | locktime=hex_deser(d['locktime']), 120 | tx_id=hex_deser(d['tx_id']), 121 | intermediate_nodes=hex_deser(d['intermediate_nodes']), 122 | index=d['index'], 123 | confirming_header=dict_to_relay_header(d['confirming_header']) 124 | ) 125 | 126 | 127 | def deserialize_spv_proof(s: str) -> SPVProof: 128 | ''' 129 | Args: 130 | s (str): A JSON-serialized SPVProof 131 | Returns: 132 | (SPVProof): The deserialized SPVProof 133 | ''' 134 | return dict_to_spv_proof(json.loads(s)) 135 | -------------------------------------------------------------------------------- /python/btcspv/test/test_ser.py: -------------------------------------------------------------------------------- 1 | import json 2 | import unittest 3 | 4 | from btcspv import ser 5 | 6 | 7 | class TestSer(unittest.TestCase): 8 | def setUp(self): 9 | f = open('../testProofs.json') 10 | self.vectors = json.loads(f.read()) 11 | 12 | # TODO: clean this up 13 | def test_deser_roundtrip(self): 14 | for v in self.vectors['valid']: 15 | proof = ser.deserialize_spv_proof(v) 16 | json_proof_string = ser.serialize_spv_proof(proof) 17 | second_proof = ser.deserialize_spv_proof(json_proof_string) 18 | 19 | self.assertEqual(proof, second_proof) 20 | 21 | def test_deser_header_roundtrip(self): 22 | for v in self.vectors['validHeader']: 23 | header = ser.deserialize_relay_header(v) 24 | json_header_string = ser.serialize_relay_header(header) 25 | second_header = ser.deserialize_relay_header(json_header_string) 26 | 27 | self.assertEqual(header, second_header) 28 | -------------------------------------------------------------------------------- /python/btcspv/test/test_utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import unittest 3 | 4 | from btcspv import utils 5 | 6 | 7 | class TestSer(unittest.TestCase): 8 | def setUp(self): 9 | f = open('../testVectors.json') 10 | self.vectors = json.loads(f.read()) 11 | 12 | def test_verify_proof(self): 13 | cases = self.vectors['verifyHash256Merkle'] 14 | 15 | for case in cases: 16 | proof = bytes.fromhex(case['input']['proof'][2:]) 17 | index = case['input']['index'] 18 | 19 | self.assertEqual( 20 | utils.verify_proof(proof, index), 21 | case['output']) 22 | -------------------------------------------------------------------------------- /python/btcspv/test/test_validate_spv.py: -------------------------------------------------------------------------------- 1 | import json 2 | import unittest 3 | 4 | from unittest import mock 5 | 6 | from btcspv import ser, validate_spv 7 | 8 | 9 | class TestValidateSPV(unittest.TestCase): 10 | def setUp(self): 11 | f = open('../testVectors.json') 12 | self.vectors = json.loads(f.read()) 13 | 14 | p = open('../testProofs.json') 15 | self.proof_vectors = json.loads(p.read()) 16 | 17 | self.valid_proofs = [ 18 | ser.deserialize_spv_proof(p) for p in self.proof_vectors['valid'] 19 | ] 20 | 21 | self.bad_headers = [ 22 | ser.dict_to_relay_header(p['header']) 23 | for p in self.proof_vectors['badHeaders'] 24 | ] 25 | 26 | with mock.patch('btcspv.ser.tx'): 27 | self.bad_proofs = [ 28 | ser.dict_to_spv_proof(p['proof']) 29 | for p in self.proof_vectors['badSPVProofs'] 30 | ] 31 | 32 | def test_validate_vin(self): 33 | for proof in self.valid_proofs: 34 | self.assertEqual( 35 | validate_spv.validate_vin(proof['vin']), 36 | True) 37 | 38 | invalid_proof = self.valid_proofs[0].copy() 39 | invalid_proof['vin'] = bytes.fromhex('00') 40 | self.assertEqual( 41 | validate_spv.validate_vin(invalid_proof['vin']), 42 | False 43 | ) 44 | 45 | def test_validate_vout(self): 46 | for proof in self.valid_proofs: 47 | self.assertEqual( 48 | validate_spv.validate_vout(proof['vout']), 49 | True) 50 | 51 | invalid_proof = self.valid_proofs[0].copy() 52 | invalid_proof['vout'] = bytes.fromhex('f12b34efcd') 53 | self.assertEqual( 54 | validate_spv.validate_vout(invalid_proof['vout']), 55 | False 56 | ) 57 | 58 | def test_extract_merkle_root_le(self): 59 | cases = self.vectors['extractMerkleRootLE'] 60 | 61 | for case in cases: 62 | input = bytes.fromhex(case['input'][2:]) 63 | output = bytes.fromhex(case['output'][2:]) 64 | 65 | self.assertEqual( 66 | validate_spv.extract_merkle_root_le(input), 67 | output 68 | ) 69 | 70 | def test_extract_prev_block_le(self): 71 | cases = self.vectors['extractPrevBlockLE'] 72 | 73 | for case in cases: 74 | input = bytes.fromhex(case['input'][2:]) 75 | output = bytes.fromhex(case['output'][2:]) 76 | 77 | self.assertEqual( 78 | validate_spv.extract_prev_block_le(input), 79 | output 80 | ) 81 | 82 | def test_prove(self): 83 | cases = self.vectors['prove'] 84 | 85 | for case in cases: 86 | tx_id = bytes.fromhex(case['input']['txIdLE'][2:]) 87 | merkle_root = bytes.fromhex(case['input']['merkleRootLE'][2:]) 88 | proof = bytes.fromhex(case['input']['proof'][2:]) 89 | index = case['input']['index'] 90 | 91 | self.assertEqual( 92 | validate_spv.prove(tx_id, merkle_root, proof, index), 93 | case['output'] 94 | ) 95 | 96 | def test_validate_header(self): 97 | for proof in self.valid_proofs: 98 | self.assertEqual( 99 | validate_spv.validate_header(proof['confirming_header']), 100 | True 101 | ) 102 | 103 | for header in self.bad_headers: 104 | self.assertEqual( 105 | validate_spv.validate_header(header), 106 | False 107 | ) 108 | 109 | def test_validate_spvproof(self): 110 | for proof in self.valid_proofs: 111 | self.assertEqual( 112 | validate_spv.validate_spvproof(proof), 113 | True 114 | ) 115 | 116 | for proof in self.bad_proofs: 117 | self.assertEqual( 118 | validate_spv.validate_spvproof(proof), 119 | False 120 | ) 121 | 122 | invalid_header_proof = self.valid_proofs[0].copy() 123 | invalid_header_proof['confirming_header'][ 124 | 'merkle_root' 125 | ] = bytes.fromhex( 126 | 'c61ac92842abc82aa93644b190fc18ad46c6738337e78bc0c69ab21c5d5ee2dd' 127 | ) 128 | 129 | self.assertEqual( 130 | validate_spv.validate_spvproof(invalid_header_proof), 131 | False 132 | ) 133 | -------------------------------------------------------------------------------- /python/btcspv/types.py: -------------------------------------------------------------------------------- 1 | from riemann import tx 2 | 3 | from mypy_extensions import TypedDict 4 | 5 | 6 | class RelayHeader(TypedDict): 7 | raw: bytes 8 | hash: bytes 9 | height: int 10 | prevhash: bytes 11 | merkle_root: bytes 12 | 13 | 14 | class SPVProof(TypedDict): 15 | tx: tx.Tx 16 | version: bytes 17 | vin: bytes 18 | vout: bytes 19 | locktime: bytes 20 | tx_id: bytes 21 | index: int 22 | intermediate_nodes: bytes 23 | confirming_header: RelayHeader 24 | -------------------------------------------------------------------------------- /python/btcspv/utils.py: -------------------------------------------------------------------------------- 1 | from riemann import utils as rutils 2 | 3 | from btcspv.types import SPVProof 4 | 5 | 6 | def verify_proof(proof: bytes, index: int) -> bool: 7 | ''' 8 | Verifies a hash256 merkle proof. 9 | The proof is encoded as a bytestring. The first 32 bytes are the leaf hash, 10 | the last 32 bytes are the roothash. 11 | Note that `index` is not a reliable indicator of location within a block. 12 | Args: 13 | proof (bytes): The merkle proof as a bytestring 14 | index (int): The 0-indexed position of the leaf in the leafset 15 | Returns: 16 | (bool): True if valid proof, else False 17 | ''' 18 | idx = index 19 | length = (len(proof) // 32) - 1 20 | 21 | if len(proof) % 32 != 0: 22 | return False 23 | 24 | if len(proof) == 32: 25 | return True 26 | 27 | # Should never occur 28 | if len(proof) == 64: 29 | return False 30 | 31 | current = proof[:32] 32 | root = proof[-32:] 33 | # For all hashes between first and last 34 | for i in range(1, length): 35 | next = proof[i * 32:i * 32 + 32] 36 | if idx % 2 == 1: 37 | current = rutils.hash256(next + current) 38 | else: 39 | current = rutils.hash256(current + next) 40 | idx = idx >> 1 41 | return current == root 42 | 43 | 44 | def verify_spv_proof(proof: SPVProof) -> bool: 45 | merkle = proof['tx_id'] \ 46 | + proof['intermediate_nodes'] \ 47 | + proof['confirming_header']['merkle_root'] 48 | return verify_proof(merkle, proof['index']) 49 | -------------------------------------------------------------------------------- /python/btcspv/validate_spv.py: -------------------------------------------------------------------------------- 1 | from riemann import tx 2 | from riemann.tx import shared 3 | from riemann import utils as rutils 4 | 5 | from btcspv import utils 6 | 7 | from typing import List 8 | from btcspv.types import RelayHeader, SPVProof 9 | 10 | 11 | def validate_vin(vin: bytes) -> bool: 12 | '''Checks that the vin is properly formatted''' 13 | if vin[0] > 0xfc or vin[0] == 0: 14 | return False 15 | try: 16 | deser = _deserialize_vin(vin) 17 | except (IndexError, ValueError): 18 | return False 19 | return sum(map(len, deser)) + 1 == len(vin) 20 | 21 | 22 | def _deserialize_vin(vin: bytes) -> List[tx.TxIn]: 23 | # Get the length of the tx_in vector 24 | tx_ins = [] 25 | tx_ins_num = shared.VarInt.from_bytes(vin) 26 | 27 | # `current` is the index of next read 28 | current = len(tx_ins_num) 29 | 30 | # Deserialize all tx_ins 31 | for _ in range(tx_ins_num.number): 32 | tx_in = tx.TxIn.from_bytes(vin[current:]) 33 | current += len(tx_in) 34 | tx_ins.append(tx_in) 35 | 36 | return tx_ins 37 | 38 | 39 | def validate_vout(vout: bytes) -> bool: 40 | '''Checks that the vout is properly formatted''' 41 | if vout[0] > 0xfc or vout[0] == 0: 42 | return False 43 | try: 44 | deser = _deserialize_vout(vout) 45 | except (IndexError, ValueError): 46 | return False 47 | return sum(map(len, deser)) + 1 == len(vout) 48 | 49 | 50 | def _deserialize_vout(vout: bytes) -> List[tx.TxOut]: 51 | # Get the length of the tx_in vector 52 | tx_outs = [] 53 | tx_outs_num = shared.VarInt.from_bytes(vout) 54 | 55 | # `current` is the index of next read 56 | current = len(tx_outs_num) 57 | 58 | # Deserialize all tx_outs 59 | for _ in range(tx_outs_num.number): 60 | tx_out = tx.TxOut.from_bytes(vout[current:]) 61 | current += len(tx_out) 62 | tx_outs.append(tx_out) 63 | 64 | return tx_outs 65 | 66 | 67 | def extract_merkle_root_le(header: bytes) -> bytes: 68 | '''Extracts the transaction merkle root from a header (little-endian)''' 69 | return header[36:68] 70 | 71 | 72 | def extract_prev_block_le(header: bytes) -> bytes: 73 | '''Extracts the previous block's hash from a header (little-endian)''' 74 | return header[4:36] 75 | 76 | 77 | def prove( 78 | txid: bytes, merkle_root: bytes, intermediate_nodes: bytes, index: int) \ 79 | -> bool: 80 | ''' 81 | Validates a tx inclusion in the block. 82 | Note that `index` is not a reliable indicator of location within a block. 83 | ''' 84 | if txid == merkle_root and index == 0 and len(intermediate_nodes) == 0: 85 | return True 86 | 87 | proof = txid + intermediate_nodes + merkle_root 88 | return utils.verify_proof(proof, index) 89 | 90 | 91 | def validate_header(header: RelayHeader) -> bool: 92 | ''' 93 | Verifies a bitcoin header 94 | Args: 95 | header (RelayHeader): The header as an object 96 | 97 | Returns: 98 | (bool): True if valid header, else False 99 | ''' 100 | # Check that HashLE is the correct hash of the raw header 101 | header_hash = rutils.hash256(header['raw']) 102 | if header_hash != header['hash']: 103 | return False 104 | 105 | # Check that the MerkleRootLE is the correct MerkleRoot for the header 106 | extracted_merkle_root = extract_merkle_root_le(header['raw']) 107 | if extracted_merkle_root != header['merkle_root']: 108 | return False 109 | 110 | # Check that PrevHash is the correct PrevHash for the header 111 | extracted_prevhash = extract_prev_block_le(header['raw']) 112 | if extracted_prevhash != header['prevhash']: 113 | return False 114 | 115 | return True 116 | 117 | 118 | def validate_spvproof(proof: SPVProof) -> bool: 119 | ''' 120 | Verifies an SPV proof object 121 | Args: 122 | proof (SPVProof): The SPV Proof as an object 123 | Returns: 124 | (bool): True if valid proof, else False 125 | ''' 126 | if not validate_vin(proof['vin']): 127 | return False 128 | 129 | if not validate_vout(proof['vout']): 130 | return False 131 | 132 | tx_id = rutils.hash256( 133 | proof['version'] + 134 | proof['vin'] + 135 | proof['vout'] + 136 | proof['locktime'] 137 | ) 138 | if tx_id != proof['tx_id']: 139 | return False 140 | 141 | if not validate_header(proof['confirming_header']): 142 | return False 143 | 144 | valid_proof = prove( 145 | proof['tx_id'], 146 | proof['confirming_header']['merkle_root'], 147 | proof['intermediate_nodes'], 148 | proof['index'] 149 | ) 150 | if not valid_proof: 151 | return False 152 | return True 153 | -------------------------------------------------------------------------------- /python/scripts/run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | flake8 \ 4 | --ignore=W503,W504 \ 5 | --exclude btcspv/tests/ \ 6 | btcspv && \ 7 | mypy \ 8 | btcspv/ \ 9 | --disallow-untyped-defs \ 10 | --strict-equality \ 11 | --show-error-codes \ 12 | --warn-return-any \ 13 | --ignore-missing-imports && \ 14 | coverage erase && \ 15 | pytest \ 16 | btcspv/ \ 17 | -q \ 18 | --cov-config .coveragerc \ 19 | --cov-report= \ 20 | --cov && \ 21 | coverage report && \ 22 | coverage html 23 | -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | # flake8: noqa 2 | from setuptools import setup, find_packages 3 | 4 | reqs = [ 5 | 'riemann-tx==2.1.0' 6 | ] 7 | 8 | setup( 9 | name='bitcoin-spv-py', 10 | version='3.0.1', 11 | url='https://github.com/summa-tx/bitcoin-spv', 12 | author='James Prestwich', 13 | author_email='james@summa.one', 14 | install_requires=reqs, 15 | packages=find_packages(), 16 | package_data={'btcspv': ['py.typed']}, 17 | package_dir={'btcspv': 'btcspv'}, 18 | python_requires='>=3.6', 19 | classifiers=[ 20 | 'License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)' 21 | ], 22 | license='MIT OR Apache-2.0' 23 | ) 24 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | # works_for_me.sh 2 | cd golang && go clean -testcache && go test ./... -cover && \ 3 | cd ../js && npm run test:coverage && \ 4 | cd ../solidity && npm run test && \ 5 | cd ../python && pipenv run test && \ 6 | cd ../rust && cargo test && cargo test --lib --no-default-features && cargo nono check --no-default-features && \ 7 | cd ../c && make && \ 8 | echo "\n\nWorks for me ¯\_(ツ)_/¯" 9 | -------------------------------------------------------------------------------- /rust/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pkg/ 3 | -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoin-spv" 3 | version = "5.0.0" 4 | authors = ["James Prestwich "] 5 | edition = "2018" 6 | license="MIT OR Apache-2.0" 7 | description="Bitcoin SPV Proof evaluation" 8 | homepage="https://github.com/summa-tx/bitcoin-spv" 9 | repository="https://github.com/summa-tx/bitcoin-spv" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [lib] 14 | crate-type = ["cdylib", "rlib"] 15 | 16 | [dependencies] 17 | primitive-types = { version = "0.7.2", default-features = false } 18 | ripemd160 = { version = "0.8.0", default-features = false } 19 | sha2 = { version = "0.8.0", default-features = false } 20 | 21 | # std 22 | hex = { version = "0.4.2", optional = true, default-features = false } 23 | serde_json = { version = "1.0", optional = true } 24 | serde = { version = "1.0", optional = true } 25 | 26 | [dev-dependencies] 27 | hex = "0.4.2" 28 | serde_json = { version = "1.0" } 29 | serde = { version = "1.0.115", features = ["derive"] } 30 | 31 | [features] 32 | default=["std"] 33 | std=["primitive-types/std", "serde", "serde/derive", "serde_json", "hex/std", "sha2/std", "ripemd160/std"] 34 | -------------------------------------------------------------------------------- /rust/README.md: -------------------------------------------------------------------------------- 1 | ## Bitcoin SPV Proofs in Rust 2 | 3 | ### What is it? 4 | 5 | `bitcoin-spv` is a collection of Rust tooling for working with Bitcoin 6 | data structures. Basically, these tools help you parse, inspect, and 7 | authenticate Bitcoin transactions. 8 | 9 | ### Features 10 | 11 | - default: `std` 12 | - `std` -- rust standard library. Disable for no-std support 13 | 14 | ### Building 15 | 16 | `$ cargo build` 17 | 18 | ### Testing 19 | 20 | `$ cargo test` 21 | 22 | Run no-std functionality tests 23 | `$ cargo test --lib --no-default-features` 24 | 25 | ## Supported by 26 | 27 | ![Binance X Fellowship, Interchain Foundation, Summa, Cross Chain Group](../logo-group.jpg) 28 | 29 | - [Binance X Fellowship](https://binancex.dev/fellowship.html) 30 | - [Interchain Foundation](https://interchain.io/) 31 | - [Summa](https://summa.one) 32 | - [Cross Chain Group](https://crosschain.group/) 33 | 34 | 35 | ### IMPORTANT WARNING 36 | 37 | It is extremely easy to write insecure code using these libraries. We do not 38 | recommend a specific security model. Any SPV verification involves complex 39 | security assumptions. Please seek external review for your design before 40 | building with these libraries. 41 | -------------------------------------------------------------------------------- /rust/src/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! impl_view_type { 2 | ( 3 | $(#[$outer:meta])* 4 | $name:ident 5 | ) => { 6 | $(#[$outer])* 7 | #[derive(Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] 8 | pub struct $name<'a>(pub(crate) &'a [u8]); 9 | 10 | impl $name<'_> { 11 | /// The length of the underlying slice 12 | pub fn len(&self) -> usize { 13 | self.0.len() 14 | } 15 | 16 | /// Whether the underlying slice is empty 17 | pub fn is_empty(&self) -> bool { 18 | self.0.is_empty() 19 | } 20 | 21 | /// The last item in the underlying slice, if any 22 | pub fn last(&self) -> Option<&u8> { 23 | self.0.last() 24 | } 25 | } 26 | 27 | impl<'a> AsRef<[u8]> for $name<'a> { 28 | fn as_ref(&self) -> &[u8] { 29 | self.0 30 | } 31 | } 32 | 33 | impl> core::ops::Index for $name<'_> { 34 | type Output = I::Output; 35 | 36 | fn index(&self, index: I) -> &Self::Output { 37 | self.as_ref().index(index) 38 | } 39 | } 40 | 41 | impl PartialEq<[u8]> for $name<'_> { 42 | fn eq(&self, other: &[u8]) -> bool { 43 | self.0 == other 44 | } 45 | } 46 | 47 | impl PartialEq<&[u8]> for $name<'_> { 48 | fn eq(&self, other: &&[u8]) -> bool { 49 | &self.0 == other 50 | } 51 | } 52 | 53 | // For convenience while testing 54 | #[cfg(test)] 55 | impl<'a> From<&'a [u8]> for $name<'a> { 56 | fn from(slice: &'a [u8]) -> Self { 57 | Self(slice) 58 | } 59 | } 60 | } 61 | } 62 | 63 | macro_rules! compact_int_conv { 64 | ($target:ty) => { 65 | impl From<$target> for CompactInt { 66 | fn from(number: $target) -> CompactInt { 67 | Self(number as u64) 68 | } 69 | } 70 | 71 | impl PartialEq<$target> for CompactInt { 72 | fn eq(&self, other: &$target) -> bool { 73 | self.0 == *other as u64 74 | } 75 | } 76 | }; 77 | } 78 | 79 | #[cfg(feature = "std")] 80 | macro_rules! impl_hex_serde { 81 | ($name:ty, $num:expr) => { 82 | #[cfg(feature = "std")] 83 | impl<'de> serde::Deserialize<'de> for $name { 84 | fn deserialize(deserializer: D) -> Result<$name, D::Error> 85 | where 86 | D: serde::Deserializer<'de>, 87 | { 88 | let s: &str = serde::Deserialize::deserialize(deserializer)?; 89 | let mut header = <$name>::default(); 90 | 91 | let result = utils::deserialize_hex(s); 92 | 93 | let deser: Vec; 94 | match result { 95 | Ok(v) => deser = v, 96 | Err(e) => return Err(serde::de::Error::custom(e.to_string())), 97 | } 98 | if deser.len() != $num { 99 | let err_string: std::string::String = std::format!( 100 | "Expected {} bytes, got {:?} bytes", 101 | stringify!($num), 102 | deser.len() 103 | ); 104 | return Err(serde::de::Error::custom(err_string)); 105 | } 106 | header.as_mut().copy_from_slice(&deser); 107 | Ok(header) 108 | } 109 | } 110 | 111 | #[cfg(feature = "std")] 112 | impl serde::Serialize for $name { 113 | fn serialize(&self, serializer: S) -> Result 114 | where 115 | S: serde::Serializer, 116 | { 117 | let s: &str = &utils::serialize_hex(self.as_ref()); 118 | serializer.serialize_str(s) 119 | } 120 | } 121 | }; 122 | } 123 | -------------------------------------------------------------------------------- /rust/src/utils.rs: -------------------------------------------------------------------------------- 1 | extern crate hex; 2 | extern crate std; 3 | 4 | use std::{ 5 | format, 6 | string::String, 7 | vec::Vec, // The struct 8 | }; 9 | 10 | /// Changes the endianness of a byte array. 11 | /// Returns a new, backwards, byte array. 12 | /// 13 | /// # Arguments 14 | /// 15 | /// * `b` - The bytes to reverse 16 | pub fn reverse_endianness(b: &[u8]) -> Vec { 17 | b.iter().rev().copied().collect() 18 | } 19 | 20 | /// Strips the '0x' prefix off of hex string so it can be deserialized. 21 | /// 22 | /// # Arguments 23 | /// 24 | /// * `s` - The hex str 25 | pub fn strip_0x_prefix(s: &str) -> &str { 26 | if &s[..2] == "0x" { 27 | &s[2..] 28 | } else { 29 | s 30 | } 31 | } 32 | 33 | /// Deserializes a hex string into a u8 array. 34 | /// 35 | /// # Arguments 36 | /// 37 | /// * `s` - The hex string 38 | pub fn deserialize_hex(s: &str) -> Result, hex::FromHexError> { 39 | hex::decode(&strip_0x_prefix(s)) 40 | } 41 | 42 | /// Serializes a u8 array into a hex string. 43 | /// 44 | /// # Arguments 45 | /// 46 | /// * `buf` - The value as a u8 array 47 | pub fn serialize_hex(buf: &[u8]) -> String { 48 | format!("0x{}", hex::encode(buf)) 49 | } 50 | 51 | /// Deserialize a hex string into bytes. 52 | /// Panics if the string is malformatted. 53 | /// 54 | /// # Arguments 55 | /// 56 | /// * `s` - The hex string 57 | /// 58 | /// # Panics 59 | /// 60 | /// When the string is not validly formatted hex. 61 | pub fn force_deserialize_hex(s: &str) -> Vec { 62 | deserialize_hex(s).unwrap() 63 | } 64 | -------------------------------------------------------------------------------- /solidity/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es6: true, 6 | }, 7 | extends: [ 8 | 'airbnb-base', 9 | ], 10 | globals: { 11 | Atomics: 'readonly', 12 | SharedArrayBuffer: 'readonly', 13 | }, 14 | parserOptions: { 15 | ecmaVersion: 2018, 16 | }, 17 | rules: { 18 | "comma-dangle": 'off', 19 | "no-bitwise": 'off', 20 | "no-await-in-loop": 'off' 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /solidity/.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /solidity/.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | build/ 3 | blocks/ 4 | __pycache__/ 5 | node_modules/ 6 | .mypy_cache 7 | 8 | # coverage 9 | scTopics 10 | coverage/ 11 | coverageEnv/ 12 | coverage.json 13 | docs/ 14 | testVectors.json 15 | -------------------------------------------------------------------------------- /solidity/.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | copyPackages: ['eth-gas-reporter'], 3 | skipFiles: ['BytesLib.sol', 4 | 'SafeMath.sol', 5 | 'test'] 6 | }; 7 | -------------------------------------------------------------------------------- /solidity/.soliumignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | contracts/Migrations.sol 3 | contracts/BytesLib.sol 4 | -------------------------------------------------------------------------------- /solidity/.soliumrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solium:recommended", 3 | "plugins": [ 4 | "security" 5 | ], 6 | "rules": { 7 | "quotes": [ 8 | "error", 9 | "double" 10 | ], 11 | "indentation": [ 12 | "error", 13 | 4 14 | ], 15 | "linebreak-style": [ 16 | "error", 17 | "unix" 18 | ] 19 | } 20 | } -------------------------------------------------------------------------------- /solidity/README.md: -------------------------------------------------------------------------------- 1 | ## Bitcoin SPV Proofs in Solidity 2 | 3 | ### What is it? 4 | 5 | `bitcoin-spv` is a collection of Solidity libraries for working with Bitcoin 6 | transactions in Solidity contracts. Basically, these tools help you parse, 7 | inspect, and authenticate Bitcoin transactions. 8 | 9 | ## Supported by 10 | 11 | ![Summa, Cross Chain Group](../logo-summa-ccg.jpg) 12 | 13 | - [Summa](https://summa.one) 14 | - [Cross Chain Group](https://crosschain.group/) 15 | 16 | ------- 17 | 18 | ## IMPORTANT WARNING 19 | 20 | It is extremely easy to write insecure code using these libraries. We do not 21 | recommend a specific security model. Any SPV verification involves complex 22 | security assumptions. Please seek external review for your design before 23 | building with these libraries. 24 | 25 | ## Using `ViewBTC` and `ViewSPV` 26 | 27 | The high level libraries use an underlying typed memory view library for 28 | efficient handling of the Solidity `bytes memory` type without copying memory. 29 | The library co-opts the `bytes29` type and uses it as a pointer to a 30 | contiguous region of memory. Operations on this memory are defined on the 31 | `bytes29` type via a `using ____ for bytes29` statement. 32 | 33 | The memory view semantics and interface are defined in `TypedMemView.sol`. 34 | For BTC-specific applications, it is usually sufficient to import `ViewBTC` 35 | without importing the underlying `TypedMemView` library. 36 | 37 | BTC types are defined in `ViewBTC.sol` and checked at run time. Solidity does 38 | not currently allow compile time type-checking for user-defined stack types. 39 | Each Bitcoin function defines an explicit runtime type-check via modifier. 40 | Type-check failures result in contract reversion. 41 | 42 | Initial conversion from `bytes memory` to a typed memory view should be done 43 | using the `tryAs_____` functions defined in `ViewBTC`. 44 | 45 | ```solidity 46 | 47 | conrtract 48 | using TypedMemView for bytes; 49 | using TypedMemView for bytes29; 50 | 51 | using ViewBTC for bytes29; 52 | using ViewSPV for bytes29; 53 | 54 | function acceptAVin(bytes memory _vin) { 55 | bytes29 vin = _vin 56 | .ref() // Produce a view (reference to) the byte array 57 | .tryAsVin() // Attempt to validate the user input as a Vin 58 | .assertValid(); // Assert the result is not an error 59 | 60 | // additional logic relying on the vin 61 | // ... 62 | } 63 | ``` 64 | 65 | ## How are proofs formatted? 66 | 67 | An SPV interaction has two players: a prover and a verifier. The prover submits 68 | an SPV proof, and the verifier checks it. 69 | 70 | The proof must contain several elements: a transaction, an inclusion proof, and 71 | a header chain. For convenience and gas minimization, we have a standard format 72 | for these: 73 | 74 | 1. The transaction is pre-parsed by the prover into 4 elements: 75 | 1. The transaction version (currently always 1 or 2 as a 4-byte LE integer) 76 | 1. The variable-length input vector 77 | 1. No more than 0xfc inputs 78 | 1. Prefixed with the number of inputs 79 | 1. Tightly packed in a single bytearray called `vin` 80 | 1. The variable-length output vector 81 | 1. No more than 0xfc outputs 82 | 1. Prefixed with the number of inputs 83 | 1. Tightly packed in a single bytearray called `vout` 84 | 1. The transaction locktime (a 4-byte LE integer) 85 | 1. The header chain: 86 | 1. Contains any number of 80-byte headers 87 | 1. Is a single bytearray without prefix or padding 88 | 1. Starts from the lowest height 89 | 1. Must form a logical hash-linked chain 90 | 1. The merkle inclusion proof, which contains 2 elements: 91 | 1. The merkle branch containing any number of 32-byte double-sha2 digests 92 | 1. In a single bytearray, without prefix or padding 93 | 1. Ordered from leaf to root (but does not include leaf or root) 94 | 1. The index of the leaf in the tree (an integer) 95 | 96 | While the prover is off-chain, and makes Ethereum transactions, the verifier is 97 | implemented as a solidity contract that validates proofs contained in those 98 | transactions. The verifier must set one parameter on-chain: the required total 99 | work, expressed as accumulated difficulty. The verifier sums difficulty across 100 | the header chain by measuring the work in its component headers. In addition, 101 | the verifier may set any number of other acceptance constraints on the proof. 102 | E.g. the contract may check that the `vout` contains an output paying at 103 | least 30,000 satoshi to a particular `scriptPubkey`. 104 | 105 | ### Development Setup 106 | 107 | ```sh 108 | $ npm run compile # truffle compile 109 | $ npm run test # truffle test 110 | $ npm run coverage 111 | ``` -------------------------------------------------------------------------------- /solidity/contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >=0.4.21 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /solidity/contracts/SafeMath.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | /* 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2016 Smart Contract Solutions, Inc. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining 9 | a copy of this software and associated documentation files (the 10 | "Software"), to deal in the Software without restriction, including 11 | without limitation the rights to use, copy, modify, merge, publish, 12 | distribute, sublicense, and/or sell copies of the Software, and to 13 | permit persons to whom the Software is furnished to do so, subject to 14 | the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included 17 | in all copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | 28 | 29 | /** 30 | * @title SafeMath 31 | * @dev Math operations with safety checks that throw on error 32 | */ 33 | library SafeMath { 34 | 35 | /** 36 | * @dev Multiplies two numbers, throws on overflow. 37 | */ 38 | function mul(uint256 _a, uint256 _b) internal pure returns (uint256 c) { 39 | // Gas optimization: this is cheaper than asserting 'a' not being zero, but the 40 | // benefit is lost if 'b' is also tested. 41 | // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 42 | if (_a == 0) { 43 | return 0; 44 | } 45 | 46 | c = _a * _b; 47 | require(c / _a == _b, "Overflow during multiplication."); 48 | return c; 49 | } 50 | 51 | /** 52 | * @dev Integer division of two numbers, truncating the quotient. 53 | */ 54 | function div(uint256 _a, uint256 _b) internal pure returns (uint256) { 55 | // assert(_b > 0); // Solidity automatically throws when dividing by 0 56 | // uint256 c = _a / _b; 57 | // assert(_a == _b * c + _a % _b); // There is no case in which this doesn't hold 58 | return _a / _b; 59 | } 60 | 61 | /** 62 | * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). 63 | */ 64 | function sub(uint256 _a, uint256 _b) internal pure returns (uint256) { 65 | require(_b <= _a, "Underflow during subtraction."); 66 | return _a - _b; 67 | } 68 | 69 | /** 70 | * @dev Adds two numbers, throws on overflow. 71 | */ 72 | function add(uint256 _a, uint256 _b) internal pure returns (uint256 c) { 73 | c = _a + _b; 74 | require(c >= _a, "Overflow during addition."); 75 | return c; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /solidity/contracts/ViewSPV.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | /** @title ViewSPV */ 4 | /** @author Summa (https://summa.one) */ 5 | 6 | import {TypedMemView} from "@summa-tx/memview.sol/contracts/TypedMemView.sol"; 7 | import {ViewBTC} from "./ViewBTC.sol"; 8 | import {SafeMath} from "./SafeMath.sol"; 9 | 10 | 11 | library ViewSPV { 12 | using TypedMemView for bytes; 13 | using TypedMemView for bytes29; 14 | using ViewBTC for bytes29; 15 | using SafeMath for uint256; 16 | 17 | uint256 constant ERR_BAD_LENGTH = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; 18 | uint256 constant ERR_INVALID_CHAIN = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe; 19 | uint256 constant ERR_LOW_WORK = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd; 20 | 21 | function getErrBadLength() internal pure returns (uint256) { 22 | return ERR_BAD_LENGTH; 23 | } 24 | 25 | function getErrInvalidChain() internal pure returns (uint256) { 26 | return ERR_INVALID_CHAIN; 27 | } 28 | 29 | function getErrLowWork() internal pure returns (uint256) { 30 | return ERR_LOW_WORK; 31 | } 32 | 33 | /// @notice requires `memView` to be of a specified type 34 | /// @param memView a 29-byte view with a 5-byte type 35 | /// @param t the expected type (e.g. BTCTypes.Outpoint, BTCTypes.TxIn, etc) 36 | /// @return passes if it is the correct type, errors if not 37 | modifier typeAssert(bytes29 memView, ViewBTC.BTCTypes t) { 38 | memView.assertType(uint40(t)); 39 | _; 40 | } 41 | 42 | /// @notice Validates a tx inclusion in the block 43 | /// @dev `index` is not a reliable indicator of location within a block 44 | /// @param _txid The txid (LE) 45 | /// @param _merkleRoot The merkle root (as in the block header) 46 | /// @param _intermediateNodes The proof's intermediate nodes (digests between leaf and root) 47 | /// @param _index The leaf's index in the tree (0-indexed) 48 | /// @return true if fully valid, false otherwise 49 | function prove( 50 | bytes32 _txid, 51 | bytes32 _merkleRoot, 52 | bytes29 _intermediateNodes, 53 | uint _index 54 | ) internal view typeAssert(_intermediateNodes, ViewBTC.BTCTypes.MerkleArray) returns (bool) { 55 | // Shortcut the empty-block case 56 | if (_txid == _merkleRoot && _index == 0 && _intermediateNodes.len() == 0) { 57 | return true; 58 | } 59 | 60 | return ViewBTC.checkMerkle(_txid, _intermediateNodes, _merkleRoot, _index); 61 | } 62 | 63 | /// @notice Hashes transaction to get txid 64 | /// @dev Supports Legacy and Witness 65 | /// @param _version 4-bytes version 66 | /// @param _vin Raw bytes length-prefixed input vector 67 | /// @param _vout Raw bytes length-prefixed output vector 68 | /// @param _locktime 4-byte tx locktime 69 | /// @return 32-byte transaction id, little endian 70 | function calculateTxId( 71 | bytes4 _version, 72 | bytes29 _vin, 73 | bytes29 _vout, 74 | bytes4 _locktime 75 | ) internal view typeAssert(_vin, ViewBTC.BTCTypes.Vin) typeAssert(_vout, ViewBTC.BTCTypes.Vout) returns (bytes32) { 76 | // TODO: write in assembly 77 | return abi.encodePacked(_version, _vin.clone(), _vout.clone(), _locktime).ref(0).hash256(); 78 | } 79 | 80 | // TODO: add test for checkWork 81 | /// @notice Checks validity of header work 82 | /// @param _header Header view 83 | /// @param _target The target threshold 84 | /// @return true if header work is valid, false otherwise 85 | function checkWork(bytes29 _header, uint256 _target) internal view typeAssert(_header, ViewBTC.BTCTypes.Header) returns (bool) { 86 | return _header.work() < _target; 87 | } 88 | 89 | 90 | /// @notice Checks validity of header chain 91 | /// @dev Compares current header parent to previous header's digest 92 | /// @param _header The raw bytes header 93 | /// @param _prevHeaderDigest The previous header's digest 94 | /// @return true if the connect is valid, false otherwise 95 | function checkParent(bytes29 _header, bytes32 _prevHeaderDigest) internal pure typeAssert(_header, ViewBTC.BTCTypes.Header) returns (bool) { 96 | return _header.parent() == _prevHeaderDigest; 97 | } 98 | 99 | /// @notice Checks validity of header chain 100 | /// @notice Compares the hash of each header to the prevHash in the next header 101 | /// @param _headers Raw byte array of header chain 102 | /// @return The total accumulated difficulty of the header chain, or an error code 103 | function checkChain(bytes29 _headers) internal view typeAssert(_headers, ViewBTC.BTCTypes.HeaderArray) returns (uint256 _totalDifficulty) { 104 | bytes32 _digest; 105 | uint256 _headerCount = _headers.len() / 80; 106 | for (uint256 i = 0; i < _headerCount; i += 1) { 107 | bytes29 _header = _headers.indexHeaderArray(i); 108 | if (i != 0) { 109 | if (!checkParent(_header, _digest)) {return ERR_INVALID_CHAIN;} 110 | } 111 | _digest = _header.workHash(); 112 | uint256 _work = TypedMemView.reverseUint256(uint256(_digest)); 113 | uint256 _target = _header.target(); 114 | 115 | if (_work > _target) {return ERR_LOW_WORK;} 116 | 117 | _totalDifficulty += ViewBTC.toDiff(_target); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /solidity/contracts/test/CheckBitcoinSigsTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | /** @title ValidateSPV*/ 4 | /** @author Summa (https://summa.one) */ 5 | 6 | import {CheckBitcoinSigs} from "../CheckBitcoinSigs.sol"; 7 | 8 | contract CheckBitcoinSigsTest { 9 | 10 | /// @notice Derives an Ethereum Account address from a pubkey 11 | /// @dev The address is the last 20 bytes of the keccak256 of the address 12 | /// @param _pubkey The public key X & Y. Unprefixed, as a 64-byte array 13 | /// @return The account address 14 | function accountFromPubkey(bytes memory _pubkey) public pure returns (address) { 15 | return CheckBitcoinSigs.accountFromPubkey(_pubkey); 16 | } 17 | 18 | /// @notice Calculates the p2wpkh output script of a pubkey 19 | /// @dev Compresses keys to 33 bytes as required by Bitcoin 20 | /// @param _pubkey The public key, compressed or uncompressed 21 | /// @return The p2wkph output script 22 | function p2wpkhFromPubkey(bytes memory _pubkey) public view returns (bytes memory) { 23 | return CheckBitcoinSigs.p2wpkhFromPubkey(_pubkey); 24 | } 25 | 26 | /// @notice checks a signed message's validity under a pubkey 27 | /// @dev does this using ecrecover because Ethereum has no soul 28 | /// @param _pubkey the public key to check (64 bytes) 29 | /// @param _digest the message digest signed 30 | /// @param _v the signature recovery value 31 | /// @param _r the signature r value 32 | /// @param _s the signature s value 33 | /// @return true if signature is valid, else false 34 | function checkSig( 35 | bytes memory _pubkey, 36 | bytes32 _digest, 37 | uint8 _v, 38 | bytes32 _r, 39 | bytes32 _s 40 | ) public pure returns (bool) { 41 | return CheckBitcoinSigs.checkSig(_pubkey, _digest, _v, _r, _s); 42 | } 43 | 44 | /// @notice checks a signed message against a bitcoin p2wpkh output script 45 | /// @dev does this my verifying the p2wpkh matches an ethereum account 46 | /// @param _p2wpkhOutputScript the bitcoin output script 47 | /// @param _pubkey the uncompressed, unprefixed public key to check 48 | /// @param _digest the message digest signed 49 | /// @param _v the signature recovery value 50 | /// @param _r the signature r value 51 | /// @param _s the signature s value 52 | /// @return true if signature is valid, else false 53 | function checkBitcoinSig( 54 | bytes memory _p2wpkhOutputScript, 55 | bytes memory _pubkey, 56 | bytes32 _digest, 57 | uint8 _v, 58 | bytes32 _r, 59 | bytes32 _s 60 | ) public view returns (bool) { 61 | return CheckBitcoinSigs.checkBitcoinSig( 62 | _p2wpkhOutputScript, 63 | _pubkey, 64 | _digest, 65 | _v, 66 | _r, 67 | _s); 68 | } 69 | /// @notice checks if a message is the sha256 preimage of a digest 70 | /// @dev this is NOT the hash256! this step is necessary for ECDSA security! 71 | /// @param _digest the digest 72 | /// @param _candidate the purported preimage 73 | /// @return true if the preimage matches the digest, else false 74 | function isSha256Preimage( 75 | bytes memory _candidate, 76 | bytes32 _digest 77 | ) public pure returns (bool) { 78 | return CheckBitcoinSigs.isSha256Preimage(_candidate, _digest); 79 | } 80 | 81 | /// @notice checks if a message is the keccak256 preimage of a digest 82 | /// @dev this step is necessary for ECDSA security! 83 | /// @param _digest the digest 84 | /// @param _candidate the purported preimage 85 | /// @return true if the preimage matches the digest, else false 86 | function isKeccak256Preimage( 87 | bytes memory _candidate, 88 | bytes32 _digest 89 | ) public pure returns (bool) { 90 | return CheckBitcoinSigs.isKeccak256Preimage(_candidate, _digest); 91 | } 92 | 93 | /// @notice calculates the signature hash of a Bitcoin transaction with the provided details 94 | /// @dev documented in bip143. many values are hardcoded here 95 | /// @param _outpoint the bitcoin output script 96 | /// @param _inputPKH the input pubkeyhash (hash160(sender_pubkey)) 97 | /// @param _inputValue the value of the input in satoshi 98 | /// @param _outputValue the value of the output in satoshi 99 | /// @param _outputPKH the output pubkeyhash (hash160(recipient_pubkey)) 100 | /// @return the double-sha256 (hash256) signature hash as defined by bip143 101 | function oneInputOneOutputSighash( 102 | bytes memory _outpoint, // 36 byte UTXO id 103 | bytes20 _inputPKH, // 20 byte hash160 104 | bytes8 _inputValue, // 8-byte LE 105 | bytes8 _outputValue, // 8-byte LE 106 | bytes20 _outputPKH // 20 byte hash160 107 | ) public view returns (bytes32) { 108 | return CheckBitcoinSigs.oneInputOneOutputSighash( 109 | _outpoint, 110 | _inputPKH, 111 | _inputValue, 112 | _outputValue, 113 | _outputPKH); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /solidity/contracts/test/ViewBTCTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | /** @title BitcoinSPV */ 4 | /** @author Summa (https://summa.one) */ 5 | 6 | import {TypedMemView} from "@summa-tx/memview.sol/contracts/TypedMemView.sol"; 7 | import {ViewBTC} from "../ViewBTC.sol"; 8 | 9 | contract ViewBTCTest { 10 | 11 | using TypedMemView for bytes; 12 | using TypedMemView for bytes29; 13 | using ViewBTC for bytes29; 14 | 15 | function encodeHex(uint256 _b) public pure returns (uint256, uint256) { 16 | return TypedMemView.encodeHex(_b); 17 | } 18 | 19 | function indexVarInt(bytes memory _b) public pure returns (uint64) { 20 | return _b.ref(0).indexCompactInt(0); 21 | } 22 | 23 | function hash160(bytes memory _b) public view returns (bytes20) { 24 | return _b.ref(0).hash160(); 25 | } 26 | 27 | function hash256(bytes memory _b) public view returns (bytes32) { 28 | return _b.ref(0).hash256(); 29 | } 30 | 31 | function indexVin(bytes memory _vin, uint256 _index) public view returns (bytes memory) { 32 | return _vin.ref(0).tryAsVin().assertValid().indexVin(_index).clone(); 33 | } 34 | 35 | function inputLength(bytes memory _input) public pure returns (uint256) { 36 | return _input.ref(uint40(ViewBTC.BTCTypes.IntermediateTxIns)).inputLength(); 37 | } 38 | 39 | function sequence(bytes memory _input) public pure returns (uint32) { 40 | return _input.ref(uint40(ViewBTC.BTCTypes.TxIn)).sequence(); 41 | 42 | } 43 | function scriptSig(bytes memory _input) public view returns (bytes memory) { 44 | return _input.ref(uint40(ViewBTC.BTCTypes.TxIn)).scriptSig().clone(); 45 | } 46 | 47 | function scriptPubkey(bytes memory _output) public view returns (bytes memory) { 48 | return _output.ref(uint40(ViewBTC.BTCTypes.TxOut)).scriptPubkey().clone(); 49 | } 50 | 51 | function outpoint(bytes memory _input) public view returns (bytes memory) { 52 | return _input.ref(uint40(ViewBTC.BTCTypes.TxIn)).outpoint().clone(); 53 | } 54 | 55 | function outpointIdx(bytes memory _input) public pure returns (uint32) { 56 | return _input.ref(uint40(ViewBTC.BTCTypes.TxIn)).outpoint().outpointIdx(); 57 | } 58 | 59 | function txidLE(bytes memory _input) public pure returns (bytes32) { 60 | return _input.ref(uint40(ViewBTC.BTCTypes.TxIn)).outpoint().txidLE(); 61 | } 62 | 63 | function outputLength(bytes memory _output) public pure returns (uint256) { 64 | return _output.ref(uint40(ViewBTC.BTCTypes.IntermediateTxOuts)).outputLength(); 65 | } 66 | 67 | function indexVout(bytes memory _vout, uint256 _index) public view returns (bytes memory) { 68 | return _vout.ref(0).tryAsVout().assertValid().indexVout(_index).clone(); 69 | } 70 | 71 | function valueBytes(bytes memory _output) public pure returns (bytes8) { 72 | return _output.ref(uint40(ViewBTC.BTCTypes.TxOut)).valueBytes(); 73 | } 74 | 75 | function extractValue(bytes memory _output) public pure returns (uint64) { 76 | return _output.ref(uint40(ViewBTC.BTCTypes.TxOut)).value(); 77 | } 78 | 79 | function opReturnPayload(bytes memory _output) public view returns (bytes memory) { 80 | // the argument is a txout. we want to slice off the first 8 bytes (the value) 81 | bytes29 v = _output.ref(0); 82 | bytes29 res = v.postfix(v.len() - 8, uint40(ViewBTC.BTCTypes.ScriptPubkey)).opReturnPayload(); 83 | bytes memory nullVal; 84 | if (res.isNull()) {return nullVal;} 85 | return res.clone(); 86 | } 87 | 88 | function payload(bytes memory _output) public view returns (bytes memory) { 89 | // the argument is a txout. we want to slice off the first 8 bytes (the value) 90 | bytes29 v = _output.ref(0); 91 | bytes29 res = v.postfix(v.len() - 8, uint40(ViewBTC.BTCTypes.ScriptPubkey)).payload(); 92 | bytes memory nullVal; 93 | if (res.isNull()) {return nullVal;} 94 | return res.clone(); 95 | } 96 | 97 | function tryAsVin(bytes memory _vin) public pure returns (bool) { 98 | return _vin.ref(0).tryAsVin().isValid(); 99 | } 100 | 101 | function tryAsVout(bytes memory _vout) public pure returns (bool) { 102 | return _vout.ref(0).tryAsVout().isValid(); 103 | } 104 | 105 | function merkleRoot(bytes memory _header) public pure returns (bytes32) { 106 | return _header.ref(0).tryAsHeader().assertValid().merkleRoot(); 107 | } 108 | 109 | function target(bytes memory _header) public pure returns (uint256) { 110 | return _header.ref(0).tryAsHeader().assertValid().target(); 111 | } 112 | 113 | function diff(bytes memory _header) public pure returns (uint256) { 114 | return _header.ref(0).tryAsHeader().assertValid().diff(); 115 | } 116 | 117 | function time(bytes memory _header) public pure returns (uint256) { 118 | return _header.ref(0).tryAsHeader().assertValid().time(); 119 | } 120 | 121 | function parent(bytes memory _header) public pure returns (bytes32) { 122 | return _header.ref(0).tryAsHeader().assertValid().parent(); 123 | } 124 | 125 | function work(bytes memory _header) public view returns (uint256) { 126 | return _header.ref(0).tryAsHeader().assertValid().work(); 127 | } 128 | 129 | function workHash(bytes memory _header) public view returns (bytes32) { 130 | return _header.ref(0).tryAsHeader().assertValid().workHash(); 131 | } 132 | 133 | function verifyHash256Merkle(bytes memory _proof, uint _index) public view returns (bool) { 134 | bytes29 _proof_ref = _proof.ref(0).tryAsMerkleArray(); 135 | bytes29 _nodes; 136 | bytes32 _leaf; 137 | bytes32 _root; 138 | 139 | if (_proof.length == 32) { 140 | _nodes = _nodes.castTo(uint40(ViewBTC.BTCTypes.MerkleArray)); 141 | _leaf = _proof_ref.index(0,32); 142 | _root = _proof_ref.index(0,32); 143 | } else if (!_proof_ref.isValid() || _proof.length < 64) { 144 | return false; 145 | } else { 146 | _nodes = _proof_ref.slice(32, _proof.length - 64, 0).tryAsMerkleArray().assertValid(); 147 | _leaf = _proof_ref.index(0, 32); 148 | _root = _proof_ref.index(_proof.length - 32, 32); 149 | } 150 | 151 | return ViewBTC.checkMerkle(_leaf, _nodes, _root, _index); 152 | } 153 | 154 | function retargetAlgorithm( 155 | uint256 _previousTarget, 156 | uint256 _firstTimestamp, 157 | uint256 _secondTimestamp 158 | ) public pure returns (uint256) { 159 | return ViewBTC.retargetAlgorithm(_previousTarget, _firstTimestamp, _secondTimestamp); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /solidity/contracts/test/ViewSPVtest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.5.10; 2 | 3 | /** @title ViewSPV */ 4 | /** @author Summa (https://summa.one) */ 5 | 6 | import {TypedMemView} from "@summa-tx/memview.sol/contracts/TypedMemView.sol"; 7 | import {ViewBTC} from "../ViewBTC.sol"; 8 | import {ViewSPV} from "../ViewSPV.sol"; 9 | 10 | contract ViewSPVTest { 11 | using TypedMemView for bytes; 12 | using TypedMemView for bytes29; 13 | using ViewBTC for bytes29; 14 | using ViewSPV for bytes29; 15 | 16 | function getErrBadLength() public pure returns (uint256) { 17 | return ViewSPV.getErrBadLength(); 18 | } 19 | 20 | function getErrInvalidChain() public pure returns (uint256) { 21 | return ViewSPV.getErrInvalidChain(); 22 | } 23 | 24 | function getErrLowWork() public pure returns (uint256) { 25 | return ViewSPV.getErrLowWork(); 26 | } 27 | 28 | /// @notice Valides a tx inclusion in the block 29 | /// @param _txid The txid (LE) 30 | /// @param _merkleRoot The merkle root 31 | /// @param _proof The proof (concatenated LE hashes) 32 | /// @param _index The proof index 33 | /// @return true if fully valid, false otherwise 34 | function prove( 35 | bytes32 _txid, 36 | bytes32 _merkleRoot, 37 | bytes memory _proof, 38 | uint _index 39 | ) public view returns (bool) { 40 | bytes29 _proof_ref = _proof.ref(0).tryAsMerkleArray(); 41 | return ViewSPV.prove(_txid, _merkleRoot, _proof_ref, _index); 42 | } 43 | 44 | /// @notice Hashes transaction to get txid 45 | /// @dev This supports legacy now 46 | /// @param _version 4-bytes version 47 | /// @param _vin Raw bytes length-prefixed input vector 48 | /// @param _vout Raw bytes length-prefixed output vector 49 | /// @ param _locktime 4-byte tx locktime 50 | /// @return 32-byte transaction id, little endian 51 | function calculateTxId( 52 | bytes4 _version, 53 | bytes memory _vin, 54 | bytes memory _vout, 55 | bytes4 _locktime 56 | ) public view returns (bytes32) { 57 | bytes29 _ins = _vin.ref(0).tryAsVin().assertValid(); 58 | bytes29 _outs = _vout.ref(0).tryAsVout().assertValid(); 59 | return ViewSPV.calculateTxId(_version, _ins, _outs, _locktime); 60 | } 61 | 62 | /// @notice Checks validity of header work 63 | /// @param _header Header view 64 | /// @param _target The target threshold 65 | /// @return true if header work is valid, false otherwise 66 | function checkWork(bytes memory _header, uint256 _target) public view returns (bool) { 67 | return _header.ref(0).tryAsHeader().assertValid().checkWork(_target); 68 | } 69 | 70 | /// @notice Checks validity of header chain 71 | /// @notice Compares the hash of each header to the prevHash in the next header 72 | /// @param _headers Raw byte array of header chain 73 | /// @return The total accumulated difficulty of the header chain 74 | function checkChain(bytes memory _headers) public view returns (uint256 _reqDiff) { 75 | return _headers.ref(0).tryAsHeaderArray().assertValid().checkChain(); 76 | } 77 | 78 | /// @notice Checks validity of header chain 79 | /// @notice Compares the hash of each header to the prevHash in the next header 80 | /// @param _headers Raw byte array of header chain 81 | /// @return The total accumulated difficulty of the header chain 82 | function checkChainTx(bytes memory _headers) public view returns (uint256 _reqDiff) { 83 | return _headers.ref(0).tryAsHeaderArray().assertValid().checkChain(); 84 | } 85 | 86 | /// @notice Checks validity of header chain 87 | /// @dev Compares current header prevHash to previous header's digest 88 | /// @param _header The raw bytes header 89 | /// @param _prevHeaderDigest The previous header's digest 90 | /// @return true if header chain is valid, false otherwise 91 | function checkParent(bytes memory _header, bytes32 _prevHeaderDigest) public pure returns (bool) { 92 | return _header.ref(0).tryAsHeader().assertValid().checkParent(_prevHeaderDigest); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /solidity/migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | const Migrations = artifacts.require("Migrations"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /solidity/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@summa-tx/bitcoin-spv-sol", 3 | "version": "4.0.1", 4 | "description": "bitcoin SPV proofs in Solidity", 5 | "scripts": { 6 | "compile": "truffle compile", 7 | "test": "cp ../testVectors.json test/ && truffle test", 8 | "test:coverage": "cp ../testVectors.json test/ && truffle run coverage", 9 | "lint": "solium -d contracts/ && eslint ./test", 10 | "lint:fix": "solium --fix -d contracts/ && eslint --fix ./test" 11 | }, 12 | "author": "", 13 | "license": "(MIT OR Apache-2.0)", 14 | "devDependencies": { 15 | "bn.js": "^5.0.0", 16 | "eslint": "^5.16.0", 17 | "eslint-config-airbnb-base": "^13.2.0", 18 | "eslint-plugin-import": "^2.18.0", 19 | "eth-gas-reporter": "^0.2.8", 20 | "solidity-coverage": "^0.7.10", 21 | "solidity-docgen": "^0.3.0-beta.4", 22 | "solium": "^1.2.4", 23 | "truffle": "^5.0.32" 24 | }, 25 | "dependencies": { 26 | "@summa-tx/memview.sol": "^1.0.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /solidity/test/ViewSPV.test.js: -------------------------------------------------------------------------------- 1 | /* global artifacts contract describe before it assert */ 2 | /* eslint-disable no-underscore-dangle */ 3 | const BN = require('bn.js'); 4 | 5 | /* eslint-disable-next-line no-unresolved */ 6 | const vectors = require('./testVectors.json'); 7 | 8 | const ViewSPV = artifacts.require('ViewSPVTest'); 9 | 10 | const { 11 | getErrBadLength, 12 | getErrInvalidChain, 13 | getErrLowWork, 14 | prove, 15 | calculateTxId, 16 | checkWork, 17 | validateHeaderChain, 18 | validateHeaderChainError, 19 | validateHeaderPrevHash 20 | } = vectors; 21 | 22 | 23 | contract('ViewSPV', () => { 24 | let instance; 25 | 26 | before(async () => { 27 | instance = await ViewSPV.new(); 28 | }); 29 | 30 | describe('#error constants', async () => { 31 | it('tests the constant getters for that sweet sweet coverage', async () => { 32 | let res = await instance.getErrBadLength.call(); 33 | assert(res.eq(new BN(getErrBadLength[0].output, 16))); 34 | 35 | res = await instance.getErrInvalidChain.call(); 36 | assert(res.eq(new BN(getErrInvalidChain[0].output, 16))); 37 | 38 | res = await instance.getErrLowWork.call(); 39 | assert(res.eq(new BN(getErrLowWork[0].output, 16))); 40 | }); 41 | }); 42 | 43 | describe('#prove', async () => { 44 | it('returns true if proof is valid', async () => { 45 | for (let i = 0; i < prove.length; i += 1) { 46 | const { 47 | txIdLE, merkleRootLE, proof, index 48 | } = prove[i].input; 49 | const res = await instance.prove(txIdLE, merkleRootLE, proof, index); 50 | assert.strictEqual(res, prove[i].output); 51 | } 52 | }); 53 | }); 54 | 55 | describe('#calculateTxId', async () => { 56 | it('returns the transaction hash', async () => { 57 | for (let i = 0; i < calculateTxId.length; i += 1) { 58 | const { 59 | version, vin, vout, locktime 60 | } = calculateTxId[i].input; 61 | const res = await instance.calculateTxId.call(version, vin, vout, locktime); 62 | assert.strictEqual(res, calculateTxId[i].output); 63 | } 64 | }); 65 | }); 66 | 67 | describe('#checkWork', async () => { 68 | it('Checks validity of header work', async () => { 69 | for (let i = 0; i < checkWork.length; i += 1) { 70 | const res = await instance.checkWork( 71 | checkWork[i].input.header, 72 | checkWork[i].input.target 73 | ); 74 | assert.strictEqual(res, checkWork[i].output); 75 | } 76 | }); 77 | }); 78 | 79 | describe('#checkChain', async () => { 80 | it('returns true if header chain is valid', async () => { 81 | for (let i = 0; i < validateHeaderChain.length; i += 1) { 82 | const res = await instance.checkChain.call(validateHeaderChain[i].input); 83 | const expected = new BN(validateHeaderChain[i].output, 10); 84 | assert( 85 | res.eq(expected), 86 | `expected ${expected.toString(16)} got ${res.toString(16)}` 87 | 88 | ); 89 | } 90 | }); 91 | 92 | it('returns error if header chain is invalid', async () => { 93 | for (let i = 0; i < validateHeaderChainError.length; i += 1) { 94 | if (validateHeaderChainError[i].solidityViewError) { 95 | try { 96 | await instance.checkChain(validateHeaderChainError[i].input); 97 | assert(false, 'expected an error'); 98 | } catch (e) { 99 | assert.include(e.message, validateHeaderChainError[i].solidityViewError); 100 | } 101 | } else { 102 | const res = await instance.checkChain(validateHeaderChainError[i].input); 103 | const expected = new BN(validateHeaderChainError[i].solidityError, 16); 104 | assert( 105 | res.eq(expected), 106 | `expected ${expected.toString(16)} got ${res.toString(16)}` 107 | ); 108 | } 109 | } 110 | }); 111 | }); 112 | 113 | describe('#checkParent', async () => { 114 | it('returns true if header prevHash is valid', async () => { 115 | for (let i = 0; i < validateHeaderPrevHash.length; i += 1) { 116 | const res = await instance.checkParent.call( 117 | validateHeaderPrevHash[i].input.header, 118 | validateHeaderPrevHash[i].input.prevHash 119 | ); 120 | assert.strictEqual(res, validateHeaderPrevHash[i].output); 121 | } 122 | }); 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /solidity/test/utils.js: -------------------------------------------------------------------------------- 1 | const BN = require('bn.js'); 2 | 3 | module.exports = { 4 | 5 | OUTPUT_TYPES: { 6 | NONE: new BN(0, 10), 7 | WPKH: new BN(1, 10), 8 | WSH: new BN(2, 10), 9 | OP_RETURN: new BN(3, 10), 10 | PKH: new BN(4, 10), 11 | SH: new BN(5, 10), 12 | NONSTANDARD: new BN(6, 10) 13 | }, 14 | 15 | INPUT_TYPES: { 16 | NONE: new BN('0', 10), 17 | LEGACY: new BN('1', 10), 18 | COMPATIBILITY: new BN('2', 10), 19 | WITNESS: new BN('3', 10) 20 | } 21 | }; 22 | 23 | 24 | // Some code is used under the following license: 25 | // The MIT License (MIT) 26 | // 27 | // Copyright (c) 2016 Smart Contract Solutions, Inc. 28 | // 29 | // Permission is hereby granted, free of charge, to any person obtaining 30 | // a copy of this software and associated documentation files (the 31 | // "Software"), to deal in the Software without restriction, including 32 | // without limitation the rights to use, copy, modify, merge, publish, 33 | // distribute, sublicense, and/or sell copies of the Software, and to 34 | // permit persons to whom the Software is furnished to do so, subject to 35 | // the following conditions: 36 | // 37 | // The above copyright notice and this permission notice shall be included 38 | // in all copies or substantial portions of the Software. 39 | // 40 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 41 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 42 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 43 | // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 44 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 45 | // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 46 | // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 47 | -------------------------------------------------------------------------------- /solidity/truffle-config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // const HDWalletProvider = require('truffle-hdwallet-provider'); 4 | // const infuraKey = "fj4jll3k....."; 5 | // 6 | // const fs = require('fs'); 7 | // const mnemonic = fs.readFileSync(".secret").toString().trim(); 8 | 9 | module.exports = { 10 | plugins: ["solidity-coverage"], 11 | networks: { 12 | // // Uncomment this to use a local ganache instance 13 | // // Useful for running the debugger on specific transactions 14 | // development: { 15 | // host: "127.0.0.1", // Localhost (default: none) 16 | // port: 8545, // Standard Ethereum port (default: none) 17 | // network_id: "*", // Any network (default: none) 18 | // }, 19 | 20 | coverage: { 21 | host: "localhost", 22 | network_id: "*", 23 | port: 8555, // <-- If you change this, also set the port option in .solcover.js. 24 | gas: 0xfffffffffff, // <-- Use this high gas value 25 | gasPrice: 0x01 // <-- Use this low gas price 26 | }, 27 | }, 28 | 29 | mocha: { 30 | useColors: true, 31 | reporter: 'eth-gas-reporter', 32 | reporterOptions : { 33 | excludeContracts: [], 34 | currency: 'USD', 35 | gasPrice: 10 36 | } 37 | }, 38 | 39 | compilers: { 40 | solc: { 41 | version: "0.5.10", // Fetch exact version from solc-bin (default: truffle's version) 42 | // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) 43 | settings: { // See the solidity docs for advice about optimization and evmVersion 44 | optimizer: { 45 | enabled: true, 46 | runs: 200 47 | } 48 | // evmVersion: "byzantium" 49 | } 50 | } 51 | } 52 | } 53 | --------------------------------------------------------------------------------