├── .github └── workflows │ ├── format.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── Makefile ├── README.md ├── docs ├── bitcoin │ └── block_header_validation_rules.md └── img │ └── khepri-architecture.png ├── protostar.toml ├── requirements.txt ├── resources ├── blocks │ ├── b170.json │ ├── b746298.json │ ├── block0.json │ └── block1.json └── tx │ ├── p2pkh02.json │ ├── p2pkh03.json │ └── p2pkh04.json ├── scripts ├── .gitkeep └── utils │ ├── check_hash_blocks_0_and_1.py │ └── header_to_cairo.py └── src ├── bitcoin └── params.cairo ├── header ├── block_header_verifier.cairo ├── library.cairo ├── model.cairo ├── rules │ ├── check_pow.cairo │ ├── median_past_time.cairo │ ├── previous_block_hash.cairo │ ├── target.cairo │ ├── test_check_pow.cairo │ ├── test_median_past_time.cairo │ ├── test_previous_block_hash.cairo │ └── test_target.cairo ├── storage.cairo ├── test_block_header_verifier.cairo ├── test_model.cairo ├── test_storage.cairo └── test_utils.cairo ├── tx ├── ecdsa256k1.cairo ├── merkle_root.cairo ├── model.cairo ├── test_ecdsa256k1.cairo ├── test_merkle.cairo └── test_utils.cairo └── utils ├── array.cairo ├── bits.cairo ├── common.cairo ├── math.cairo ├── sha256.cairo ├── target.cairo └── test_target.cairo /.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: Format 2 | 3 | on: push 4 | 5 | jobs: 6 | cairo: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | - uses: actions/setup-python@v3 11 | with: 12 | python-version: "3.9" 13 | cache: "pip" 14 | - name: Install dependencies 15 | run: | 16 | pip install cairo-lang==0.9.1 17 | - name: Check files formatting 18 | run: cairo-format -c src/**/*.cairo 19 | python: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | - uses: actions/setup-python@v3 24 | with: 25 | python-version: "3.9" 26 | cache: "pip" 27 | - name: Install dependencies 28 | run: | 29 | pip install black 30 | - name: Check files formatting 31 | run: black --check scripts 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | unit-tests: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Install protostar 14 | run: | 15 | curl -L https://raw.githubusercontent.com/software-mansion/protostar/master/install.sh | bash -s -- -v 0.3.2 16 | - name: Update env variables 17 | run: | 18 | source /home/runner/.bashrc | bash 19 | - name: Install protostar dependencies 20 | run: /home/runner/.protostar/dist/protostar/protostar install 21 | - name: Run protostar tests 22 | run: /home/runner/.protostar/dist/protostar/protostar test src 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .vscode 3 | 4 | # Python 5 | __pycache__ 6 | env 7 | 8 | # starknet 9 | node.json 10 | *.deployments.txt 11 | artifacts 12 | 13 | # nile 14 | 127.0.0.1.* 15 | 16 | # Protostar 17 | build 18 | 19 | .DS_Store 20 | 21 | .env -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cairo_contracts"] 2 | url = https://github.com/OpenZeppelin/cairo-contracts 3 | path = lib/cairo_contracts 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 bitcoin-stark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build test 2 | 3 | build: 4 | protostar build --disable-hint-validation 5 | 6 | test: 7 | protostar test src 8 | 9 | date: 10 | date 11 | 12 | format: 13 | black scripts 14 | cairo-format -i src/**/*.cairo 15 | 16 | clean: 17 | rm -Rf build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Khepri 2 | 3 | STARK-proven Stateful Bitcoin client enabling hyper fast trustless sync and checkpoints. 4 | 5 | ## Architecture 6 | 7 | ![Khepri Architecture](docs/img/khepri-architecture.png) 8 | 9 | ## Description 10 | 11 | The idea of Khepri is to leverage Stark proof system in order to solve some issues in Bitcoin ecosystem or at least enhance the user experience. 12 | 13 | With Stark proofs and Cairo, we have the ability to verify the integrity of a computation without having to naively redo all computation. Hence a verifier can be sure of the honest execution of a program, without the need of executing it and check the result. 14 | 15 | The verification of the proof is exponentially faster than the naive execution of the program, which provide scalability. 16 | 17 | Moreover, the overhead of generating the proof is negligible compared to the normal execution without the generation of the proof. 18 | 19 | This mechanism can enable a lot of potential optimizations on several use cases. 20 | 21 | What kind of problems can we solve with this super power ? Well, there are so many issues we could imagine to tackle with this approach, here are some cool features that could be enabled with this system: 22 | 23 | - hyper fast & trustless synchronization (IBD) 24 | - enhanced Simplified Payment Verification 25 | - protection against DoS on the P2P layer 26 | 27 | In short, Khepri is a proof of concept to demonstrate how STARK proofs can be leveraged for Bitcoin without changing the protocol. 28 | 29 | ## Usage 30 | 31 | > ## ⚠️ WARNING! ⚠️ 32 | > 33 | > This is repo contains highly experimental code. 34 | > Expect rapid iteration. 35 | > **Use at your own risk.** 36 | 37 | ### Set up the project 38 | 39 | #### 📦 Install the requirements 40 | 41 | - [protostar 0.3.2 or above](https://docs.swmansion.com/protostar/docs/tutorials/installation) 42 | 43 | ### ⛏️ Compile 44 | 45 | ```bash 46 | protostar build 47 | ``` 48 | 49 | ### 🌡️ Test 50 | 51 | ```bash 52 | protostar test 53 | ``` 54 | 55 | ## 📄 License 56 | 57 | **khepri** is released under the [MIT](LICENSE). 58 | -------------------------------------------------------------------------------- /docs/bitcoin/block_header_validation_rules.md: -------------------------------------------------------------------------------- 1 | # Headers validation 2 | 3 | See 4 | 5 | Note: the genesis block is treated separately: if the current block hash is equal to the genesis block hash, then all checks are bypassed. 6 | 7 | This documentation keeps the order of checks as they are done in bitcoin core. 8 | 9 | The "🙈" emoji indicates checks or specificities that can be bypassed if we don't handle reorgs. 10 | 11 | ## Structure 12 | 13 | A bitcoin block header is composed of the following elements: 14 | 15 | - version 16 | - previous block hash 17 | - merkle root 18 | - timestamp 19 | - difficulty target (encoded in bits) 20 | - nonce 21 | 22 | ## Look for duplicates 23 | 24 | This is not a validation per se, but the algo starts by checking if the block is already known. 25 | If it is known, there is nothing more to do for us. 26 | 27 | ## Check proof of work 28 | 29 | [Issue](https://github.com/bitcoin-stark/khepri-starknet/issues/13) 30 | 31 | Check proof of work matches claimed amount. In other words, check that the proof of work is lower than (or equal) the target which is specified in the header `bits` field. 32 | 33 | ## Check previous block 34 | 35 | [PR](https://github.com/bitcoin-stark/khepri-starknet/pull/19) 36 | 37 | The hash of the previous block must point to a valid block that is already on the chain. 38 | 39 | 🙈 In a general case, the previous block doesn't has to be the last block (the block with the highest height), because there might be 40 | a reorg. If we don't want to handle reorgs for now, then it must strictly be the block with the highest height. 41 | 42 | ## Context-dependent validity checks 43 | 44 | > By "context", we mean only the previous block headers, but not the UTXO set; UTXO-related validity checks are done in ConnectBlock(). 45 | 46 | See 47 | 48 | ### Check proof of work target (bits) 49 | 50 | [Issue](https://github.com/bitcoin-stark/khepri-starknet/issues/11) 51 | 52 | Check that the target (ie. the field `bits` of the header) is valid. 53 | 54 | See 55 | 56 | See the implementation for full details, but basically the equation for retargeting difficulty measures the time it took to find the last 2,016 blocks and compares that to the expected time of 20,160 minutes (two weeks based upon a desired 10-minute block time). The ratio between the actual timespan and desired timespan is calculated and a corresponding adjustment (up or down) is made to the difficulty. 57 | 58 | `New Difficulty = Old Difficulty * (Actual Time of Last 2016 Blocks / 20160 minutes)` 59 | 60 | ### Check against checkpoints 🙈 61 | 62 | Don't accept any forks from the main chain prior to last checkpoint. Useless if we don't treat reorgs. 63 | 64 | See 65 | 66 | ### Check timestamp against prev 67 | 68 | [Issue](https://github.com/bitcoin-stark/khepri-starknet/issues/7) 69 | 70 | The famous "Median Past Time" rule where the clock's timestamp must be higher than the median of the previous 11 timestamps. 71 | 72 | See 73 | 74 | ### Check timestamp 75 | 76 | [Issue](https://github.com/bitcoin-stark/khepri-starknet/issues/26) 77 | 78 | The block timestamp cannot be more than 2 hours in the future based on the MAX_FUTURE_BLOCK_TIME constant, 79 | relative to the adjusted time (the median time from the node’s peers). 80 | 81 | See 82 | 83 | ### Reject blocks with outdated version 84 | 85 | [Issue](https://github.com/bitcoin-stark/khepri-starknet/issues/25) 86 | 87 | The block version must be higher than a given value depending on its height. 88 | 89 | See 90 | 91 | ## Check ancestors 🙈 92 | 93 | This checks must be done if we want to support reorgs. If we don't support reorgs, and if we checked that the 94 | previous block hash is the one of the last blockchain block, those checks don't seem necessary. 95 | 96 | ```cpp 97 | /* Determine if this block descends from any block which has been found 98 | * invalid (m_failed_blocks), then mark pindexPrev and any blocks between 99 | * them as failed. For example: 100 | * 101 | * D3 102 | * / 103 | * B2 - C2 104 | * / \ 105 | * A D2 - E2 - F2 106 | * \ 107 | * B1 - C1 - D1 - E1 108 | * 109 | * In the case that we attempted to reorg from E1 to F2, only to find 110 | * C2 to be invalid, we would mark D2, E2, and F2 as BLOCK_FAILED_CHILD 111 | * but NOT D3 (it was not in any of our candidate sets at the time). 112 | * 113 | * In any case D3 will also be marked as BLOCK_FAILED_CHILD at restart 114 | * in LoadBlockIndex. 115 | */ 116 | ``` 117 | 118 | See 119 | -------------------------------------------------------------------------------- /docs/img/khepri-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoin-stark/khepri/c1732a631cf6b1f95b8f688b3fcf9ba791166a6b/docs/img/khepri-architecture.png -------------------------------------------------------------------------------- /protostar.toml: -------------------------------------------------------------------------------- 1 | ["protostar.config"] 2 | protostar_version = "0.3.2" 3 | 4 | ["protostar.project"] 5 | libs_path = "lib" 6 | 7 | ["protostar.shared_command_configs"] 8 | cairo_path = ["./lib/cairo_contracts/src", "src"] 9 | 10 | ["protostar.test"] 11 | target = ["src"] 12 | 13 | ["protostar.contracts"] 14 | block_header_verifier = [ 15 | "./src/header/block_header_verifier.cairo", 16 | ] 17 | 18 | # https://github.com/Shard-Labs/starknet-devnet 19 | [profile.devnet.protostar.deploy] 20 | gateway_url="http://127.0.0.1:5050/" 21 | 22 | [profile.testnet.protostar.deploy] 23 | network="alpha-goerli" 24 | 25 | [profile.mainnet.protostar.deploy] 26 | network="alpha-mainnet" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cairo-lang 2 | git+https://github.com/OpenZeppelin/cairo-contracts.git 3 | marshmallow-dataclass==8.5.3 4 | black 5 | pylint -------------------------------------------------------------------------------- /resources/blocks/b170.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash":"00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee", 3 | "confirmations":735792, 4 | "height":170, 5 | "version":1, 6 | "versionHex":"00000001", 7 | "merkleroot":"7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff", 8 | "time":1231731025, 9 | "mediantime":1231716245, 10 | "nonce":1889418792, 11 | "bits":"1d00ffff", 12 | "difficulty":1, 13 | "chainwork":"000000000000000000000000000000000000000000000000000000ab00ab00ab", 14 | "nTx":2, 15 | "previousblockhash":"000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55", 16 | "nextblockhash":"00000000c9ec538cab7f38ef9c67a95742f56ab07b0a37c5be6b02808dbfb4e0", 17 | "strippedsize":490, 18 | "size":490, 19 | "weight":1960, 20 | "tx":[ 21 | "b1fea52486ce0c62bb442b530a3f0132b826c74e473d1f2c220bfa78111c5082", 22 | "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16" 23 | ] 24 | } -------------------------------------------------------------------------------- /resources/blocks/block0.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", 3 | "confirmations": 733487, 4 | "height": 0, 5 | "version": 1, 6 | "versionHex": "00000001", 7 | "merkleroot": "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b", 8 | "time": 1231006505, 9 | "mediantime": 1231006505, 10 | "nonce": 2083236893, 11 | "bits": "1d00ffff", 12 | "difficulty": 1, 13 | "chainwork": "0000000000000000000000000000000000000000000000000000000100010001", 14 | "nTx": 1, 15 | "nextblockhash": "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048", 16 | "strippedsize": 285, 17 | "size": 285, 18 | "weight": 1140, 19 | "tx": [ 20 | "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" 21 | ] 22 | } -------------------------------------------------------------------------------- /resources/blocks/block1.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048", 3 | "confirmations": 733486, 4 | "height": 1, 5 | "version": 1, 6 | "versionHex": "00000001", 7 | "merkleroot": "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098", 8 | "time": 1231469665, 9 | "mediantime": 1231469665, 10 | "nonce": 2573394689, 11 | "bits": "1d00ffff", 12 | "difficulty": 1, 13 | "chainwork": "0000000000000000000000000000000000000000000000000000000200020002", 14 | "nTx": 1, 15 | "previousblockhash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", 16 | "nextblockhash": "000000006a625f06636b8bb6ac7b960a8d03705d1ace08b1a19da3fdcc99ddbd", 17 | "strippedsize": 215, 18 | "size": 215, 19 | "weight": 860, 20 | "tx": [ 21 | "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098" 22 | ] 23 | } -------------------------------------------------------------------------------- /resources/tx/p2pkh02.json: -------------------------------------------------------------------------------- 1 | { 2 | "txid": "4269fdc239d027922dcec96f1ae283dbaff10e2d1bd49605661d091e79714956", 3 | "hash": "4269fdc239d027922dcec96f1ae283dbaff10e2d1bd49605661d091e79714956", 4 | "version": 1, 5 | "size": 224, 6 | "vsize": 224, 7 | "weight": 896, 8 | "locktime": 0, 9 | "vin": [ 10 | { 11 | "txid": "63769b3ff37f203f5a4007189e13fe2368585dd25698e7050c8e8390b70a957f", 12 | "vout": 1, 13 | "scriptSig": { 14 | "asm": "3045022100d8629403cd3b49950da9293653c6279149c029e6b7b15371342d0d2ce286c8f2022078787985a644e94fd9246f6c25733336c94af5f00d9d34a07dc2f9e0987ef990[ALL] 02b726d7eae11a6d5cf3b2362e773e116a6140347dcee1b2943f4a2897351e5d90", 15 | "hex": "483045022100d8629403cd3b49950da9293653c6279149c029e6b7b15371342d0d2ce286c8f2022078787985a644e94fd9246f6c25733336c94af5f00d9d34a07dc2f9e0987ef990012102b726d7eae11a6d5cf3b2362e773e116a6140347dcee1b2943f4a2897351e5d90" 16 | }, 17 | "sequence": 4294967295 18 | } 19 | ], 20 | "vout": [ 21 | { 22 | "value": 0.03993627, 23 | "n": 0, 24 | "scriptPubKey": { 25 | "asm": "OP_HASH160 69f3757380a56820abc7052867216599e575cddd OP_EQUAL", 26 | "hex": "a91469f3757380a56820abc7052867216599e575cddd87", 27 | "address": "3BMEXVvXXRFh2eJ9Eji115xfqJjWmLTCf8", 28 | "type": "scripthash" 29 | } 30 | }, 31 | { 32 | "value": 4.83049847, 33 | "n": 1, 34 | "scriptPubKey": { 35 | "asm": "OP_DUP OP_HASH160 d5f950abe0b559b2b7a7ab3d18a507ea1c3e4ac6 OP_EQUALVERIFY OP_CHECKSIG", 36 | "hex": "76a914d5f950abe0b559b2b7a7ab3d18a507ea1c3e4ac688ac", 37 | "address": "1LWPbaYN2jqhv9oZvYHxYKXuaiR1qJn52i", 38 | "type": "pubkeyhash" 39 | } 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /resources/tx/p2pkh03.json: -------------------------------------------------------------------------------- 1 | { 2 | "txid": "757b56e1d5072bb7cb9f8cffbac450cb93af63ec391378b4e73d12778cdfabdc", 3 | "hash": "757b56e1d5072bb7cb9f8cffbac450cb93af63ec391378b4e73d12778cdfabdc", 4 | "version": 1, 5 | "size": 223, 6 | "vsize": 223, 7 | "weight": 892, 8 | "locktime": 0, 9 | "vin": [ 10 | { 11 | "txid": "811f95e4eca43963b401eb0d0940ff1dc3f2249e0d68c9583077b8ecf6dae4e7", 12 | "vout": 0, 13 | "scriptSig": { 14 | "asm": "3044022012f5a907fa0385a780886be1c04d4bbd45d9d82377844e2b5249695e2b9112b0022058e9dd2cf072d3d595d1a6fa5c51ab1ee5f7cf9e8404d05b37ca9fcec73e779c[ALL] 0369e03e2c91f0badec46c9c903d9e9edae67c167b9ef9b550356ee791c9a40896", 15 | "hex": "473044022012f5a907fa0385a780886be1c04d4bbd45d9d82377844e2b5249695e2b9112b0022058e9dd2cf072d3d595d1a6fa5c51ab1ee5f7cf9e8404d05b37ca9fcec73e779c01210369e03e2c91f0badec46c9c903d9e9edae67c167b9ef9b550356ee791c9a40896" 16 | }, 17 | "sequence": 4294967295 18 | } 19 | ], 20 | "vout": [ 21 | { 22 | "value": 0.11630159, 23 | "n": 0, 24 | "scriptPubKey": { 25 | "asm": "OP_DUP OP_HASH160 9f21a07a0c7c3cf65a51f586051395762267cdaf OP_EQUALVERIFY OP_CHECKSIG", 26 | "hex": "76a9149f21a07a0c7c3cf65a51f586051395762267cdaf88ac", 27 | "address": "1FWQiwK27EnGXb6BiBMRLJvunJQZZPMcGd", 28 | "type": "pubkeyhash" 29 | } 30 | }, 31 | { 32 | "value": 0.09875988, 33 | "n": 1, 34 | "scriptPubKey": { 35 | "asm": "OP_HASH160 9a854477ae1f5fda4eddec287150aef752143aa1 OP_EQUAL", 36 | "hex": "a9149a854477ae1f5fda4eddec287150aef752143aa187", 37 | "address": "3Fn3dfqjrZnf9HEmffyrfbdkwVre3Y8mx8", 38 | "type": "scripthash" 39 | } 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /resources/tx/p2pkh04.json: -------------------------------------------------------------------------------- 1 | { 2 | "txid": "cca7507897abc89628f450e8b1e0c6fca4ec3f7b34cccf55f3f531c659ff4d79", 3 | "hash": "cca7507897abc89628f450e8b1e0c6fca4ec3f7b34cccf55f3f531c659ff4d79", 4 | "version": 1, 5 | "size": 300, 6 | "vsize": 300, 7 | "weight": 1200, 8 | "locktime": 0, 9 | "vin": [ 10 | { 11 | "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", 12 | "vout": 0, 13 | "scriptSig": { 14 | "asm": "30450221009908144ca6539e09512b9295c8a27050d478fbb96f8addbc3d075544dc41328702201aa528be2b907d316d2da068dd9eb1e23243d97e444d59290d2fddf25269ee0e[ALL] 042e930f39ba62c6534ee98ed20ca98959d34aa9e057cda01cfd422c6bab3667b76426529382c23f42b9b08d7832d4fee1d6b437a8526e59667ce9c4e9dcebcabb", 15 | "hex": "4830450221009908144ca6539e09512b9295c8a27050d478fbb96f8addbc3d075544dc41328702201aa528be2b907d316d2da068dd9eb1e23243d97e444d59290d2fddf25269ee0e0141042e930f39ba62c6534ee98ed20ca98959d34aa9e057cda01cfd422c6bab3667b76426529382c23f42b9b08d7832d4fee1d6b437a8526e59667ce9c4e9dcebcabb" 16 | }, 17 | "sequence": 4294967295 18 | } 19 | ], 20 | "vout": [ 21 | { 22 | "value": 5777, 23 | "n": 0, 24 | "scriptPubKey": { 25 | "asm": "OP_DUP OP_HASH160 df1bd49a6c9e34dfa8631f2c54cf39986027501b OP_EQUALVERIFY OP_CHECKSIG", 26 | "hex": "76a914df1bd49a6c9e34dfa8631f2c54cf39986027501b88ac", 27 | "address": "1MLh2UVHgonJY4ZtsakoXtkcXDJ2EPU6RY", 28 | "type": "pubkeyhash" 29 | } 30 | }, 31 | { 32 | "value": 4223, 33 | "n": 1, 34 | "scriptPubKey": { 35 | "asm": "04cd5e9726e6afeae357b1806be25a4c3d3811775835d235417ea746b7db9eeab33cf01674b944c64561ce3388fa1abd0fa88b06c44ce81e2234aa70fe578d455d OP_CHECKSIG", 36 | "hex": "4104cd5e9726e6afeae357b1806be25a4c3d3811775835d235417ea746b7db9eeab33cf01674b944c64561ce3388fa1abd0fa88b06c44ce81e2234aa70fe578d455dac", 37 | "type": "pubkey" 38 | } 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /scripts/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoin-stark/khepri/c1732a631cf6b1f95b8f688b3fcf9ba791166a6b/scripts/.gitkeep -------------------------------------------------------------------------------- /scripts/utils/check_hash_blocks_0_and_1.py: -------------------------------------------------------------------------------- 1 | import json 2 | from hashlib import sha256 3 | from binascii import unhexlify 4 | 5 | with open("resources/blocks/block0.json") as block0_file: 6 | block0 = json.load(block0_file) 7 | with open("resources/blocks/block1.json") as block1_file: 8 | block1 = json.load(block1_file) 9 | 10 | ENDIANNESS = "little" 11 | 12 | 13 | def to_bytes(string, unhexlify=True): 14 | if not string: 15 | return b"" 16 | if unhexlify: 17 | try: 18 | if isinstance(string, bytes): 19 | string = string.decode() 20 | s = bytes.fromhex(string) 21 | return s 22 | except (TypeError, ValueError): 23 | pass 24 | if isinstance(string, bytes): 25 | return string 26 | else: 27 | return bytes(string, "utf8") 28 | 29 | 30 | def double_sha256(string, as_hex=False): 31 | if not as_hex: 32 | return sha256(sha256(string).digest()).digest() 33 | else: 34 | return sha256(sha256(string).digest()).hexdigest() 35 | 36 | 37 | def big_to_little_endian(s): 38 | return bytes.fromhex(s)[::-1].hex() 39 | 40 | 41 | def verifyBlock(block): 42 | versionHex = big_to_little_endian(block["versionHex"]) 43 | previousBlockHashHex = ( 44 | big_to_little_endian(block["previousblockhash"]) 45 | if "previousblockhash" in block 46 | else (0).to_bytes(32, ENDIANNESS).hex() 47 | ) 48 | merkleRootHex = big_to_little_endian(block["merkleroot"]) 49 | timeHex = block["time"].to_bytes(4, ENDIANNESS).hex() 50 | bitsHex = big_to_little_endian(block["bits"]) 51 | nonceB = block["nonce"].to_bytes(4, ENDIANNESS).hex() 52 | 53 | header_hex = ( 54 | versionHex + previousBlockHashHex + merkleRootHex + timeHex + bitsHex + nonceB 55 | ) 56 | print(len(header_hex), header_hex) 57 | header_bin = unhexlify(header_hex) 58 | hash = sha256(sha256(header_bin).digest()).digest() 59 | print(hash[::-1].hex()) 60 | 61 | 62 | def header_to_cairo(block): 63 | versionHex = big_to_little_endian(block["versionHex"]) 64 | previousBlockHashHex = ( 65 | big_to_little_endian(block["previousblockhash"]) 66 | if "previousblockhash" in block 67 | else (0).to_bytes(32, ENDIANNESS).hex() 68 | ) 69 | merkleRootHex = big_to_little_endian(block["merkleroot"]) 70 | timeHex = block["time"].to_bytes(4, ENDIANNESS).hex() 71 | bitsHex = big_to_little_endian(block["bits"]) 72 | nonceB = block["nonce"].to_bytes(4, ENDIANNESS).hex() 73 | 74 | header_hex = ( 75 | versionHex + previousBlockHashHex + merkleRootHex + timeHex + bitsHex + nonceB 76 | ) 77 | header_bin = unhexlify(header_hex) 78 | 79 | data = header_bin.hex() 80 | tmp = [int(data[8 * i : 8 * (i + 1)], 16) for i in range(160 // 8)] 81 | return tmp 82 | 83 | 84 | if __name__ == "__main__": 85 | print("Block 0 cairo header: ") 86 | print(header_to_cairo(block0)) 87 | print("Block 1 cairo header: ") 88 | print(header_to_cairo(block1)) 89 | 90 | verifyBlock(block0) 91 | print("Block 0 hash: ") 92 | print(block0["hash"]) 93 | verifyBlock(block1) 94 | print("Block 1 hash: ") 95 | print(block1["hash"]) 96 | -------------------------------------------------------------------------------- /scripts/utils/header_to_cairo.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | def hash_to_Uint256_cairo(hash_hex_str): 5 | low = hash_hex_str[32:] 6 | high = hash_hex_str[:32] 7 | return f"Uint256(low=0x{low}, high=0x{high})" 8 | 9 | 10 | def header_to_cairo(block_json): 11 | prev_block = ( 12 | block_json["previousblockhash"] 13 | if "previousblockhash" in block_json 14 | else "0" * 64 15 | ) 16 | hash = block_json["hash"] if "hash" in block_json else "0" * 64 17 | merkle_root = block_json["merkleroot"] if "merkleroot" in block_json else "0" * 64 18 | res = f"""\ 19 | BlockHeader( 20 | version= {block_json['version']}, 21 | prev_block={hash_to_Uint256_cairo(prev_block)}, 22 | merkle_root={hash_to_Uint256_cairo(merkle_root)}, 23 | timestamp={block_json['time']}, 24 | bits=0x{block_json['bits']}, 25 | nonce={block_json['nonce']}, 26 | hash={hash_to_Uint256_cairo(hash)}, 27 | )\ 28 | """ 29 | return res 30 | 31 | 32 | def main(): 33 | import sys 34 | 35 | input_name = sys.argv[1] 36 | 37 | if input_name is None: 38 | return 39 | 40 | with open(input_name) as block_file: 41 | block = json.load(block_file) 42 | 43 | res = header_to_cairo(block) 44 | print(f"let header: BlockHeader = {res}") 45 | 46 | 47 | if __name__ == "__main__": 48 | main() 49 | -------------------------------------------------------------------------------- /src/bitcoin/params.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.uint256 import Uint256 4 | from utils.math import felt_to_Uint256 5 | 6 | struct Params: 7 | member pow_limit : Uint256 8 | member pow_target_timespan : felt 9 | member pow_target_timespan_div_by_4 : felt 10 | member pow_target_timespan_mul_by_4 : felt 11 | member difficulty_adjustment_interval : felt 12 | end 13 | 14 | func get_params{range_check_ptr}() -> (params : Params): 15 | let (pow_limit : Uint256) = felt_to_Uint256( 16 | 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff 17 | ) 18 | return ( 19 | params=Params( 20 | pow_limit=pow_limit, 21 | pow_target_timespan=14 * 24 * 60 * 60, 22 | pow_target_timespan_div_by_4=14 * 24 * 60 * 15, 23 | pow_target_timespan_mul_by_4=14 * 24 * 60 * 60 * 4, 24 | difficulty_adjustment_interval=2016 25 | ), 26 | ) 27 | end 28 | -------------------------------------------------------------------------------- /src/header/block_header_verifier.cairo: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Khepri smart contracts written in Cairo v0.1.0 (block_header_verifier.cairo) 3 | 4 | %lang starknet 5 | 6 | from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin 7 | from starkware.cairo.common.uint256 import Uint256 8 | 9 | from header.model import BlockHeader 10 | from header.library import BlockHeaderVerifier 11 | from header.storage import storage 12 | 13 | # ------ 14 | # CONSTRUCTOR 15 | # ------ 16 | 17 | @constructor 18 | func constructor{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(): 19 | return BlockHeaderVerifier.constructor() 20 | end 21 | 22 | # ----- 23 | # VIEWS 24 | # ----- 25 | 26 | @view 27 | func block_header_hash{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 28 | block_height 29 | ) -> (block_header_hash : Uint256): 30 | return storage.block_header_hash(block_height) 31 | end 32 | 33 | @view 34 | func block_header_by_hash{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 35 | block_header_hash : Uint256 36 | ) -> (block_header : BlockHeader): 37 | return storage.block_header_by_hash(block_header_hash) 38 | end 39 | 40 | @view 41 | func block_header_by_height{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 42 | block_height 43 | ) -> (block_header : BlockHeader): 44 | return storage.block_header_by_height(block_height) 45 | end 46 | 47 | # ------------------ 48 | # EXTERNAL FUNCTIONS 49 | # ------------------ 50 | 51 | @external 52 | func process_block{ 53 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, bitwise_ptr : BitwiseBuiltin*, range_check_ptr 54 | }(data_len : felt, data : felt*): 55 | return BlockHeaderVerifier.process_block(data_len, data) 56 | end 57 | -------------------------------------------------------------------------------- /src/header/library.cairo: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Khepri smart contracts written in Cairo v0.1.0 (header/library.cairo) 3 | 4 | %lang starknet 5 | # Starkware dependencies 6 | from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin 7 | from starkware.cairo.common.alloc import alloc 8 | from starkware.cairo.common.uint256 import Uint256, uint256_lt, uint256_eq 9 | from starkware.cairo.common.bool import TRUE, FALSE 10 | from starkware.cairo.common.math import split_felt, assert_not_equal, assert_not_zero 11 | from starkware.cairo.common.math_cmp import is_not_zero 12 | 13 | from utils.common import swap_endianness_64 14 | from utils.sha256 import sha256 15 | 16 | from header.model import BlockHeader, BlockHeaderValidationContext 17 | from header.storage import storage 18 | from header.rules.median_past_time import median_past_time 19 | from header.rules.check_pow import check_pow 20 | from header.rules.previous_block_hash import previous_block_hash 21 | from header.rules.target import target 22 | from bitcoin.params import Params, get_params 23 | 24 | namespace BlockHeaderVerifier: 25 | # ------ 26 | # CONSTRUCTOR 27 | # ------ 28 | 29 | func constructor{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(): 30 | let (genesis_block_header : BlockHeader) = internal.genesis_block_header() 31 | storage.unsafe_write_header(0, genesis_block_header) 32 | return () 33 | end 34 | 35 | # ------------------ 36 | # EXTERNAL FUNCTIONS 37 | # ------------------ 38 | 39 | func process_block{ 40 | syscall_ptr : felt*, 41 | pedersen_ptr : HashBuiltin*, 42 | bitwise_ptr : BitwiseBuiltin*, 43 | range_check_ptr, 44 | }(data_len : felt, data : felt*): 45 | alloc_locals 46 | 47 | let (last_height) = storage.current_height() 48 | 49 | # Verify provided block header 50 | let (local header) = internal.prepare_header(data) 51 | let (previous_block_header) = storage.block_header_by_height(last_height) 52 | let ctx = BlockHeaderValidationContext(last_height + 1, header, previous_block_header) 53 | internal.process_header(ctx) 54 | 55 | return () 56 | end 57 | end 58 | 59 | namespace internal: 60 | # Assuming data is the header packed as an array of 4 bytes 61 | func prepare_header{range_check_ptr, bitwise_ptr : BitwiseBuiltin*}(data : felt*) -> ( 62 | res : BlockHeader 63 | ): 64 | alloc_locals 65 | let (version) = swap_endianness_64(data[0], 4) 66 | 67 | let (prev0) = swap_endianness_64(data[7] * 2 ** 32 + data[8], 8) 68 | let (prev1) = swap_endianness_64(data[5] * 2 ** 32 + data[6], 8) 69 | let (prev2) = swap_endianness_64(data[3] * 2 ** 32 + data[4], 8) 70 | let (prev3) = swap_endianness_64(data[1] * 2 ** 32 + data[2], 8) 71 | 72 | local prev_block : Uint256 = Uint256( 73 | prev3 + prev2 * 2 ** 64, 74 | prev1 + prev0 * 2 ** 64, 75 | ) 76 | 77 | let (merkle0) = swap_endianness_64(data[15] * 2 ** 32 + data[16], 8) 78 | let (merkle1) = swap_endianness_64(data[13] * 2 ** 32 + data[14], 8) 79 | let (merkle2) = swap_endianness_64(data[11] * 2 ** 32 + data[12], 8) 80 | let (merkle3) = swap_endianness_64(data[09] * 2 ** 32 + data[10], 8) 81 | 82 | local merkle_root : Uint256 = Uint256( 83 | merkle3 + merkle2 * 2 ** 64, 84 | merkle1 + merkle0 * 2 ** 64, 85 | ) 86 | let (timestamp) = swap_endianness_64(data[17], 4) 87 | let (bits) = swap_endianness_64(data[18], 4) 88 | let (nonce) = swap_endianness_64(data[19], 4) 89 | 90 | let (single_sha) = sha256(data, 80 * 8) 91 | let (double_sha) = sha256(single_sha, 32 * 8) 92 | 93 | let (hash0) = swap_endianness_64(double_sha[6] * 2 ** 32 + double_sha[7], 8) 94 | let (hash1) = swap_endianness_64(double_sha[4] * 2 ** 32 + double_sha[5], 8) 95 | let (hash2) = swap_endianness_64(double_sha[2] * 2 ** 32 + double_sha[3], 8) 96 | let (hash3) = swap_endianness_64(double_sha[0] * 2 ** 32 + double_sha[1], 8) 97 | 98 | local header_hash : Uint256 = Uint256( 99 | hash3 + hash2 * 2 ** 64, 100 | hash1 + hash0 * 2 ** 64, 101 | ) 102 | 103 | local header : BlockHeader = BlockHeader(version, prev_block, merkle_root, timestamp, bits, nonce, header_hash) 104 | return (header) 105 | end 106 | 107 | func process_header{ 108 | syscall_ptr : felt*, 109 | pedersen_ptr : HashBuiltin*, 110 | range_check_ptr, 111 | bitwise_ptr : BitwiseBuiltin*, 112 | }(ctx : BlockHeaderValidationContext): 113 | alloc_locals 114 | let (local params : Params) = get_params() 115 | 116 | let (should_skip) = should_skip_checks(ctx) 117 | if should_skip == TRUE: 118 | return () 119 | end 120 | 121 | # Invoke consensus rules checks 122 | 123 | # RULE: Previous Block Hash 124 | previous_block_hash.assert_rule(ctx) 125 | 126 | # RULE: Proof Of Work 127 | check_pow.assert_rule(ctx) 128 | 129 | # RULE: Check proof of work target (bits) 130 | target.assert_rule(ctx, params) 131 | 132 | # RULE: Median Past Time 133 | median_past_time.assert_rule(ctx) 134 | 135 | # RULE: Timestamp is not in the future 136 | # 137 | # This rule is a bit specific as it makes sense for accepting new blocks, but not really 138 | # during Initial Block Download (aka IBD). 139 | # 140 | # In Bitcoin Core, the rule is implemented as: `block time <= adjusted-time + 2h`, where 141 | # adjusted-time is the present time adjusted with the time of peers. The rule remains the same 142 | # during IBD. 143 | # 144 | # In StarkNet, the only source of time we have is the current (StarkNet) block timestamp, which 145 | # we cannot trust. 146 | # 147 | # Therefore, we cannot implement the rule as-is. 148 | 149 | # Accept block 150 | accept_block(ctx) 151 | 152 | return () 153 | end 154 | 155 | func should_skip_checks{ 156 | syscall_ptr : felt*, 157 | pedersen_ptr : HashBuiltin*, 158 | range_check_ptr, 159 | bitwise_ptr : BitwiseBuiltin*, 160 | }(ctx : BlockHeaderValidationContext) -> (skip : felt): 161 | # Should never encounter the genesis block 162 | let (genesis : BlockHeader) = genesis_block_header() 163 | let (is_genesis_hash) = uint256_eq(ctx.block_header.hash, genesis.hash) 164 | assert is_genesis_hash = FALSE 165 | 166 | # Skip if block was already processed 167 | let (stored_block_header : BlockHeader) = storage.block_header_by_hash( 168 | ctx.block_header.hash 169 | ) 170 | let (is_already_stored) = is_not_zero(stored_block_header.version) 171 | return (skip=is_already_stored) 172 | end 173 | 174 | func accept_block{ 175 | syscall_ptr : felt*, 176 | pedersen_ptr : HashBuiltin*, 177 | range_check_ptr, 178 | bitwise_ptr : BitwiseBuiltin*, 179 | }(ctx : BlockHeaderValidationContext): 180 | alloc_locals 181 | # Write current header to storage 182 | storage.write_header(ctx.height, ctx.block_header) 183 | 184 | # Consensus rules callback 185 | previous_block_hash.on_block_accepted(ctx) 186 | check_pow.on_block_accepted(ctx) 187 | median_past_time.on_block_accepted(ctx) 188 | return () 189 | end 190 | 191 | # Returns the hardcoded genesis block. 192 | # See https://github.com/bitcoin/bitcoin/blob/master/src/chainparams.cpp 193 | # and https://www.blockchain.com/btc/block/000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f 194 | func genesis_block_header{range_check_ptr}() -> (genesis_block_header : BlockHeader): 195 | alloc_locals 196 | let (local genesis_hash : Uint256) = felt_to_uint256( 197 | 0x19d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f 198 | ) 199 | let (genesis_merkle_root : Uint256) = felt_to_uint256( 200 | 0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b 201 | ) 202 | let genesis_block_header = BlockHeader( 203 | version=1, 204 | prev_block=Uint256(0, 0), 205 | merkle_root=genesis_merkle_root, 206 | timestamp=1231006505, 207 | bits=0x1d00ffff, 208 | nonce=2083236893, 209 | hash=genesis_hash, 210 | ) 211 | return (genesis_block_header) 212 | end 213 | 214 | func felt_to_uint256{range_check_ptr}(x) -> (res : Uint256): 215 | let (hi, lo) = split_felt(x) 216 | return (Uint256(lo, hi)) 217 | end 218 | end 219 | -------------------------------------------------------------------------------- /src/header/model.cairo: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Khepri smart contracts written in Cairo v0.1.0 (header/model.cairo) 3 | 4 | %lang starknet 5 | 6 | # Starkware dependencies 7 | from starkware.cairo.common.uint256 import Uint256 8 | from starkware.cairo.common.math import assert_not_zero 9 | 10 | struct BlockHeader: 11 | member version : felt # 4 bytes 12 | member prev_block : Uint256 # 32 bytes 13 | member merkle_root : Uint256 # 32 bytes 14 | member timestamp : felt # 4 bytes 15 | member bits : felt # 4 bytes 16 | member nonce : felt # 4 bytes 17 | member hash : Uint256 # 32 bytes 18 | end 19 | 20 | struct BlockHeaderValidationContext: 21 | member height : felt 22 | member block_header : BlockHeader 23 | member previous_block_header : BlockHeader 24 | end 25 | 26 | func assert_block_header{range_check_ptr}(block_header : BlockHeader): 27 | with_attr error_message("block header is undefined"): 28 | assert_not_zero(block_header.version) 29 | end 30 | return () 31 | end 32 | 33 | func assert_block_header_is_undefined{range_check_ptr}(block_header : BlockHeader): 34 | with_attr error_message("block header is not undefined"): 35 | assert 0 = block_header.version 36 | end 37 | return () 38 | end 39 | -------------------------------------------------------------------------------- /src/header/rules/check_pow.cairo: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | 3 | %lang starknet 4 | 5 | from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin 6 | from starkware.cairo.common.bool import TRUE, FALSE 7 | from starkware.cairo.common.uint256 import Uint256, uint256_lt 8 | 9 | from header.model import BlockHeader, BlockHeaderValidationContext 10 | from utils.target import decode_target 11 | 12 | # ------ 13 | # CONSTANTS 14 | # ------ 15 | const MAX_TARGET = 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 16 | const TRUNC_MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000 17 | 18 | # ------ 19 | # RULE: Proof Of Work 20 | # Description: A block is considered valid if the block header hash value is less than the difficulty target 21 | # Ref: https://en.bitcoin.it/wiki/Target 22 | # ------ 23 | namespace check_pow: 24 | # This function reverts if the hash of the input block header is greater than the block target 25 | func assert_rule{ 26 | syscall_ptr : felt*, 27 | pedersen_ptr : HashBuiltin*, 28 | range_check_ptr, 29 | bitwise_ptr : BitwiseBuiltin*, 30 | }(ctx : BlockHeaderValidationContext): 31 | alloc_locals 32 | 33 | let header_hash = ctx.block_header.hash 34 | let (target : Uint256) = decode_target(ctx.block_header.bits) 35 | let (res) = uint256_lt(header_hash, target) 36 | 37 | local target_hi = target.high 38 | local target_lo = target.low 39 | local hash_hi = header_hash.high 40 | local hash_lo = header_hash.low 41 | with_attr error_message( 42 | "[rule] PoW check: Hash value (({hash_lo}, {hash_hi}) must be less than the target ({target_lo}, {target_hi})"): 43 | assert TRUE = res 44 | end 45 | return () 46 | end 47 | 48 | func on_block_accepted{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 49 | ctx : BlockHeaderValidationContext 50 | ): 51 | return () 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /src/header/rules/median_past_time.cairo: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | 3 | %lang starknet 4 | 5 | from starkware.cairo.common.cairo_builtins import HashBuiltin 6 | from starkware.cairo.common.alloc import alloc 7 | from starkware.cairo.common.math import assert_lt 8 | from starkware.cairo.common.math_cmp import is_le 9 | from starkware.cairo.common.bool import TRUE, FALSE 10 | 11 | from header.model import BlockHeader, BlockHeaderValidationContext 12 | 13 | # ------ 14 | # CONSTANTS 15 | # ------ 16 | const TIMESTAMP_COUNT = 11 17 | const TIMESTAMP_MEDIAN_INDEX = 6 18 | 19 | # ------ 20 | # STRUCTS 21 | # ------ 22 | struct Timestamps: 23 | member t1 : felt 24 | member t2 : felt 25 | member t3 : felt 26 | member t4 : felt 27 | member t5 : felt 28 | member t6 : felt 29 | member t7 : felt 30 | member t8 : felt 31 | member t9 : felt 32 | member t10 : felt 33 | member t11 : felt 34 | end 35 | 36 | # ------ 37 | # STORAGE 38 | # ------ 39 | @storage_var 40 | func last_11_timestamps() -> (timestamps : Timestamps): 41 | end 42 | 43 | # ------ 44 | # RULE: Median Past Time 45 | # Description: A timestamp is accepted as valid if it is greater than the median timestamp of previous 11 blocks 46 | # Ref: https://en.bitcoin.it/wiki/Block_timestamp, https://en.bitcoin.it/wiki/BIP_0113 47 | # ------ 48 | namespace median_past_time: 49 | # This function reverts if the timestamp of the given header if lower than or equal to the median timestamp of previous 11 blocks 50 | func assert_rule{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 51 | ctx : BlockHeaderValidationContext 52 | ): 53 | alloc_locals 54 | let (timestamps : Timestamps) = last_11_timestamps.read() 55 | let (local timestamp_median) = internal.compute_timestamps_median(timestamps) 56 | local block_timestamp = ctx.block_header.timestamp 57 | 58 | with_attr error_message( 59 | "[rule] Median Past Time: block timestamp ({block_timestamp}) must be higher than the median ({timestamp_median}) of the previous 11 block timestamps"): 60 | assert_lt(timestamp_median, block_timestamp) 61 | end 62 | return () 63 | end 64 | 65 | # This function must be called when a block is accepted so that the list of the last 11 timestamps is updated 66 | func on_block_accepted{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 67 | ctx : BlockHeaderValidationContext 68 | ): 69 | let (timestamps : Timestamps) = last_11_timestamps.read() 70 | let new_timestamps : Timestamps = Timestamps( 71 | timestamps.t2, 72 | timestamps.t3, 73 | timestamps.t4, 74 | timestamps.t5, 75 | timestamps.t6, 76 | timestamps.t7, 77 | timestamps.t8, 78 | timestamps.t9, 79 | timestamps.t10, 80 | timestamps.t11, 81 | ctx.block_header.timestamp, 82 | ) 83 | last_11_timestamps.write(new_timestamps) 84 | return () 85 | end 86 | end 87 | 88 | # ------ 89 | # INTERNAL 90 | # ------ 91 | namespace internal: 92 | func compute_timestamps_median{range_check_ptr}(timestamps : Timestamps) -> ( 93 | median_value : felt 94 | ): 95 | tempvar timestamp_array : felt* = new ( 96 | timestamps.t1, 97 | timestamps.t2, 98 | timestamps.t3, 99 | timestamps.t4, 100 | timestamps.t5, 101 | timestamps.t6, 102 | timestamps.t7, 103 | timestamps.t8, 104 | timestamps.t9, 105 | timestamps.t10, 106 | timestamps.t11) 107 | 108 | let (sorted_timestamp_array : felt*) = sort_unsigned(TIMESTAMP_COUNT, timestamp_array) 109 | return (median_value=sorted_timestamp_array[TIMESTAMP_MEDIAN_INDEX]) 110 | end 111 | 112 | # Implement a naive sort algorithm for an array of felts without using any hint. 113 | # Complexity is O(n^2) but this is not a problem as it is used to sort an array of only 11 elements. 114 | func sort_unsigned{range_check_ptr}(arr_len : felt, arr : felt*) -> (sorted_array : felt*): 115 | alloc_locals 116 | 117 | let (local sorted_array : felt*) = alloc() 118 | sort_unsigned_loop(arr_len, arr, sorted_array) 119 | return (sorted_array) 120 | end 121 | 122 | func sort_unsigned_loop{range_check_ptr}(arr_len : felt, arr : felt*, sorted_array : felt*): 123 | if arr_len == 0: 124 | return () 125 | end 126 | 127 | # find the lowest element out of remaining elements 128 | let (lowest_element_index, lowest_element) = internal.find_lowest_element(arr_len, arr) 129 | 130 | # push the lowest element to the sorted array 131 | assert sorted_array[0] = lowest_element 132 | 133 | # remove the lowest element from the remaining elements 134 | let (arr : felt*) = copy_array_without_index(arr_len, arr, lowest_element_index) 135 | 136 | sort_unsigned_loop(arr_len - 1, arr, sorted_array + 1) 137 | return () 138 | end 139 | 140 | func find_lowest_element{range_check_ptr}(arr_len : felt, arr : felt*) -> ( 141 | lowest_element_index : felt, lowest_element : felt 142 | ): 143 | return find_lowest_element_loop(0, arr_len, arr, 0) 144 | end 145 | 146 | func find_lowest_element_loop{range_check_ptr}( 147 | index : felt, arr_len : felt, arr : felt*, lowest_element_index : felt 148 | ) -> (lowest_element_index : felt, lowest_element : felt): 149 | if index == arr_len: 150 | return ( 151 | lowest_element_index=lowest_element_index, lowest_element=arr[lowest_element_index] 152 | ) 153 | end 154 | 155 | let (is_lower) = is_le(arr[index], arr[lowest_element_index]) 156 | let new_lowest_element_index = index * is_lower + lowest_element_index * (1 - is_lower) 157 | 158 | return find_lowest_element_loop(index + 1, arr_len, arr, new_lowest_element_index) 159 | end 160 | 161 | func copy_array_without_index{range_check_ptr}( 162 | arr_len : felt, arr : felt*, removed_index : felt 163 | ) -> (new_arr : felt*): 164 | alloc_locals 165 | 166 | let (local new_arr : felt*) = alloc() 167 | copy_array_without_index_loop(0, arr_len, arr, removed_index, 0, new_arr) 168 | return (new_arr) 169 | end 170 | 171 | func copy_array_without_index_loop{range_check_ptr}( 172 | index : felt, 173 | arr_len : felt, 174 | arr : felt*, 175 | removed_index : felt, 176 | new_index : felt, 177 | new_arr : felt*, 178 | ): 179 | if index == arr_len: 180 | return () 181 | end 182 | 183 | if index == removed_index: 184 | copy_array_without_index_loop( 185 | index + 1, arr_len, arr, removed_index, new_index, new_arr 186 | ) 187 | return () 188 | end 189 | 190 | assert new_arr[new_index] = arr[index] 191 | 192 | copy_array_without_index_loop( 193 | index + 1, arr_len, arr, removed_index, new_index + 1, new_arr 194 | ) 195 | return () 196 | end 197 | end 198 | -------------------------------------------------------------------------------- /src/header/rules/previous_block_hash.cairo: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | # Khepri smart contracts written in Cairo v0.1.0 (header/rules/previous_block_hash.cairo) 3 | 4 | %lang starknet 5 | 6 | from starkware.cairo.common.cairo_builtins import HashBuiltin 7 | from starkware.cairo.common.uint256 import Uint256, uint256_eq 8 | from starkware.cairo.common.bool import TRUE 9 | 10 | from header.model import BlockHeader, BlockHeaderValidationContext 11 | 12 | # ------ 13 | # RULE: Previous Block Hash 14 | # Description: Previous block hash is a reference to the hash of the previous (parent) block in the chain 15 | # ------ 16 | namespace previous_block_hash: 17 | # This function reverts if the previous block hash is different from the one stored 18 | func assert_rule{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 19 | ctx : BlockHeaderValidationContext 20 | ): 21 | alloc_locals 22 | let prev_block_header_hash = ctx.previous_block_header.hash 23 | 24 | with_attr error_message( 25 | "[rule] Previous Block Hash: previous block header hash reference is invalid"): 26 | internal.assert_hash_equal(ctx.block_header.prev_block, prev_block_header_hash) 27 | end 28 | return () 29 | end 30 | 31 | func on_block_accepted{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 32 | ctx : BlockHeaderValidationContext 33 | ): 34 | return () 35 | end 36 | end 37 | 38 | # ------ 39 | # INTERNAL 40 | # ------ 41 | namespace internal: 42 | func assert_hash_equal{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 43 | hash_1 : Uint256, hash_2 : Uint256 44 | ): 45 | let (is_eq) = uint256_eq(hash_1, hash_2) 46 | assert is_eq = TRUE 47 | return () 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /src/header/rules/target.cairo: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: MIT 2 | 3 | %lang starknet 4 | 5 | from starkware.cairo.common.cairo_builtins import HashBuiltin 6 | from starkware.cairo.common.alloc import alloc 7 | from starkware.cairo.common.math import ( 8 | assert_not_zero, 9 | assert_le, 10 | split_felt, 11 | unsigned_div_rem, 12 | assert_nn, 13 | ) 14 | from starkware.cairo.common.math_cmp import is_le, is_not_zero 15 | from starkware.cairo.common.bool import TRUE, FALSE 16 | from starkware.cairo.common.uint256 import Uint256 17 | 18 | from openzeppelin.security.safemath import SafeUint256 19 | 20 | from header.model import BlockHeader, BlockHeaderValidationContext, assert_block_header 21 | from header.storage import storage 22 | from utils.math import clamp, min_uint256, felt_to_Uint256 23 | from utils.target import decode_target, encode_target 24 | from bitcoin.params import Params 25 | 26 | # ------ 27 | # RULE: Target 28 | # Description: Check the block's target for proof of work 29 | # Ref: 30 | # https://en.bitcoin.it/wiki/Target 31 | # https://en.bitcoin.it/wiki/Protocol_rules#Difficulty_change 32 | # https://en.bitcoin.it/wiki/Difficulty 33 | # https://github.com/bitcoin/bitcoin/blob/master/src/pow.cpp 34 | # ------ 35 | namespace target: 36 | # This function checks the block's target (bits) 37 | func assert_rule{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 38 | ctx : BlockHeaderValidationContext, params : Params 39 | ): 40 | alloc_locals 41 | let last_height = ctx.height - 1 42 | let (local expected_bits) = internal.getNextWorkRequired( 43 | ctx.previous_block_header, last_height, params 44 | ) 45 | local block_bits = ctx.block_header.bits 46 | with_attr error_message( 47 | "[invalid-header]::bad-diffbits: expected block target to be {expected_bits}, got {block_bits}"): 48 | assert expected_bits = block_bits 49 | end 50 | return () 51 | end 52 | end 53 | 54 | # ------ 55 | # INTERNAL 56 | # ------ 57 | namespace internal: 58 | # See https://github.com/bitcoin/bitcoin/blob/master/src/pow.cpp#L13 59 | func getNextWorkRequired{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 60 | last_header : BlockHeader, last_height : felt, params : Params 61 | ) -> (bits : felt): 62 | let (_, rest) = unsigned_div_rem(last_height + 1, params.difficulty_adjustment_interval) 63 | let (no_retarget_needed) = is_not_zero(rest) 64 | 65 | # Only change once per difficulty adjustment interval 66 | if no_retarget_needed == TRUE: 67 | return (bits=last_header.bits) 68 | end 69 | 70 | return calculateNextWorkRequired(last_header, last_height, params) 71 | end 72 | 73 | # See https://github.com/bitcoin/bitcoin/blob/master/src/pow.cpp#L49 74 | func calculateNextWorkRequired{ 75 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr 76 | }(last_header : BlockHeader, last_height : felt, params : Params) -> (bits : felt): 77 | alloc_locals 78 | 79 | # Go back by what we want to be 14 days worth of blocks 80 | let height_first = last_height - (params.difficulty_adjustment_interval - 1) 81 | assert_le(0, height_first) 82 | let (first_block_header : BlockHeader) = storage.block_header_by_height(height_first) 83 | assert_block_header(first_block_header) 84 | 85 | # Limit adjustment step 86 | let actual_timespan = last_header.timestamp - first_block_header.timestamp 87 | assert_nn(actual_timespan) 88 | let (actual_timespan) = clamp( 89 | actual_timespan, 90 | params.pow_target_timespan_div_by_4, 91 | params.pow_target_timespan_mul_by_4, 92 | ) 93 | 94 | # Retarget 95 | let (last_target : Uint256) = decode_target(last_header.bits) 96 | let (actual_timespan_256 : Uint256) = felt_to_Uint256(actual_timespan) 97 | let (pow_target_timespan : Uint256) = felt_to_Uint256(params.pow_target_timespan) 98 | 99 | let (new_target : Uint256) = SafeUint256.mul(last_target, actual_timespan_256) # new_target = last_target * actual_timespan 100 | let (new_target, _) = SafeUint256.div_rem(new_target, pow_target_timespan) # new_target /= pow_target_timespan 101 | 102 | # Ensure new target is not two high (ie. difficulty too low) 103 | let (new_target) = min_uint256(new_target, params.pow_limit) 104 | 105 | let (new_bits) = encode_target(new_target) 106 | return (bits=new_bits) 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /src/header/rules/test_check_pow.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin 4 | from starkware.cairo.common.uint256 import Uint256 5 | from starkware.cairo.common.bool import TRUE 6 | 7 | from header.model import BlockHeader 8 | from header.rules.check_pow import check_pow 9 | from header.test_utils import test_utils 10 | 11 | @view 12 | func test_check_pow_verifies_genesis{ 13 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr, bitwise_ptr : BitwiseBuiltin* 14 | }(): 15 | tempvar header : BlockHeader = BlockHeader( 16 | version=2, 17 | prev_block=Uint256(0, 0), 18 | merkle_root=Uint256(0, 0), 19 | timestamp=1231006505, 20 | bits=0x1d00ffff, 21 | nonce=0x7c2bac1d, 22 | hash=Uint256(0x4ff763ae46a2a6c172b3f1b60a8ce26f, 0x000000000019d6689c085ae165831e93) 23 | ) 24 | 25 | let (ctx) = test_utils.mock_ctx(header) 26 | check_pow.assert_rule(ctx) 27 | 28 | return () 29 | end 30 | -------------------------------------------------------------------------------- /src/header/rules/test_median_past_time.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | from starkware.cairo.common.alloc import alloc 5 | from starkware.cairo.common.uint256 import Uint256 6 | from header.model import BlockHeader, BlockHeaderValidationContext 7 | from header.rules.median_past_time import internal, Timestamps, last_11_timestamps, median_past_time 8 | from header.test_utils import test_utils 9 | 10 | @view 11 | func test_median_past_time_rule_doesnt_revert_when_timestamp_is_higher_than_median{ 12 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr 13 | }(): 14 | # median of timestamps is 8 15 | let timestamps : Timestamps = Timestamps(10, 16, 8, 0, 3, 3, 7, 20, 0, 4, 10) 16 | last_11_timestamps.write(timestamps) 17 | 18 | tempvar header : BlockHeader = BlockHeader( 19 | version=2, prev_block=Uint256(0, 0), merkle_root=Uint256(0, 0), timestamp=9, bits=10, nonce=10, hash=Uint256(0, 0) 20 | ) 21 | 22 | let (ctx) = test_utils.mock_ctx(header) 23 | median_past_time.assert_rule(ctx) 24 | 25 | return () 26 | end 27 | 28 | @view 29 | func test_median_past_time_rule_reverts_when_timestamp_is_lower_than_median{ 30 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr 31 | }(): 32 | # median of timestamps is 8 33 | let timestamps : Timestamps = Timestamps(10, 16, 8, 0, 3, 3, 7, 20, 0, 4, 10) 34 | last_11_timestamps.write(timestamps) 35 | 36 | tempvar header : BlockHeader = BlockHeader( 37 | version=2, prev_block=Uint256(0, 0), merkle_root=Uint256(0, 0), timestamp=7, bits=10, nonce=10, hash=Uint256(0, 0) 38 | ) 39 | 40 | %{ expect_revert(error_message="[rule] Median Past Time: block timestamp (7) must be higher than the median (8) of the previous 11 block timestamps") %} 41 | let (ctx) = test_utils.mock_ctx(header) 42 | median_past_time.assert_rule(ctx) 43 | 44 | return () 45 | end 46 | 47 | @view 48 | func test_median_past_time_rule_reverts_when_timestamp_equals_median{ 49 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr 50 | }(): 51 | # median of timestamps is 8 52 | let timestamps : Timestamps = Timestamps(10, 16, 8, 0, 3, 3, 7, 20, 0, 4, 10) 53 | last_11_timestamps.write(timestamps) 54 | 55 | tempvar header : BlockHeader = BlockHeader( 56 | version=2, prev_block=Uint256(0, 0), merkle_root=Uint256(0, 0), timestamp=8, bits=10, nonce=10, hash=Uint256(0, 0) 57 | ) 58 | 59 | %{ expect_revert(error_message="[rule] Median Past Time: block timestamp (8) must be higher than the median (8) of the previous 11 block timestamps") %} 60 | let (ctx) = test_utils.mock_ctx(header) 61 | median_past_time.assert_rule(ctx) 62 | 63 | return () 64 | end 65 | 66 | @view 67 | func test_on_block_accepted{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(): 68 | # median of timestamps is 8 69 | let timestamps : Timestamps = Timestamps(10, 16, 8, 0, 3, 3, 7, 20, 0, 4, 10) 70 | last_11_timestamps.write(timestamps) 71 | 72 | tempvar header : BlockHeader = BlockHeader( 73 | version=2, prev_block=Uint256(0, 0), merkle_root=Uint256(0, 0), timestamp=9, bits=10, nonce=10, hash=Uint256(0, 0) 74 | ) 75 | 76 | let (ctx) = test_utils.mock_ctx(header) 77 | median_past_time.on_block_accepted(ctx) 78 | 79 | let (timestamps : Timestamps) = last_11_timestamps.read() 80 | assert timestamps = Timestamps(16, 8, 0, 3, 3, 7, 20, 0, 4, 10, 9) 81 | return () 82 | end 83 | 84 | @view 85 | func test_compute_timestamps_median{range_check_ptr}(): 86 | let timestamps : Timestamps = Timestamps(10, 16, 8, 0, 3, 3, 7, 20, 0, 4, 10) 87 | 88 | let (median) = internal.compute_timestamps_median(timestamps) 89 | 90 | assert median = 8 91 | 92 | return () 93 | end 94 | 95 | @view 96 | func test_find_lowest_element{range_check_ptr}(): 97 | let (lowest_element_index, lowest_element) = internal.find_lowest_element(4, new (10, 4, 3, 7)) 98 | 99 | assert lowest_element_index = 2 100 | assert lowest_element = 3 101 | 102 | return () 103 | end 104 | 105 | @view 106 | func test_sort_unsigned{range_check_ptr}(): 107 | let (sorted_array : felt*) = internal.sort_unsigned(4, new (10, 4, 3, 7)) 108 | 109 | assert 3 = sorted_array[0] 110 | assert 4 = sorted_array[1] 111 | assert 7 = sorted_array[2] 112 | assert 10 = sorted_array[3] 113 | 114 | return () 115 | end 116 | 117 | @view 118 | func test_sort_unsigned_with_equal_values{range_check_ptr}(): 119 | let (sorted_array : felt*) = internal.sort_unsigned( 120 | 11, new (10, 16, 8, 0, 3, 3, 7, 20, 0, 4, 10) 121 | ) 122 | 123 | assert 0 = sorted_array[0] 124 | assert 0 = sorted_array[1] 125 | assert 3 = sorted_array[2] 126 | assert 3 = sorted_array[3] 127 | assert 4 = sorted_array[4] 128 | assert 7 = sorted_array[5] 129 | assert 8 = sorted_array[6] 130 | assert 10 = sorted_array[7] 131 | assert 10 = sorted_array[8] 132 | assert 16 = sorted_array[9] 133 | assert 20 = sorted_array[10] 134 | 135 | return () 136 | end 137 | -------------------------------------------------------------------------------- /src/header/rules/test_previous_block_hash.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin 4 | from starkware.cairo.common.uint256 import Uint256 5 | from starkware.cairo.common.bool import TRUE 6 | 7 | from header.model import BlockHeader, BlockHeaderValidationContext 8 | from header.rules.previous_block_hash import previous_block_hash 9 | from header.test_utils import test_utils 10 | 11 | @view 12 | func test_previous_block_hash_nominal_case{ 13 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr, bitwise_ptr : BitwiseBuiltin* 14 | }(): 15 | tempvar header : BlockHeader = BlockHeader( 16 | version=2, 17 | prev_block=Uint256(0x4ff763ae46a2a6c172b3f1b60a8ce26f, 0x000000000019d6689c085ae165831e93), 18 | merkle_root=Uint256(0, 0), 19 | timestamp=1231006505, 20 | bits=0x1d00ffff, 21 | nonce=0x7c2bac1d, 22 | hash=Uint256(0x4ff763ae46a2a6c172b3f1b60a8ce26f, 0x000000000019d6689c085ae165831e93) 23 | ) 24 | tempvar previous_block_header : BlockHeader = BlockHeader( 25 | version=2, 26 | prev_block=Uint256(0, 0), 27 | merkle_root=Uint256(0, 0), 28 | timestamp=1231006505, 29 | bits=0x1d00ffff, 30 | nonce=0x7c2bac1d, 31 | hash=Uint256(0x4ff763ae46a2a6c172b3f1b60a8ce26f, 0x000000000019d6689c085ae165831e93) 32 | ) 33 | 34 | tempvar ctx : BlockHeaderValidationContext = BlockHeaderValidationContext( 35 | height=1, block_header=header, previous_block_header=previous_block_header) 36 | 37 | previous_block_hash.assert_rule(ctx) 38 | 39 | return () 40 | end 41 | 42 | @view 43 | func test_previous_block_hash_invalid{ 44 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr, bitwise_ptr : BitwiseBuiltin* 45 | }(): 46 | tempvar header : BlockHeader = BlockHeader( 47 | version=2, 48 | prev_block=Uint256(41, 42), 49 | merkle_root=Uint256(0, 0), 50 | timestamp=1231006505, 51 | bits=0x1d00ffff, 52 | nonce=0x7c2bac1d, 53 | hash=Uint256(0x4ff763ae46a2a6c172b3f1b60a8ce26f, 0x000000000019d6689c085ae165831e93) 54 | ) 55 | tempvar previous_block_header : BlockHeader = BlockHeader( 56 | version=2, 57 | prev_block=Uint256(0, 0), 58 | merkle_root=Uint256(0, 0), 59 | timestamp=1231006505, 60 | bits=0x1d00ffff, 61 | nonce=0x7c2bac1d, 62 | hash=Uint256(42, 42) 63 | ) 64 | 65 | tempvar ctx : BlockHeaderValidationContext = BlockHeaderValidationContext( 66 | height=1, block_header=header, previous_block_header=previous_block_header) 67 | %{ expect_revert(error_message="[rule] Previous Block Hash: previous block header hash reference is invalid") %} 68 | previous_block_hash.assert_rule(ctx) 69 | 70 | return () 71 | end 72 | -------------------------------------------------------------------------------- /src/header/rules/test_target.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | from starkware.cairo.common.alloc import alloc 5 | from starkware.cairo.common.uint256 import Uint256 6 | 7 | from header.model import BlockHeader, BlockHeaderValidationContext 8 | from header.storage import block_header_hash_, block_header_ 9 | from header.rules.target import target 10 | from utils.target import decode_target, encode_target 11 | from utils.math import felt_to_Uint256 12 | from bitcoin.params import Params, get_params 13 | 14 | const TWO_WEEKS = 2 * 7 * 24 * 60 * 60 15 | const TWO_WEEKS_DIV_BY_6 = 2 * 7 * 24 * 60 * 10 16 | const RETARGET_HEIGHT = 2016 17 | 18 | @view 19 | func test_target_rule_no_retarget{ 20 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr 21 | }(): 22 | alloc_locals 23 | let (local params : Params) = get_params() 24 | 25 | let (current_target : Uint256) = felt_to_Uint256(42000) 26 | let (bits) = encode_target(current_target) 27 | 28 | tempvar last_header : BlockHeader = BlockHeader( 29 | version=2, 30 | prev_block=Uint256(0, 0), 31 | merkle_root=Uint256(0, 0), 32 | timestamp=1000, 33 | bits=bits, 34 | nonce=0, 35 | hash=Uint256(0, 1) 36 | ) 37 | 38 | tempvar header : BlockHeader = BlockHeader( 39 | version=2, 40 | prev_block=Uint256(0, 1), 41 | merkle_root=Uint256(0, 0), 42 | timestamp=last_header.timestamp + 600, 43 | bits=bits, 44 | nonce=0, 45 | hash=Uint256(0, 2) 46 | ) 47 | 48 | let ctx = BlockHeaderValidationContext( 49 | height=10 * RETARGET_HEIGHT - 100, block_header=header, previous_block_header=last_header 50 | ) 51 | target.assert_rule(ctx, params) 52 | 53 | return () 54 | end 55 | 56 | @view 57 | func test_target_rule_no_retarget_wrong_target{ 58 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr 59 | }(): 60 | alloc_locals 61 | let (local params : Params) = get_params() 62 | 63 | let (current_target : Uint256) = felt_to_Uint256(42000) 64 | let (bits) = encode_target(current_target) 65 | 66 | let (wrong_target : Uint256) = felt_to_Uint256(30000) 67 | let (wrong_bits) = encode_target(wrong_target) 68 | 69 | tempvar last_header : BlockHeader = BlockHeader( 70 | version=2, 71 | prev_block=Uint256(0, 0), 72 | merkle_root=Uint256(0, 0), 73 | timestamp=1000, 74 | bits=bits, 75 | nonce=0, 76 | hash=Uint256(0, 1) 77 | ) 78 | 79 | tempvar header : BlockHeader = BlockHeader( 80 | version=2, 81 | prev_block=Uint256(0, 1), 82 | merkle_root=Uint256(0, 0), 83 | timestamp=last_header.timestamp + 600, 84 | bits=wrong_bits, 85 | nonce=0, 86 | hash=Uint256(0, 2) 87 | ) 88 | 89 | let ctx = BlockHeaderValidationContext( 90 | height=10 * RETARGET_HEIGHT - 100, block_header=header, previous_block_header=last_header 91 | ) 92 | %{ expect_revert(error_message="[invalid-header]::bad-diffbits: expected block target to be 50373648, got 41234432") %} 93 | target.assert_rule(ctx, params) 94 | return () 95 | end 96 | 97 | @view 98 | func test_target_rule_retarget{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(): 99 | alloc_locals 100 | let (local params : Params) = get_params() 101 | let last_height = 10 * RETARGET_HEIGHT - 1 102 | 103 | let (current_target : Uint256) = felt_to_Uint256(42000) 104 | let (bits) = encode_target(current_target) 105 | 106 | tempvar header_2016_away : BlockHeader = BlockHeader( 107 | version=2, 108 | prev_block=Uint256(0, 0), 109 | merkle_root=Uint256(0, 0), 110 | timestamp=1000, 111 | bits=bits, 112 | nonce=0, 113 | hash=Uint256(0, 0) 114 | ) 115 | 116 | block_header_hash_.write(last_height - (RETARGET_HEIGHT - 1), Uint256(1, 1)) 117 | block_header_.write(Uint256(1, 1), header_2016_away) 118 | 119 | tempvar last_header : BlockHeader = BlockHeader( 120 | version=2, 121 | prev_block=Uint256(0, 0), 122 | merkle_root=Uint256(0, 0), 123 | timestamp=header_2016_away.timestamp + TWO_WEEKS * 2, 124 | bits=bits, 125 | nonce=0, 126 | hash=Uint256(0, 0) 127 | ) 128 | 129 | let (new_target : Uint256) = felt_to_Uint256(84000) # 42000 * (TWO_WEEKS*2 / TWO_WEEKS) 130 | let (new_bits) = encode_target(new_target) 131 | 132 | tempvar header : BlockHeader = BlockHeader( 133 | version=2, 134 | prev_block=Uint256(0, 0), 135 | merkle_root=Uint256(0, 0), 136 | timestamp=last_header.timestamp + 600, 137 | bits=new_bits, 138 | nonce=0, 139 | hash=Uint256(0, 0) 140 | ) 141 | 142 | let ctx = BlockHeaderValidationContext( 143 | height=last_height + 1, block_header=header, previous_block_header=last_header 144 | ) 145 | target.assert_rule(ctx, params) 146 | return () 147 | end 148 | 149 | @view 150 | func test_target_rule_retarget_wrong_target{ 151 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr 152 | }(): 153 | alloc_locals 154 | let (local params : Params) = get_params() 155 | let last_height = 10 * RETARGET_HEIGHT - 1 156 | 157 | let (current_target : Uint256) = felt_to_Uint256(42000) 158 | let (bits) = encode_target(current_target) 159 | 160 | tempvar header_2016_away : BlockHeader = BlockHeader( 161 | version=2, 162 | prev_block=Uint256(0, 0), 163 | merkle_root=Uint256(0, 0), 164 | timestamp=1000, 165 | bits=bits, 166 | nonce=0, 167 | hash=Uint256(0, 0) 168 | ) 169 | 170 | block_header_hash_.write(last_height - (RETARGET_HEIGHT - 1), Uint256(1, 1)) 171 | block_header_.write(Uint256(1, 1), header_2016_away) 172 | 173 | tempvar last_header : BlockHeader = BlockHeader( 174 | version=2, 175 | prev_block=Uint256(0, 0), 176 | merkle_root=Uint256(0, 0), 177 | timestamp=header_2016_away.timestamp + TWO_WEEKS * 2, 178 | bits=bits, 179 | nonce=0, 180 | hash=Uint256(0, 0) 181 | ) 182 | 183 | tempvar header : BlockHeader = BlockHeader( 184 | version=2, 185 | prev_block=Uint256(0, 0), 186 | merkle_root=Uint256(0, 0), 187 | timestamp=last_header.timestamp + 600, 188 | bits=bits, # should have been new target 84000 = 42000 * (TWO_WEEKS*2 / TWO_WEEKS) 189 | nonce=0, 190 | hash=Uint256(0, 0) 191 | ) 192 | 193 | let ctx = BlockHeaderValidationContext( 194 | height=last_height + 1, block_header=header, previous_block_header=last_header 195 | ) 196 | %{ expect_revert(error_message="[invalid-header]::bad-diffbits: expected block target to be 50415648, got 50373648") %} 197 | target.assert_rule(ctx, params) 198 | return () 199 | end 200 | 201 | @view 202 | func test_target_rule_retarget_with_too_high_timestamp{ 203 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr 204 | }(): 205 | alloc_locals 206 | let (local params : Params) = get_params() 207 | let last_height = 10 * RETARGET_HEIGHT - 1 208 | 209 | let (current_target : Uint256) = felt_to_Uint256(42000) 210 | let (bits) = encode_target(current_target) 211 | 212 | tempvar header_2016_away : BlockHeader = BlockHeader( 213 | version=2, 214 | prev_block=Uint256(0, 0), 215 | merkle_root=Uint256(0, 0), 216 | timestamp=1000, 217 | bits=bits, 218 | nonce=0, 219 | hash=Uint256(0, 0) 220 | ) 221 | 222 | block_header_hash_.write(last_height - (RETARGET_HEIGHT - 1), Uint256(1, 1)) 223 | block_header_.write(Uint256(1, 1), header_2016_away) 224 | 225 | tempvar last_header : BlockHeader = BlockHeader( 226 | version=2, 227 | prev_block=Uint256(0, 0), 228 | merkle_root=Uint256(0, 0), 229 | timestamp=header_2016_away.timestamp + TWO_WEEKS * 5, # too high, this will be clamped to TWO_WEEKS * 4 (this is what this test checks) 230 | bits=bits, 231 | nonce=0, 232 | hash=Uint256(0, 0) 233 | ) 234 | 235 | let (new_target : Uint256) = felt_to_Uint256(42000 * 4) # 42000 * (TWO_WEEKS*4 / TWO_WEEKS) 236 | let (new_bits) = encode_target(new_target) 237 | 238 | tempvar header : BlockHeader = BlockHeader( 239 | version=2, 240 | prev_block=Uint256(0, 0), 241 | merkle_root=Uint256(0, 0), 242 | timestamp=last_header.timestamp + 600, 243 | bits=new_bits, 244 | nonce=0, 245 | hash=Uint256(0, 0) 246 | ) 247 | 248 | let ctx = BlockHeaderValidationContext( 249 | height=last_height + 1, block_header=header, previous_block_header=last_header 250 | ) 251 | target.assert_rule(ctx, params) 252 | return () 253 | end 254 | 255 | @view 256 | func test_target_rule_retarget_with_too_low_timestamp{ 257 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr 258 | }(): 259 | alloc_locals 260 | let (local params : Params) = get_params() 261 | let last_height = 10 * RETARGET_HEIGHT - 1 262 | 263 | let (current_target : Uint256) = felt_to_Uint256(42000) 264 | let (bits) = encode_target(current_target) 265 | 266 | tempvar header_2016_away : BlockHeader = BlockHeader( 267 | version=2, 268 | prev_block=Uint256(0, 0), 269 | merkle_root=Uint256(0, 0), 270 | timestamp=1000, 271 | bits=bits, 272 | nonce=0, 273 | hash=Uint256(0, 0) 274 | ) 275 | 276 | block_header_hash_.write(last_height - (RETARGET_HEIGHT - 1), Uint256(1, 1)) 277 | block_header_.write(Uint256(1, 1), header_2016_away) 278 | 279 | tempvar last_header : BlockHeader = BlockHeader( 280 | version=2, 281 | prev_block=Uint256(0, 0), 282 | merkle_root=Uint256(0, 0), 283 | timestamp=header_2016_away.timestamp + TWO_WEEKS_DIV_BY_6, # too low, this will be clamped to TWO_WEEKS / 4 (this is what this test checks) 284 | bits=bits, 285 | nonce=0, 286 | hash=Uint256(0, 0) 287 | ) 288 | 289 | let (new_target : Uint256) = felt_to_Uint256(10500) # 42000 * (TWO_WEEKS/4 / TWO_WEEKS) 290 | let (new_bits) = encode_target(new_target) 291 | 292 | tempvar header : BlockHeader = BlockHeader( 293 | version=2, 294 | prev_block=Uint256(0, 0), 295 | merkle_root=Uint256(0, 0), 296 | timestamp=last_header.timestamp + 600, 297 | bits=new_bits, 298 | nonce=0, 299 | hash=Uint256(0, 0) 300 | ) 301 | 302 | let ctx = BlockHeaderValidationContext( 303 | height=last_height + 1, block_header=header, previous_block_header=last_header 304 | ) 305 | target.assert_rule(ctx, params) 306 | return () 307 | end 308 | -------------------------------------------------------------------------------- /src/header/storage.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | # Starkware dependencies 4 | from starkware.cairo.common.cairo_builtins import HashBuiltin 5 | from starkware.cairo.common.uint256 import Uint256, uint256_eq 6 | 7 | from header.model import BlockHeader, assert_block_header_is_undefined 8 | 9 | # ------ 10 | # STORAGE 11 | # ------ 12 | @storage_var 13 | func block_header_hash_(block_height : felt) -> (block_header_hash : Uint256): 14 | end 15 | 16 | @storage_var 17 | func block_header_(block_header_hash : Uint256) -> (block_header : BlockHeader): 18 | end 19 | 20 | @storage_var 21 | func current_height_() -> (last_block_height : felt): 22 | end 23 | 24 | namespace storage: 25 | # ----- 26 | # GETTERS 27 | # ----- 28 | func block_header_hash{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 29 | block_height 30 | ) -> (block_header_hash : Uint256): 31 | let (block_header_hash) = block_header_hash_.read(block_height) 32 | return (block_header_hash) 33 | end 34 | 35 | func block_header_by_hash{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 36 | block_header_hash : Uint256 37 | ) -> (block_header : BlockHeader): 38 | let (block_header) = block_header_.read(block_header_hash) 39 | return (block_header) 40 | end 41 | 42 | func block_header_by_height{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 43 | block_header_height : felt 44 | ) -> (block_header : BlockHeader): 45 | let (block_header_hash) = block_header_hash_.read(block_header_height) 46 | let (block_header) = block_header_.read(block_header_hash) 47 | return (block_header) 48 | end 49 | 50 | func current_height{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}() -> ( 51 | height : felt 52 | ): 53 | let (height) = current_height_.read() 54 | return (height) 55 | end 56 | 57 | # ----- 58 | # SETTERS 59 | # ----- 60 | func write_header{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 61 | height : felt, block_header : BlockHeader 62 | ): 63 | alloc_locals 64 | 65 | let (last_height) = current_height() 66 | with_attr error_message("invalid height"): 67 | assert height = last_height + 1 68 | end 69 | 70 | let (existing_hash : Uint256) = block_header_hash(height) 71 | let (existing_hash_is_zero) = uint256_eq(existing_hash, Uint256(0, 0)) 72 | with_attr error_message("block header at height {height} is already stored"): 73 | assert existing_hash_is_zero = 1 74 | end 75 | 76 | let (existing_block_header : BlockHeader) = block_header_by_hash(block_header.hash) 77 | with_attr error_message("block header with same hash is already stored"): 78 | assert_block_header_is_undefined(existing_block_header) 79 | end 80 | 81 | unsafe_write_header(height, block_header) 82 | return () 83 | end 84 | 85 | # This function should only be used to store the genesis block 86 | func unsafe_write_header{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}( 87 | height : felt, block_header : BlockHeader 88 | ): 89 | current_height_.write(height) 90 | block_header_hash_.write(height, block_header.hash) 91 | block_header_.write(block_header.hash, block_header) 92 | return () 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /src/header/test_block_header_verifier.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin 4 | from starkware.cairo.common.uint256 import Uint256 5 | 6 | from header.model import BlockHeaderValidationContext, BlockHeader 7 | from header.library import BlockHeaderVerifier, internal 8 | from header.test_utils import test_utils 9 | 10 | @view 11 | func test_process_block{ 12 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr, bitwise_ptr : BitwiseBuiltin* 13 | }(): 14 | alloc_locals 15 | 16 | let (header0) = test_utils.load_header_from_json('./resources/blocks/block0.json') 17 | 18 | let header1 : BlockHeader = BlockHeader( 19 | version=1, 20 | prev_block=Uint256(low=0x4ff763ae46a2a6c172b3f1b60a8ce26f, high=0x000000000019d6689c085ae165831e93), 21 | merkle_root=Uint256(low=0x6714ee1f0e68bebb44a74b1efd512098, high=0x0e3e2357e806b6cdb1f70b54c3a3a17b), 22 | timestamp=1231469665, 23 | bits=0x1d00ffff, 24 | nonce=2573394689, 25 | hash=Uint256(low=0x75428afc90947ee320161bbf18eb6048, high=0x00000000839a8e6886ab5951d76f4114), 26 | ) 27 | let ctx = BlockHeaderValidationContext( 28 | height=1, block_header=header1, previous_block_header=header0 29 | ) 30 | 31 | internal.process_header(ctx) 32 | 33 | return () 34 | end 35 | -------------------------------------------------------------------------------- /src/header/test_model.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | from starkware.cairo.common.uint256 import Uint256 5 | 6 | from header.model import BlockHeader, assert_block_header_is_undefined, assert_block_header 7 | 8 | @view 9 | func test_assert_block_header{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(): 10 | alloc_locals 11 | 12 | let header : BlockHeader = BlockHeader( 13 | version=1, 14 | prev_block=Uint256(0, 0), 15 | merkle_root=Uint256(0, 0), 16 | timestamp=0, 17 | bits=0, 18 | nonce=0, 19 | hash=Uint256(0, 0), 20 | ) 21 | 22 | assert_block_header(header) 23 | 24 | let header : BlockHeader = BlockHeader( 25 | version=0, 26 | prev_block=Uint256(0, 0), 27 | merkle_root=Uint256(0, 0), 28 | timestamp=0, 29 | bits=0, 30 | nonce=0, 31 | hash=Uint256(0, 0), 32 | ) 33 | 34 | %{ expect_revert(error_message="block header is undefined") %} 35 | assert_block_header(header) 36 | 37 | return () 38 | end 39 | 40 | @view 41 | func test_assert_block_header_is_undefined{ 42 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr 43 | }(): 44 | alloc_locals 45 | 46 | let header : BlockHeader = BlockHeader( 47 | version=0, 48 | prev_block=Uint256(0, 0), 49 | merkle_root=Uint256(0, 0), 50 | timestamp=0, 51 | bits=0, 52 | nonce=0, 53 | hash=Uint256(0, 0), 54 | ) 55 | 56 | assert_block_header_is_undefined(header) 57 | 58 | let header : BlockHeader = BlockHeader( 59 | version=1, 60 | prev_block=Uint256(0, 0), 61 | merkle_root=Uint256(0, 0), 62 | timestamp=0, 63 | bits=0, 64 | nonce=0, 65 | hash=Uint256(0, 0), 66 | ) 67 | 68 | %{ expect_revert(error_message="block header is not undefined") %} 69 | assert_block_header_is_undefined(header) 70 | 71 | return () 72 | end 73 | -------------------------------------------------------------------------------- /src/header/test_storage.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin 4 | from starkware.cairo.common.uint256 import Uint256 5 | 6 | from header.model import BlockHeader, assert_block_header_is_undefined, assert_block_header 7 | from header.storage import storage, block_header_hash_ 8 | 9 | @view 10 | func test_storage{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(): 11 | alloc_locals 12 | 13 | let (height) = storage.current_height() 14 | assert 0 = height 15 | 16 | let (hash) = storage.block_header_hash(height) 17 | assert Uint256(0, 0) = hash 18 | 19 | let (header) = storage.block_header_by_height(height) 20 | assert_block_header_is_undefined(header) 21 | 22 | # The first header must be stored using the unsafe function 23 | storage.unsafe_write_header( 24 | 0, 25 | BlockHeader( 26 | version=1, 27 | prev_block=Uint256(0, 0), 28 | merkle_root=Uint256(0, 0), 29 | timestamp=0, 30 | bits=0, 31 | nonce=0, 32 | hash=Uint256(0, 1), 33 | ), 34 | ) 35 | 36 | let (height) = storage.current_height() 37 | assert 0 = height 38 | 39 | let (hash) = storage.block_header_hash(height) 40 | assert Uint256(0, 1) = hash 41 | 42 | let (header) = storage.block_header_by_height(height) 43 | assert_block_header(header) 44 | assert Uint256(0, 1) = header.hash 45 | 46 | storage.write_header( 47 | 1, 48 | BlockHeader( 49 | version=1, 50 | prev_block=Uint256(0, 0), 51 | merkle_root=Uint256(0, 0), 52 | timestamp=0, 53 | bits=0, 54 | nonce=0, 55 | hash=Uint256(0, 2), 56 | ), 57 | ) 58 | 59 | let (height) = storage.current_height() 60 | assert 1 = height 61 | 62 | let (hash) = storage.block_header_hash(height) 63 | assert Uint256(0, 2) = hash 64 | 65 | let (header) = storage.block_header_by_height(height) 66 | assert_block_header(header) 67 | assert Uint256(0, 2) = header.hash 68 | 69 | storage.write_header( 70 | 2, 71 | BlockHeader( 72 | version=1, 73 | prev_block=Uint256(0, 0), 74 | merkle_root=Uint256(0, 0), 75 | timestamp=0, 76 | bits=0, 77 | nonce=0, 78 | hash=Uint256(0, 3), 79 | ), 80 | ) 81 | 82 | let (height) = storage.current_height() 83 | assert 2 = height 84 | 85 | let (hash) = storage.block_header_hash(height) 86 | assert Uint256(0, 3) = hash 87 | 88 | let (header) = storage.block_header_by_height(height) 89 | assert_block_header(header) 90 | assert Uint256(0, 3) = header.hash 91 | 92 | return () 93 | end 94 | 95 | @view 96 | func test_storage_wrong_height{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(): 97 | alloc_locals 98 | 99 | storage.unsafe_write_header( 100 | 0, 101 | BlockHeader( 102 | version=1, 103 | prev_block=Uint256(0, 0), 104 | merkle_root=Uint256(0, 0), 105 | timestamp=0, 106 | bits=0, 107 | nonce=0, 108 | hash=Uint256(0, 1), 109 | ), 110 | ) 111 | 112 | # wrong height, we pass 0 but it should be 1 113 | %{ expect_revert(error_message="invalid height") %} 114 | storage.write_header( 115 | 0, 116 | BlockHeader( 117 | version=1, 118 | prev_block=Uint256(0, 0), 119 | merkle_root=Uint256(0, 0), 120 | timestamp=0, 121 | bits=0, 122 | nonce=0, 123 | hash=Uint256(0, 2), 124 | ), 125 | ) 126 | return () 127 | end 128 | 129 | @view 130 | func test_storage_block_already_stored{ 131 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr 132 | }(): 133 | alloc_locals 134 | 135 | storage.unsafe_write_header( 136 | 0, 137 | BlockHeader( 138 | version=1, 139 | prev_block=Uint256(0, 0), 140 | merkle_root=Uint256(0, 0), 141 | timestamp=0, 142 | bits=0, 143 | nonce=0, 144 | hash=Uint256(0, 1), 145 | ), 146 | ) 147 | 148 | # Insert block hash without updating current height 149 | block_header_hash_.write(1, Uint256(0, 2)) 150 | 151 | # block at height 1 is already stored 152 | %{ expect_revert(error_message="block header at height 1 is already stored") %} 153 | storage.write_header( 154 | 1, 155 | BlockHeader( 156 | version=1, 157 | prev_block=Uint256(0, 0), 158 | merkle_root=Uint256(0, 0), 159 | timestamp=0, 160 | bits=0, 161 | nonce=0, 162 | hash=Uint256(0, 3), 163 | ), 164 | ) 165 | return () 166 | end 167 | 168 | @view 169 | func test_storage_block_hash_already_exists{ 170 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr 171 | }(): 172 | alloc_locals 173 | 174 | storage.unsafe_write_header( 175 | 0, 176 | BlockHeader( 177 | version=1, 178 | prev_block=Uint256(0, 0), 179 | merkle_root=Uint256(0, 0), 180 | timestamp=0, 181 | bits=0, 182 | nonce=0, 183 | hash=Uint256(0, 1), 184 | ), 185 | ) 186 | 187 | storage.unsafe_write_header( 188 | 1, 189 | BlockHeader( 190 | version=1, 191 | prev_block=Uint256(0, 0), 192 | merkle_root=Uint256(0, 0), 193 | timestamp=0, 194 | bits=0, 195 | nonce=0, 196 | hash=Uint256(0, 2), 197 | ), 198 | ) 199 | 200 | # same hash than first block 201 | %{ expect_revert(error_message="block header with same hash is already stored") %} 202 | storage.write_header( 203 | 2, 204 | BlockHeader( 205 | version=1, 206 | prev_block=Uint256(0, 0), 207 | merkle_root=Uint256(0, 0), 208 | timestamp=0, 209 | bits=0, 210 | nonce=0, 211 | hash=Uint256(0, 1), 212 | ), 213 | ) 214 | return () 215 | end 216 | -------------------------------------------------------------------------------- /src/header/test_utils.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.uint256 import Uint256 4 | from header.model import BlockHeader, BlockHeaderValidationContext 5 | 6 | namespace test_utils: 7 | func mock_ctx(header : BlockHeader) -> (ctx : BlockHeaderValidationContext): 8 | tempvar ctx : BlockHeaderValidationContext = BlockHeaderValidationContext( 9 | height=0, block_header=header, previous_block_header=header) 10 | return (ctx) 11 | end 12 | 13 | func print_block_header(x : BlockHeader): 14 | %{ 15 | x = ids.x 16 | print(f'version: {x.version:08x}') 17 | print(f'prev_block: {x.prev_block.high:032x}{x.prev_block.low:032x}') 18 | print(f'merkle_root: {x.merkle_root.high:032x}{x.merkle_root.low:032x}') 19 | print(f'timestamp: {x.timestamp}') 20 | print(f'bits: {x.bits:08x}') 21 | print(f'nonce: {x.nonce:08x}') 22 | print(f'hash: {x.hash.high:032x}{x.hash.low:032x}') 23 | %} 24 | return () 25 | end 26 | 27 | func genesis_block_header() -> (header : BlockHeader): 28 | let (header : BlockHeader) = BlockHeader( 29 | version=1, 30 | prev_block=Uint256(0, 0), 31 | merkle_root=Uint256(0x618f76673e2cc77ab2127b7afdeda33b, 0x4a5e1e4baab89f3a32518a88c31bc87f), 32 | timestamp=1231006505, 33 | bits=0x1d00ffff, 34 | nonce=0x7c2bac1d, 35 | hash=Uint256(0x4ff763ae46a2a6c172b3f1b60a8ce26f, 0x000000000019d6689c085ae165831e93), 36 | ) 37 | return (header=header) 38 | end 39 | 40 | func load_header_from_json(file_name : felt) -> (header : BlockHeader): 41 | alloc_locals 42 | local header : BlockHeader 43 | %{ 44 | import json 45 | f_name_int = int(ids.file_name) 46 | file_name = f_name_int.to_bytes((f_name_int.bit_length() + 7 ) // 8, 'big').decode() 47 | with open(file_name, 'r') as f: 48 | data = json.load(f) 49 | 50 | prev_block = data['previousblockhash'] if 'previousblockhash' in data else "0"*64 51 | merkle_root = data['merkleroot'] 52 | hash = data['hash'] 53 | 54 | ids.header.version = data['version'] 55 | ids.header.prev_block.low = int(prev_block[32:], 16) 56 | ids.header.prev_block.high = int(prev_block[:32], 16) 57 | ids.header.merkle_root.low = int(merkle_root[32:], 16) 58 | ids.header.merkle_root.high = int(merkle_root[:32], 16) 59 | 60 | ids.header.timestamp = data['version'] 61 | ids.header.bits = int(data['bits'], 16) 62 | ids.header.nonce = data['nonce'] 63 | ids.header.hash.low = int(hash[32:], 16) 64 | ids.header.hash.high = int(hash[:32], 16) 65 | %} 66 | return (header) 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /src/tx/ecdsa256k1.cairo: -------------------------------------------------------------------------------- 1 | from starkware.cairo.common.uint256 import Uint256 2 | 3 | from starkware.cairo.common.cairo_secp.signature import get_point_from_x 4 | from starkware.cairo.common.cairo_secp.bigint import BigInt3, uint256_to_bigint 5 | from starkware.cairo.common.cairo_secp.ec import EcPoint, ec_add, ec_mul 6 | from starkware.cairo.common.cairo_secp.signature import ( 7 | validate_signature_entry, 8 | get_generator_point, 9 | div_mod_n, 10 | ) 11 | 12 | # Verifies a Secp256k1 ECDSA signature. 13 | # Soundness assumptions: 14 | # * public_key_pt is on the curve. 15 | # * All the limbs of public_key_pt.x, public_key_pt.y, msg_hash are in the range [0, 3 * BASE). 16 | func _verify_ecdsa_secp256k1{range_check_ptr}( 17 | public_key_pt : EcPoint, msg_hash : BigInt3, r : BigInt3, s : BigInt3 18 | ): 19 | alloc_locals 20 | 21 | with_attr error_message("Signature out of range."): 22 | validate_signature_entry(r) 23 | validate_signature_entry(s) 24 | end 25 | 26 | let (gen_pt : EcPoint) = get_generator_point() 27 | 28 | # Compute u1 and u2. 29 | let (u1 : BigInt3) = div_mod_n(msg_hash, s) 30 | let (u2 : BigInt3) = div_mod_n(r, s) 31 | 32 | # The following assert also implies that res is not the zero point. 33 | with_attr error_message("Invalid signature."): 34 | let (gen_u1 : EcPoint) = ec_mul(gen_pt, u1) 35 | let (pub_u2 : EcPoint) = ec_mul(public_key_pt, u2) 36 | let (res) = ec_add(gen_u1, pub_u2) 37 | assert res.x = r 38 | end 39 | 40 | return () 41 | end 42 | 43 | func get_ecpoint_from_pubkey{range_check_ptr}(x : Uint256, y : Uint256) -> (ec : EcPoint): 44 | if (y.low - 2) * (y.low - 3) == 0: 45 | let (x1 : BigInt3) = uint256_to_bigint(x) 46 | let (ec : EcPoint) = get_point_from_x(x1, y.low) 47 | return (ec=ec) 48 | end 49 | let (x1 : BigInt3) = uint256_to_bigint(x) 50 | let (y1 : BigInt3) = uint256_to_bigint(y) 51 | return (ec=EcPoint(x1, y1)) 52 | end 53 | 54 | func verify_ecdsa_secp256k1{range_check_ptr}( 55 | point_x : Uint256, point_y : Uint256, tx_hash : Uint256, sig_r : Uint256, sig_s : Uint256 56 | ): 57 | let (ec : EcPoint) = get_ecpoint_from_pubkey(point_x, point_y) 58 | let (r : BigInt3) = uint256_to_bigint(sig_r) 59 | let (s : BigInt3) = uint256_to_bigint(sig_s) 60 | let (z : BigInt3) = uint256_to_bigint(tx_hash) 61 | _verify_ecdsa_secp256k1(ec, z, r, s) 62 | return () 63 | end 64 | -------------------------------------------------------------------------------- /src/tx/merkle_root.cairo: -------------------------------------------------------------------------------- 1 | from starkware.cairo.common.uint256 import Uint256 2 | from starkware.cairo.common.alloc import alloc 3 | 4 | func hash_chain(a : Uint256*, a_len : felt, c : Uint256*) -> (c_end : Uint256*): 5 | alloc_locals 6 | 7 | if (a_len + 1) * a_len == 0: 8 | return (c_end=c) 9 | end 10 | 11 | let x = [a] 12 | local y : Uint256 13 | if a_len == 1: 14 | assert y = [a] 15 | else: 16 | assert y = a[1] 17 | end 18 | 19 | local z : Uint256 20 | 21 | %{ 22 | from hashlib import sha256 23 | 24 | sha = sha256() 25 | sha.update(ids.x.low.to_bytes(16, 'little')) 26 | sha.update(ids.x.high.to_bytes(16, 'little')) 27 | sha.update(ids.y.low.to_bytes(16, 'little')) 28 | sha.update(ids.y.high.to_bytes(16, 'little')) 29 | h = sha256(sha.digest()).digest()[::-1] 30 | ids.z.low = int.from_bytes(h[16:], "big") 31 | ids.z.high = int.from_bytes(h[:16], "big") 32 | %} 33 | 34 | assert [c] = z 35 | 36 | return hash_chain(a + Uint256.SIZE * 2, a_len - 2, c + Uint256.SIZE) 37 | end 38 | 39 | func build_merkle_root(a : Uint256*, a_len : felt) -> (res : Uint256): 40 | alloc_locals 41 | 42 | if a_len == 1: 43 | return (res=[a]) 44 | end 45 | 46 | let (local c_start : Uint256*) = alloc() 47 | let (c_end : Uint256*) = hash_chain(a, a_len, c_start) 48 | tempvar c_len = (c_end - c_start) / Uint256.SIZE 49 | 50 | return build_merkle_root(a=c_start, a_len=c_len) 51 | end 52 | -------------------------------------------------------------------------------- /src/tx/model.cairo: -------------------------------------------------------------------------------- 1 | from starkware.cairo.common.uint256 import Uint256 2 | 3 | struct Point: 4 | member x : Uint256 5 | member y : Uint256 6 | end 7 | 8 | struct Signature: 9 | member r : Uint256 10 | member s : Uint256 11 | end 12 | 13 | struct SignatureVerification: 14 | member pub_key : Point 15 | member signature : Signature 16 | member digest : Uint256 17 | end 18 | -------------------------------------------------------------------------------- /src/tx/test_ecdsa256k1.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.uint256 import Uint256 4 | from starkware.cairo.common.cairo_secp.ec import EcPoint 5 | from starkware.cairo.common.cairo_secp.bigint import BigInt3 6 | 7 | from tx.ecdsa256k1 import _verify_ecdsa_secp256k1, verify_ecdsa_secp256k1 8 | from tx.model import Signature, Point, SignatureVerification 9 | from tx.test_utils import test_utils 10 | 11 | # stark template 12 | @view 13 | func test_ecsda_secp256k1{range_check_ptr}(): 14 | let public_key_pt = EcPoint( 15 | BigInt3(0x35dec240d9f76e20b48b41, 0x27fcb378b533f57a6b585, 0xbff381888b165f92dd33d), 16 | BigInt3(0x1711d8fb6fbbf53986b57f, 0x2e56f964d38cb8dbdeb30b, 0xe4be2a8547d802dc42041), 17 | ) 18 | let r = BigInt3(0x2e6c77fee73f3ac9be1217, 0x3f0c0b121ac1dc3e5c03c6, 0xeee3e6f50c576c07d7e4a) 19 | let s = BigInt3(0x20a4b46d3c5e24cda81f22, 0x967bf895824330d4273d0, 0x541e10c21560da25ada4c) 20 | let msg_hash = BigInt3( 21 | 0x38a23ca66202c8c2a72277, 0x6730e765376ff17ea8385, 0xca1ad489ab60ea581e6c1 22 | ) 23 | _verify_ecdsa_secp256k1(public_key_pt=public_key_pt, msg_hash=msg_hash, r=r, s=s) 24 | return () 25 | end 26 | 27 | func validate_ecdsa_secp256k1{range_check_ptr}(sv : SignatureVerification): 28 | # test_utils.print_ecdsa_params(sv) 29 | verify_ecdsa_secp256k1(sv.pub_key.x, sv.pub_key.y, sv.digest, sv.signature.r, sv.signature.s) 30 | return () 31 | end 32 | 33 | @view 34 | func test_p2pkh_with_compressed_pubkey02{range_check_ptr}(): 35 | let (sv : SignatureVerification) = test_utils.load_p2pkh_tx_from_json( 36 | './resources/tx/p2pkh02.json' 37 | ) 38 | validate_ecdsa_secp256k1(sv) 39 | return () 40 | end 41 | 42 | @view 43 | func test_p2pkh_with_compressed_pubkey03{range_check_ptr}(): 44 | let (sv : SignatureVerification) = test_utils.load_p2pkh_tx_from_json( 45 | './resources/tx/p2pkh03.json' 46 | ) 47 | validate_ecdsa_secp256k1(sv) 48 | return () 49 | end 50 | 51 | # uncompressed pubkey 52 | @view 53 | func test_p2pkh_with_uncompressed_pubkey04{range_check_ptr}(): 54 | let (sv : SignatureVerification) = test_utils.load_p2pkh_tx_from_json( 55 | './resources/tx/p2pkh04.json' 56 | ) 57 | validate_ecdsa_secp256k1(sv) 58 | return () 59 | end 60 | -------------------------------------------------------------------------------- /src/tx/test_merkle.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.uint256 import Uint256, uint256_eq 4 | from starkware.cairo.common.alloc import alloc 5 | from starkware.cairo.common.bool import TRUE, FALSE 6 | 7 | from tx.test_utils import test_utils 8 | from tx.merkle_root import build_merkle_root 9 | 10 | @view 11 | func test_merkle_root_block01{range_check_ptr}(): 12 | alloc_locals 13 | local root1 : Uint256 14 | let (tx : Uint256*, tx_len : felt, root1 : Uint256) = test_utils.load_merkle_tx_from_json( 15 | './resources/blocks/block1.json' 16 | ) 17 | let (root2 : Uint256) = build_merkle_root(tx, tx_len) 18 | let (is_true_root) = uint256_eq(root1, root2) 19 | assert is_true_root = TRUE 20 | return () 21 | end 22 | 23 | @view 24 | func test_merkle_root_block170{range_check_ptr}(): 25 | alloc_locals 26 | local root1 : Uint256 27 | let (tx : Uint256*, tx_len : felt, root1 : Uint256) = test_utils.load_merkle_tx_from_json( 28 | './resources/blocks/b170.json' 29 | ) 30 | let (root2 : Uint256) = build_merkle_root(tx, tx_len) 31 | let (is_true_root) = uint256_eq(root1, root2) 32 | assert is_true_root = TRUE 33 | return () 34 | end 35 | 36 | @view 37 | func test_merkle_root_block746298{range_check_ptr}(): 38 | alloc_locals 39 | local root1 : Uint256 40 | let (tx : Uint256*, tx_len : felt, root1 : Uint256) = test_utils.load_merkle_tx_from_json( 41 | './resources/blocks/b746298.json' 42 | ) 43 | let (root2 : Uint256) = build_merkle_root(tx, tx_len) 44 | let (is_true_root) = uint256_eq(root1, root2) 45 | assert is_true_root = TRUE 46 | return () 47 | end 48 | -------------------------------------------------------------------------------- /src/tx/test_utils.cairo: -------------------------------------------------------------------------------- 1 | from starkware.cairo.common.uint256 import Uint256 2 | from starkware.cairo.common.alloc import alloc 3 | 4 | from tx.model import SignatureVerification 5 | 6 | namespace test_utils: 7 | func p2pkh_helper(): 8 | %{ 9 | import hashlib 10 | from binascii import unhexlify 11 | 12 | def generate_pubkey_script(key_hex): 13 | sha = hashlib.sha256() 14 | rip = hashlib.new('ripemd160') 15 | sha.update(unhexlify(key_hex)) 16 | rip.update(sha.digest()) 17 | return f'1976a914{rip.hexdigest()}88ac' 18 | 19 | def decode_signature_script(sig): 20 | s1 = sig 21 | sig = s1[2:] 22 | s_len = int(sig[6:8],16)*2 23 | r_hex = sig[8:8+s_len] 24 | if int(r_hex[0:2],16) == 0: r_hex = r_hex[2:] 25 | r_len = int(sig[10+s_len:12+s_len],16)*2 26 | s_hex = sig[12+s_len:12+s_len+r_len] 27 | key_offset = int(s1[:2], 16)*2 + 2 28 | k_len = int(s1[key_offset:key_offset+2], 16)*2 29 | key_hex = s1[-k_len:] 30 | return (r_hex, s_hex, key_hex) 31 | 32 | def digest_raw_transaction(tx_hex): 33 | tx_bin = unhexlify(rawtx_hex) 34 | tx_hex = hashlib.sha256(hashlib.sha256(tx_bin).digest()).hexdigest() 35 | return tx_hex 36 | 37 | def generate_hex_vout(vout): 38 | vout = [(v["scriptPubKey"]["hex"], int(v["value"]*10**8).to_bytes(8, 'little').hex()) for v in vout] 39 | voutHex = ''.join(f'{v}{int(len(s)/2).to_bytes(1, "little").hex()}{s}' for (s, v) in vout) 40 | coutHex = len(vout).to_bytes(1, 'little').hex() 41 | return (coutHex, voutHex) 42 | %} 43 | return () 44 | end 45 | 46 | func load_p2pkh_tx_from_json(file_name : felt) -> (sv : SignatureVerification): 47 | alloc_locals 48 | local sv : SignatureVerification 49 | p2pkh_helper() 50 | %{ 51 | import json 52 | 53 | f_name_int = int(ids.file_name) 54 | file_name = f_name_int.to_bytes((f_name_int.bit_length() + 7 ) // 8, 'big').decode() 55 | with open(file_name, 'r') as f: tx = json.load(f) 56 | 57 | # tx raw 58 | vin = tx["vin"][0] 59 | vinCountHex = "01" 60 | versionHex = tx["version"].to_bytes(4, 'little').hex() 61 | previousTxHex = (bytes.fromhex(vin["txid"])[::-1].hex() if "txid" in vin else (0).to_bytes(32, 'little').hex()) 62 | 63 | # extract signature and pub key 64 | (r_hex, s_hex, key_hex) = decode_signature_script(vin["scriptSig"]["hex"]) 65 | 66 | # extract out elements 67 | (coutHex, voutHex) = generate_hex_vout(tx["vout"]) 68 | 69 | # generate pubkey script 70 | voutIndexHex = vin["vout"].to_bytes(4, 'little').hex() 71 | vinHex = generate_pubkey_script(key_hex) 72 | 73 | seqHex = "ffffffff" 74 | suffixHex = "0000000001000000" 75 | 76 | rawtx_hex = ( 77 | versionHex + vinCountHex + previousTxHex + voutIndexHex + vinHex + seqHex + coutHex + voutHex + suffixHex 78 | ) 79 | 80 | tx_hex = digest_raw_transaction(rawtx_hex) 81 | 82 | # if uncompressed pubkey 83 | if len(key_hex) > 128: 84 | x = key_hex[2:66] 85 | y = key_hex[-64:] 86 | ids.sv.pub_key.x.low = int(x[32:], 16) 87 | ids.sv.pub_key.x.high = int(x[:32], 16) 88 | ids.sv.pub_key.y.low = int(y[32:], 16) 89 | ids.sv.pub_key.y.high = int(y[:32], 16) 90 | else: 91 | x = key_hex[2:66] 92 | ids.sv.pub_key.x.low = int(x[32:], 16) 93 | ids.sv.pub_key.x.high = int(x[:32], 16) 94 | ids.sv.pub_key.y.low = int(key_hex[:2], 16) 95 | ids.sv.pub_key.y.high = 0 96 | 97 | ids.sv.signature.r.low = int(r_hex[32:], 16) 98 | ids.sv.signature.r.high = int(r_hex[:32], 16) 99 | ids.sv.signature.s.low = int(s_hex[32:], 16) 100 | ids.sv.signature.s.high = int(s_hex[:32], 16) 101 | ids.sv.digest.low = int(tx_hex[32:], 16) 102 | ids.sv.digest.high = int(tx_hex[:32], 16) 103 | %} 104 | return (sv) 105 | end 106 | 107 | func load_merkle_tx_from_json(file_name : felt) -> ( 108 | tx : Uint256*, tx_len : felt, root : Uint256 109 | ): 110 | alloc_locals 111 | local tx_root : Uint256 112 | local tx : Uint256* 113 | local tx_len 114 | %{ 115 | import json 116 | 117 | f_name_int = int(ids.file_name) 118 | file_name = f_name_int.to_bytes((f_name_int.bit_length() + 7 ) // 8, 'big').decode() 119 | with open(file_name, 'r') as f: j = json.load(f) 120 | 121 | ids.tx = tx = segments.add() 122 | for i, x in enumerate(j["tx"]): 123 | memory[tx + i*2] = int(x[32:], 16) 124 | memory[tx + i*2 + 1] = int(x[:32], 16) 125 | ids.tx_len = len(j["tx"]) 126 | 127 | x = j["merkleroot"] 128 | ids.tx_root.low = int(x[32:], 16) 129 | ids.tx_root.high = int(x[:32], 16) 130 | %} 131 | return (tx=tx, tx_len=tx_len, root=tx_root) 132 | end 133 | 134 | func print_ecdsa_params(sv : SignatureVerification): 135 | %{ 136 | x = ids.sv 137 | print(f'pub_x: {x.pub_key.x.high:032x}{x.pub_key.x.low:032x}') 138 | print(f'pub_y: {x.pub_key.y.high:032x}{x.pub_key.y.low:032x}') 139 | print(f'sig_r: {x.signature.r.high:032x}{x.signature.r.low:032x}') 140 | print(f'sig_s: {x.signature.s.high:032x}{x.signature.s.low:032x}') 141 | print(f'hash_: {x.digest.high:032x}{x.digest.low:032x}') 142 | %} 143 | return () 144 | end 145 | end 146 | -------------------------------------------------------------------------------- /src/utils/array.cairo: -------------------------------------------------------------------------------- 1 | func arr_eq(a : felt*, a_len : felt, b : felt*, b_len : felt) -> (res : felt): 2 | if a_len != b_len: 3 | return (0) 4 | end 5 | if a_len == 0: 6 | return (1) 7 | end 8 | return _arr_eq(a=a, a_len=a_len, b=b, b_len=b_len, current_index=a_len - 1) 9 | end 10 | 11 | func _arr_eq(a : felt*, a_len : felt, b : felt*, b_len : felt, current_index : felt) -> ( 12 | res : felt 13 | ): 14 | if current_index == -1: 15 | return (1) 16 | end 17 | 18 | if a[current_index] != b[current_index]: 19 | return (0) 20 | end 21 | return _arr_eq(a=a, a_len=a_len, b=b, b_len=b_len, current_index=current_index - 1) 22 | end 23 | -------------------------------------------------------------------------------- /src/utils/bits.cairo: -------------------------------------------------------------------------------- 1 | from starkware.cairo.common.cairo_builtins import BitwiseBuiltin 2 | from starkware.cairo.common.registers import get_label_location 3 | from starkware.cairo.common.math import unsigned_div_rem 4 | from starkware.cairo.common.bitwise import bitwise_and 5 | from starkware.cairo.common.bool import TRUE, FALSE 6 | from starkware.cairo.common.math_cmp import is_le 7 | from starkware.cairo.common.memcpy import memcpy 8 | from starkware.cairo.common.alloc import alloc 9 | 10 | namespace Bits: 11 | func merge{range_check_ptr}(a : felt*, a_nb_bits : felt, b : felt*, b_nb_bits : felt) -> ( 12 | merged : felt*, merged_nb_bits : felt 13 | ): 14 | # b must not be null 15 | alloc_locals 16 | let (merged) = alloc() 17 | let (a_full_words, a_rest) = unsigned_div_rem(a_nb_bits, 32) 18 | memcpy(merged, a, a_full_words) 19 | let (b_full_words, b_rest) = unsigned_div_rem(b_nb_bits, 32) 20 | # if a is exactly made of 32-bits words 21 | if a_rest == 0: 22 | local exact_len 23 | # if b is exactly made of 32-bits words 24 | if b_rest == 0: 25 | exact_len = b_full_words 26 | else: 27 | exact_len = b_full_words + 1 28 | end 29 | memcpy(merged + a_full_words, b, exact_len) 30 | return (merged, a_nb_bits + b_nb_bits) 31 | end 32 | 33 | # this contains a_rest bits at the left 34 | let left = a[a_full_words] 35 | # this contains 32-a_rest bits at the right 36 | let (right) = rightshift([b], a_rest) 37 | assert merged[a_full_words] = left + right 38 | 39 | let shift = 32 - a_rest 40 | extract(b, shift, b_nb_bits - shift, merged + a_full_words + 1) 41 | return (merged, a_nb_bits + b_nb_bits) 42 | end 43 | 44 | func extract{range_check_ptr}(input : felt*, start : felt, len : felt, output : felt*) -> (): 45 | # Write len bits from input to output, starting at start. 46 | # 47 | # Parameters: 48 | # input: The input bits as 32-bit integers 49 | # start: The start bit (included) 50 | # len: The number of bits to write 51 | # output: Where to write the output 52 | if len == 0: 53 | return () 54 | end 55 | alloc_locals 56 | 57 | let (test) = is_le(len, 32) 58 | local to_dump 59 | if test == TRUE: 60 | assert to_dump = len 61 | else: 62 | assert to_dump = 32 63 | end 64 | 65 | let (words_len, shift) = unsigned_div_rem(start, 32) 66 | let (test2) = is_le(to_dump + shift, 32) 67 | # erase the shift first bits and move to the left 68 | let (left) = Bits.leftshift(input[words_len], shift) 69 | local right 70 | if test2 == FALSE: 71 | # erase the shift last bits and move to the right 72 | let (value) = Bits.rightshift(input[words_len + 1], 32 - shift) 73 | assert right = value 74 | else: 75 | assert right = 0 76 | end 77 | # erase without shifting 78 | let (powed) = pow2(32 - to_dump) 79 | let (erased_and_shifted, _) = unsigned_div_rem(left + right, powed) 80 | assert [output] = erased_and_shifted * powed 81 | return extract(input, start + to_dump, len - to_dump, output + 1) 82 | end 83 | 84 | func rightshift{range_check_ptr}(word : felt, n : felt) -> (word : felt): 85 | # Shift bits to the right and lose values 86 | # 87 | # Parameters: 88 | # word: A 32-bits word 89 | # n: The amount of bits to shift 90 | # 91 | # Returns: 92 | # word: The word with the last n bits shifted. 93 | let (divisor) = pow2(n) 94 | let (p, _) = unsigned_div_rem(word, divisor) 95 | return (p) 96 | end 97 | 98 | func leftshift{range_check_ptr}(word : felt, n : felt) -> (word : felt): 99 | # Shift bits to the left and lose values 100 | # 101 | # Parameters: 102 | # word: A 32-bits word 103 | # n: The amount of bits to shift 104 | # 105 | # Returns: 106 | # word: The word with the first n bits shifted. 107 | alloc_locals 108 | let (divisor) = pow2(32 - n) 109 | let (_, r) = unsigned_div_rem(word, divisor) 110 | let (multiplicator) = pow2(n) 111 | return (multiplicator * r) 112 | end 113 | 114 | func rightrotate{range_check_ptr}(word : felt, n : felt) -> (word : felt): 115 | # Shift bits to the right and move values to the left 116 | # 117 | # Parameters: 118 | # word: A 32-bits word 119 | # n: The amount of bits to rotate 120 | # 121 | # Returns: 122 | # word: The word with the last n bits rotated. 123 | alloc_locals 124 | let (d) = pow2(n) 125 | let (p, r) = unsigned_div_rem(word, d) 126 | # %{ print("d:", ids.d, "p:", ids.p, "r:", ids.r) %} 127 | let (m) = pow2(32 - n) 128 | return (p + r * m) 129 | end 130 | 131 | func negate{range_check_ptr}(word : felt) -> (word : felt): 132 | # Negate bits (replace 1 by 0 and 0 by 1) of a 32-bits word 133 | # 134 | # Parameters: 135 | # word: A 32-bits word 136 | # 137 | # Returns: 138 | # word: The negated value 139 | return (4294967295 - word) 140 | end 141 | 142 | func pow2{range_check_ptr}(i) -> (res): 143 | # optimized pow2 stolen from warp source code 144 | let (data_address) = get_label_location(data) 145 | return ([data_address + i]) 146 | 147 | data: 148 | dw 0x1 149 | dw 0x2 150 | dw 0x4 151 | dw 0x8 152 | dw 0x10 153 | dw 0x20 154 | dw 0x40 155 | dw 0x80 156 | dw 0x100 157 | dw 0x200 158 | dw 0x400 159 | dw 0x800 160 | dw 0x1000 161 | dw 0x2000 162 | dw 0x4000 163 | dw 0x8000 164 | dw 0x10000 165 | dw 0x20000 166 | dw 0x40000 167 | dw 0x80000 168 | dw 0x100000 169 | dw 0x200000 170 | dw 0x400000 171 | dw 0x800000 172 | dw 0x1000000 173 | dw 0x2000000 174 | dw 0x4000000 175 | dw 0x8000000 176 | dw 0x10000000 177 | dw 0x20000000 178 | dw 0x40000000 179 | dw 0x80000000 180 | dw 0x100000000 181 | dw 0x200000000 182 | dw 0x400000000 183 | dw 0x800000000 184 | dw 0x1000000000 185 | dw 0x2000000000 186 | dw 0x4000000000 187 | dw 0x8000000000 188 | dw 0x10000000000 189 | dw 0x20000000000 190 | dw 0x40000000000 191 | dw 0x80000000000 192 | dw 0x100000000000 193 | dw 0x200000000000 194 | dw 0x400000000000 195 | dw 0x800000000000 196 | dw 0x1000000000000 197 | dw 0x2000000000000 198 | dw 0x4000000000000 199 | dw 0x8000000000000 200 | dw 0x10000000000000 201 | dw 0x20000000000000 202 | dw 0x40000000000000 203 | dw 0x80000000000000 204 | dw 0x100000000000000 205 | dw 0x200000000000000 206 | dw 0x400000000000000 207 | dw 0x800000000000000 208 | dw 0x1000000000000000 209 | dw 0x2000000000000000 210 | dw 0x4000000000000000 211 | dw 0x8000000000000000 212 | dw 0x10000000000000000 213 | dw 0x20000000000000000 214 | dw 0x40000000000000000 215 | dw 0x80000000000000000 216 | dw 0x100000000000000000 217 | dw 0x200000000000000000 218 | dw 0x400000000000000000 219 | dw 0x800000000000000000 220 | dw 0x1000000000000000000 221 | dw 0x2000000000000000000 222 | dw 0x4000000000000000000 223 | dw 0x8000000000000000000 224 | dw 0x10000000000000000000 225 | dw 0x20000000000000000000 226 | dw 0x40000000000000000000 227 | dw 0x80000000000000000000 228 | dw 0x100000000000000000000 229 | dw 0x200000000000000000000 230 | dw 0x400000000000000000000 231 | dw 0x800000000000000000000 232 | dw 0x1000000000000000000000 233 | dw 0x2000000000000000000000 234 | dw 0x4000000000000000000000 235 | dw 0x8000000000000000000000 236 | dw 0x10000000000000000000000 237 | dw 0x20000000000000000000000 238 | dw 0x40000000000000000000000 239 | dw 0x80000000000000000000000 240 | dw 0x100000000000000000000000 241 | dw 0x200000000000000000000000 242 | dw 0x400000000000000000000000 243 | dw 0x800000000000000000000000 244 | dw 0x1000000000000000000000000 245 | dw 0x2000000000000000000000000 246 | dw 0x4000000000000000000000000 247 | dw 0x8000000000000000000000000 248 | dw 0x10000000000000000000000000 249 | dw 0x20000000000000000000000000 250 | dw 0x40000000000000000000000000 251 | dw 0x80000000000000000000000000 252 | dw 0x100000000000000000000000000 253 | dw 0x200000000000000000000000000 254 | dw 0x400000000000000000000000000 255 | dw 0x800000000000000000000000000 256 | dw 0x1000000000000000000000000000 257 | dw 0x2000000000000000000000000000 258 | dw 0x4000000000000000000000000000 259 | dw 0x8000000000000000000000000000 260 | dw 0x10000000000000000000000000000 261 | dw 0x20000000000000000000000000000 262 | dw 0x40000000000000000000000000000 263 | dw 0x80000000000000000000000000000 264 | dw 0x100000000000000000000000000000 265 | dw 0x200000000000000000000000000000 266 | dw 0x400000000000000000000000000000 267 | dw 0x800000000000000000000000000000 268 | dw 0x1000000000000000000000000000000 269 | dw 0x2000000000000000000000000000000 270 | dw 0x4000000000000000000000000000000 271 | dw 0x8000000000000000000000000000000 272 | dw 0x10000000000000000000000000000000 273 | dw 0x20000000000000000000000000000000 274 | dw 0x40000000000000000000000000000000 275 | dw 0x80000000000000000000000000000000 276 | dw 0x100000000000000000000000000000000 277 | dw 0x200000000000000000000000000000000 278 | dw 0x400000000000000000000000000000000 279 | dw 0x800000000000000000000000000000000 280 | dw 0x1000000000000000000000000000000000 281 | dw 0x2000000000000000000000000000000000 282 | dw 0x4000000000000000000000000000000000 283 | dw 0x8000000000000000000000000000000000 284 | dw 0x10000000000000000000000000000000000 285 | dw 0x20000000000000000000000000000000000 286 | dw 0x40000000000000000000000000000000000 287 | dw 0x80000000000000000000000000000000000 288 | dw 0x100000000000000000000000000000000000 289 | dw 0x200000000000000000000000000000000000 290 | dw 0x400000000000000000000000000000000000 291 | dw 0x800000000000000000000000000000000000 292 | dw 0x1000000000000000000000000000000000000 293 | dw 0x2000000000000000000000000000000000000 294 | dw 0x4000000000000000000000000000000000000 295 | dw 0x8000000000000000000000000000000000000 296 | dw 0x10000000000000000000000000000000000000 297 | dw 0x20000000000000000000000000000000000000 298 | dw 0x40000000000000000000000000000000000000 299 | dw 0x80000000000000000000000000000000000000 300 | dw 0x100000000000000000000000000000000000000 301 | dw 0x200000000000000000000000000000000000000 302 | dw 0x400000000000000000000000000000000000000 303 | dw 0x800000000000000000000000000000000000000 304 | dw 0x1000000000000000000000000000000000000000 305 | dw 0x2000000000000000000000000000000000000000 306 | dw 0x4000000000000000000000000000000000000000 307 | dw 0x8000000000000000000000000000000000000000 308 | dw 0x10000000000000000000000000000000000000000 309 | dw 0x20000000000000000000000000000000000000000 310 | dw 0x40000000000000000000000000000000000000000 311 | dw 0x80000000000000000000000000000000000000000 312 | dw 0x100000000000000000000000000000000000000000 313 | dw 0x200000000000000000000000000000000000000000 314 | dw 0x400000000000000000000000000000000000000000 315 | dw 0x800000000000000000000000000000000000000000 316 | dw 0x1000000000000000000000000000000000000000000 317 | dw 0x2000000000000000000000000000000000000000000 318 | dw 0x4000000000000000000000000000000000000000000 319 | dw 0x8000000000000000000000000000000000000000000 320 | dw 0x10000000000000000000000000000000000000000000 321 | dw 0x20000000000000000000000000000000000000000000 322 | dw 0x40000000000000000000000000000000000000000000 323 | dw 0x80000000000000000000000000000000000000000000 324 | dw 0x100000000000000000000000000000000000000000000 325 | dw 0x200000000000000000000000000000000000000000000 326 | dw 0x400000000000000000000000000000000000000000000 327 | dw 0x800000000000000000000000000000000000000000000 328 | dw 0x1000000000000000000000000000000000000000000000 329 | dw 0x2000000000000000000000000000000000000000000000 330 | dw 0x4000000000000000000000000000000000000000000000 331 | dw 0x8000000000000000000000000000000000000000000000 332 | dw 0x10000000000000000000000000000000000000000000000 333 | dw 0x20000000000000000000000000000000000000000000000 334 | dw 0x40000000000000000000000000000000000000000000000 335 | dw 0x80000000000000000000000000000000000000000000000 336 | dw 0x100000000000000000000000000000000000000000000000 337 | dw 0x200000000000000000000000000000000000000000000000 338 | dw 0x400000000000000000000000000000000000000000000000 339 | dw 0x800000000000000000000000000000000000000000000000 340 | dw 0x1000000000000000000000000000000000000000000000000 341 | dw 0x2000000000000000000000000000000000000000000000000 342 | dw 0x4000000000000000000000000000000000000000000000000 343 | dw 0x8000000000000000000000000000000000000000000000000 344 | dw 0x10000000000000000000000000000000000000000000000000 345 | dw 0x20000000000000000000000000000000000000000000000000 346 | dw 0x40000000000000000000000000000000000000000000000000 347 | dw 0x80000000000000000000000000000000000000000000000000 348 | dw 0x100000000000000000000000000000000000000000000000000 349 | dw 0x200000000000000000000000000000000000000000000000000 350 | dw 0x400000000000000000000000000000000000000000000000000 351 | dw 0x800000000000000000000000000000000000000000000000000 352 | dw 0x1000000000000000000000000000000000000000000000000000 353 | dw 0x2000000000000000000000000000000000000000000000000000 354 | dw 0x4000000000000000000000000000000000000000000000000000 355 | dw 0x8000000000000000000000000000000000000000000000000000 356 | dw 0x10000000000000000000000000000000000000000000000000000 357 | dw 0x20000000000000000000000000000000000000000000000000000 358 | dw 0x40000000000000000000000000000000000000000000000000000 359 | dw 0x80000000000000000000000000000000000000000000000000000 360 | dw 0x100000000000000000000000000000000000000000000000000000 361 | dw 0x200000000000000000000000000000000000000000000000000000 362 | dw 0x400000000000000000000000000000000000000000000000000000 363 | dw 0x800000000000000000000000000000000000000000000000000000 364 | dw 0x1000000000000000000000000000000000000000000000000000000 365 | dw 0x2000000000000000000000000000000000000000000000000000000 366 | dw 0x4000000000000000000000000000000000000000000000000000000 367 | dw 0x8000000000000000000000000000000000000000000000000000000 368 | dw 0x10000000000000000000000000000000000000000000000000000000 369 | dw 0x20000000000000000000000000000000000000000000000000000000 370 | dw 0x40000000000000000000000000000000000000000000000000000000 371 | dw 0x80000000000000000000000000000000000000000000000000000000 372 | dw 0x100000000000000000000000000000000000000000000000000000000 373 | dw 0x200000000000000000000000000000000000000000000000000000000 374 | dw 0x400000000000000000000000000000000000000000000000000000000 375 | dw 0x800000000000000000000000000000000000000000000000000000000 376 | dw 0x1000000000000000000000000000000000000000000000000000000000 377 | dw 0x2000000000000000000000000000000000000000000000000000000000 378 | dw 0x4000000000000000000000000000000000000000000000000000000000 379 | dw 0x8000000000000000000000000000000000000000000000000000000000 380 | dw 0x10000000000000000000000000000000000000000000000000000000000 381 | dw 0x20000000000000000000000000000000000000000000000000000000000 382 | dw 0x40000000000000000000000000000000000000000000000000000000000 383 | dw 0x80000000000000000000000000000000000000000000000000000000000 384 | dw 0x100000000000000000000000000000000000000000000000000000000000 385 | dw 0x200000000000000000000000000000000000000000000000000000000000 386 | dw 0x400000000000000000000000000000000000000000000000000000000000 387 | dw 0x800000000000000000000000000000000000000000000000000000000000 388 | dw 0x1000000000000000000000000000000000000000000000000000000000000 389 | dw 0x2000000000000000000000000000000000000000000000000000000000000 390 | dw 0x4000000000000000000000000000000000000000000000000000000000000 391 | dw 0x8000000000000000000000000000000000000000000000000000000000000 392 | dw 0x10000000000000000000000000000000000000000000000000000000000000 393 | dw 0x20000000000000000000000000000000000000000000000000000000000000 394 | dw 0x40000000000000000000000000000000000000000000000000000000000000 395 | dw 0x80000000000000000000000000000000000000000000000000000000000000 396 | dw 0x100000000000000000000000000000000000000000000000000000000000000 397 | dw 0x200000000000000000000000000000000000000000000000000000000000000 398 | dw 0x400000000000000000000000000000000000000000000000000000000000000 399 | end 400 | end 401 | -------------------------------------------------------------------------------- /src/utils/common.cairo: -------------------------------------------------------------------------------- 1 | from starkware.cairo.common.cairo_builtins import BitwiseBuiltin 2 | from starkware.cairo.common.math import assert_le 3 | from starkware.cairo.common.alloc import alloc 4 | from starkware.cairo.common.math import assert_nn_le, unsigned_div_rem 5 | from starkware.cairo.common.math_cmp import is_in_range 6 | from starkware.cairo.common.uint256 import Uint256 7 | from starkware.cairo.common.math import split_felt 8 | from starkware.cairo.common.pow import pow 9 | 10 | func swap_endianness_64{range_check_ptr, bitwise_ptr : BitwiseBuiltin*}( 11 | input : felt, size : felt 12 | ) -> (output : felt): 13 | alloc_locals 14 | let (local output : felt*) = alloc() 15 | 16 | # verifies word fits in 64bits 17 | assert_le(input, 2 ** 64 - 1) 18 | 19 | # swapped_bytes = ((word & 0xFF00FF00FF00FF00) >> 8) | ((word & 0x00FF00FF00FF00FF) << 8) 20 | let (left_part, _) = unsigned_div_rem(input, 256) 21 | 22 | assert bitwise_ptr[0].x = left_part 23 | assert bitwise_ptr[0].y = 0x00FF00FF00FF00FF 24 | 25 | assert bitwise_ptr[1].x = input * 256 26 | assert bitwise_ptr[1].y = 0xFF00FF00FF00FF00 27 | 28 | let swapped_bytes = bitwise_ptr[0].x_and_y + bitwise_ptr[1].x_and_y 29 | 30 | # swapped_2byte_pair = ((swapped_bytes & 0xFFFF0000FFFF0000) >> 16) | ((swapped_bytes & 0x0000FFFF0000FFFF) << 16) 31 | let (left_part2, _) = unsigned_div_rem(swapped_bytes, 2 ** 16) 32 | 33 | assert bitwise_ptr[2].x = left_part2 34 | assert bitwise_ptr[2].y = 0x0000FFFF0000FFFF 35 | 36 | assert bitwise_ptr[3].x = swapped_bytes * 2 ** 16 37 | assert bitwise_ptr[3].y = 0xFFFF0000FFFF0000 38 | 39 | let swapped_2bytes = bitwise_ptr[2].x_and_y + bitwise_ptr[3].x_and_y 40 | 41 | # swapped_4byte_pair = (swapped_2byte_pair >> 32) | ((swapped_2byte_pair << 32) % 2**64) 42 | let (left_part4, _) = unsigned_div_rem(swapped_2bytes, 2 ** 32) 43 | 44 | assert bitwise_ptr[4].x = swapped_2bytes * 2 ** 32 45 | assert bitwise_ptr[4].y = 0xFFFFFFFF00000000 46 | 47 | let swapped_4bytes = left_part4 + bitwise_ptr[4].x_and_y 48 | 49 | let bitwise_ptr = bitwise_ptr + 5 * BitwiseBuiltin.SIZE 50 | 51 | # Some Shiva-inspired code here 52 | let (local shift) = pow(2, ((8 - size) * 8)) 53 | 54 | if size == 8: 55 | return (swapped_4bytes) 56 | else: 57 | let (shifted_4bytes, _) = unsigned_div_rem(swapped_4bytes, shift) 58 | return (shifted_4bytes) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /src/utils/math.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.math_cmp import is_le 4 | from starkware.cairo.common.math import split_felt 5 | from starkware.cairo.common.uint256 import Uint256, uint256_le 6 | from starkware.cairo.common.bool import TRUE, FALSE 7 | from openzeppelin.security.safemath import SafeUint256 8 | 9 | func min{range_check_ptr}(a : felt, b : felt) -> (min : felt): 10 | let (a_is_le_b) = is_le(a, b) 11 | return (min=a * a_is_le_b + b * (1 - a_is_le_b)) 12 | end 13 | 14 | func max{range_check_ptr}(a : felt, b : felt) -> (max : felt): 15 | let (a_is_le_b) = is_le(a, b) 16 | return (max=a * (1 - a_is_le_b) + b * a_is_le_b) 17 | end 18 | 19 | func clamp{range_check_ptr}(value : felt, min_value : felt, max_value : felt) -> ( 20 | clamped_value : felt 21 | ): 22 | let (clamped_value) = max(value, min_value) 23 | let (clamped_value) = min(clamped_value, max_value) 24 | return (clamped_value) 25 | end 26 | 27 | func felt_to_Uint256{range_check_ptr}(value : felt) -> (value : Uint256): 28 | let (high, low) = split_felt(value) 29 | return (value=Uint256(low=low, high=high)) 30 | end 31 | 32 | func min_uint256{range_check_ptr}(a : Uint256, b : Uint256) -> (min : Uint256): 33 | let (a_is_le_b) = uint256_le(a, b) 34 | if a_is_le_b == TRUE: 35 | return (min=a) 36 | end 37 | return (min=b) 38 | end 39 | -------------------------------------------------------------------------------- /src/utils/sha256.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin 4 | from starkware.cairo.common.registers import get_label_location 5 | from starkware.cairo.common.bool import TRUE, FALSE 6 | from starkware.cairo.common.math import unsigned_div_rem 7 | from starkware.cairo.common.math_cmp import is_le 8 | from starkware.cairo.common.memcpy import memcpy 9 | from starkware.cairo.common.bitwise import bitwise_and 10 | from starkware.cairo.common.alloc import alloc 11 | from src.utils.bits import Bits 12 | 13 | func sha256{bitwise_ptr : BitwiseBuiltin*, range_check_ptr}(input : felt*, n_bits : felt) -> ( 14 | output : felt* 15 | ): 16 | # Computes SHA256 of 'input'. See https://en.wikipedia.org/wiki/SHA-2 17 | # 18 | # Parameters: 19 | # input: array of 32-bit words 20 | # n_bits: number of bits to consider from input 21 | # 22 | # Returns: 23 | # output: an array of 8 32-bit words (big endian). 24 | 25 | alloc_locals 26 | 27 | # Initialize hash values 28 | let (hash : felt*) = alloc() 29 | assert hash[0] = 0x6a09e667 30 | assert hash[1] = 0xbb67ae85 31 | assert hash[2] = 0x3c6ef372 32 | assert hash[3] = 0xa54ff53a 33 | assert hash[4] = 0x510e527f 34 | assert hash[5] = 0x9b05688c 35 | assert hash[6] = 0x1f83d9ab 36 | assert hash[7] = 0x5be0cd19 37 | 38 | # Pre-processing (Padding) 39 | 40 | let (len_chunks : felt, chunks : felt**) = create_chunks(input, n_bits, 0) 41 | 42 | return for_all_chunks(hash, len_chunks, chunks) 43 | end 44 | 45 | func create_chunks{range_check_ptr}(input : felt*, n_bits : felt, bits_prefix : felt) -> ( 46 | len_chunks : felt, chunks : felt** 47 | ): 48 | # Creates an array of chunks of length 512 bits (16 32-bit words) from 'input'. 49 | # 50 | # Parameters: 51 | # input: array of 32-bit words 52 | # n_bits: length of input 53 | # bits_prefix: number of bits to skip 54 | 55 | alloc_locals 56 | 57 | # if that's the last chunk 58 | # we need to append a single bit at 1, zeros and the length as a 64 bit integer 59 | # so that's 512-65=447 bits free 60 | let len = n_bits - bits_prefix 61 | 62 | # n_bits-bits_prefix <= 511 63 | let (test) = is_le(len, 511) 64 | if test == TRUE: 65 | let (msg : felt*) = alloc() 66 | Bits.extract(input, bits_prefix, len, msg) 67 | 68 | # one followed by 31 0 69 | let (one : felt*) = alloc() 70 | assert [one] = 2147483648 71 | 72 | # we will bind it to get full words 73 | let (full_words, _) = unsigned_div_rem(len, 32) 74 | let size = (full_words + 1) * 32 - len 75 | let (chunk : felt*, new_len : felt) = Bits.merge(msg, len, one, size) 76 | let words_len = new_len / 32 77 | 78 | let (test) = is_le(len, 447) 79 | # if that's the last chunk 80 | # we need to append 447-len '0' and len on 64 bits (2 felt words) 81 | # so that's 512-65=447 bits free 82 | if test == TRUE: 83 | let zero_words = 14 - words_len 84 | append_zeros(chunk + words_len, zero_words) 85 | # now chunk is 448 bits long = 14 words 86 | # todo: support > 32 bits longs size 87 | # current maximum size = 2^33-1 88 | # = 8589934591 bits ~= 8.6GB 89 | 90 | assert chunk[14] = 0 91 | assert chunk[15] = n_bits 92 | let (chunks : felt**) = alloc() 93 | assert chunks[0] = chunk 94 | return (1, chunks) 95 | else: 96 | # here we can put 0 until the 512 bits and get an empty next chunk 97 | let zero_words = 16 - words_len 98 | append_zeros(chunk + words_len, zero_words) 99 | let (chunks : felt**) = alloc() 100 | let (last_chunk) = alloc() 101 | assert last_chunk[15] = n_bits 102 | assert chunks[0] = last_chunk 103 | append_zeros(last_chunk, 15) 104 | assert chunks[1] = chunk 105 | return (2, chunks) 106 | end 107 | end 108 | 109 | # if 512 <= n_bits 110 | # 512/32 = 16 111 | let (len_chunks : felt, chunks : felt**) = create_chunks(input, n_bits, bits_prefix + 512) 112 | 113 | let (chunk : felt*) = alloc() 114 | Bits.extract(input, bits_prefix, 512, chunk) 115 | assert chunks[len_chunks] = chunk 116 | 117 | return (len_chunks + 1, chunks) 118 | end 119 | 120 | func append_zeros{range_check_ptr}(ptr : felt*, amount : felt): 121 | if amount == 0: 122 | return () 123 | end 124 | assert [ptr] = 0 125 | return append_zeros(ptr + 1, amount - 1) 126 | end 127 | 128 | func for_all_chunks{bitwise_ptr : BitwiseBuiltin*, range_check_ptr}( 129 | hash : felt*, chunks_len : felt, chunks : felt** 130 | ) -> (output : felt*): 131 | if chunks_len == 0: 132 | return (hash) 133 | end 134 | let chunk : felt* = chunks[chunks_len - 1] 135 | let (updated_hash : felt*) = process_chunk(chunk, hash) 136 | return for_all_chunks(updated_hash, chunks_len - 1, chunks) 137 | end 138 | 139 | const SHIFTS = 1 + 2 ** 35 + 2 ** (35 * 2) + 2 ** (35 * 3) + 2 ** (35 * 4) + 2 ** (35 * 5) + 140 | 2 ** (35 * 6) 141 | 142 | func process_chunk{bitwise_ptr : BitwiseBuiltin*, range_check_ptr}(chunk : felt*, hash : felt*) -> ( 143 | output : felt* 144 | ): 145 | alloc_locals 146 | # Extend the first 16 words into a total of 64 words 147 | compute_message_schedule(chunk) 148 | let (k : felt*) = get_constants() 149 | compute_compression(hash, chunk, k, 0) 150 | 151 | let shifted_hash : felt* = hash + 8 * 64 152 | 153 | # additions are mod 2^32 154 | let (_, mod32bits) = unsigned_div_rem(hash[0] + shifted_hash[0], 4294967296) 155 | assert shifted_hash[8] = mod32bits 156 | let (_, mod32bits) = unsigned_div_rem(hash[1] + shifted_hash[1], 4294967296) 157 | assert shifted_hash[9] = mod32bits 158 | let (_, mod32bits) = unsigned_div_rem(hash[2] + shifted_hash[2], 4294967296) 159 | assert shifted_hash[10] = mod32bits 160 | let (_, mod32bits) = unsigned_div_rem(hash[3] + shifted_hash[3], 4294967296) 161 | assert shifted_hash[11] = mod32bits 162 | let (_, mod32bits) = unsigned_div_rem(hash[4] + shifted_hash[4], 4294967296) 163 | assert shifted_hash[12] = mod32bits 164 | let (_, mod32bits) = unsigned_div_rem(hash[5] + shifted_hash[5], 4294967296) 165 | assert shifted_hash[13] = mod32bits 166 | let (_, mod32bits) = unsigned_div_rem(hash[6] + shifted_hash[6], 4294967296) 167 | assert shifted_hash[14] = mod32bits 168 | let (_, mod32bits) = unsigned_div_rem(hash[7] + shifted_hash[7], 4294967296) 169 | assert shifted_hash[15] = mod32bits 170 | 171 | return (shifted_hash + 8) 172 | end 173 | 174 | func compute_compression{bitwise_ptr : BitwiseBuiltin*, range_check_ptr}( 175 | hash : felt*, w : felt*, k : felt*, index : felt 176 | ): 177 | alloc_locals 178 | if index == 64: 179 | return () 180 | end 181 | 182 | # s1 := (h4 rightrotate 6) xor (h4 rightrotate 11) xor (h4 rightrotate 25) 183 | 184 | # %{ print("d:", ids.d, "p:", ids.p, "r:", ids.r) %} 185 | 186 | let (p, r) = unsigned_div_rem(hash[4], 2 ** 6) 187 | assert bitwise_ptr[0].x = (p + r * 2 ** (32 - 6)) 188 | let (p, r) = unsigned_div_rem(hash[4], 2 ** 11) 189 | assert bitwise_ptr[0].y = (p + r * 2 ** (32 - 11)) 190 | assert bitwise_ptr[1].x = bitwise_ptr[0].x_xor_y 191 | let (p, r) = unsigned_div_rem(hash[4], 2 ** 25) 192 | assert bitwise_ptr[1].y = (p + r * 2 ** (32 - 25)) 193 | let s1 : felt = bitwise_ptr[1].x_xor_y 194 | 195 | # ch := (h4 and h5) xor ((not h4) and h6) 196 | assert bitwise_ptr[2].x = hash[4] 197 | assert bitwise_ptr[2].y = hash[5] 198 | assert bitwise_ptr[3].x = 4294967295 - hash[4] 199 | assert bitwise_ptr[3].y = hash[6] 200 | assert bitwise_ptr[4].x = bitwise_ptr[2].x_and_y 201 | assert bitwise_ptr[4].y = bitwise_ptr[3].x_and_y 202 | let ch : felt = bitwise_ptr[4].x_xor_y 203 | 204 | # temp1 := hash[7] + s1 + ch + k[i] + w[i] 205 | local temp1 : felt = hash[7] + s1 + ch + k[index] + w[index] 206 | 207 | # s0 := (hash[0] rightrotate 2) xor (hash[0] rightrotate 13) xor (a rightrotate 22) 208 | let (p, r) = unsigned_div_rem(hash[0], 2 ** 2) 209 | assert bitwise_ptr[5].x = p + r * 2 ** (32 - 2) 210 | let (p, r) = unsigned_div_rem(hash[0], 2 ** 13) 211 | assert bitwise_ptr[5].y = p + r * 2 ** (32 - 13) 212 | assert bitwise_ptr[6].x = bitwise_ptr[5].x_xor_y 213 | let (p, r) = unsigned_div_rem(hash[0], 2 ** 22) 214 | assert bitwise_ptr[6].y = p + r * 2 ** (32 - 22) 215 | let s0 : felt = bitwise_ptr[6].x_xor_y 216 | 217 | # maj := (hash[0] and hash[1]) xor (hash[0] and hash[2]) xor (hash[1] and hash[2]) 218 | assert bitwise_ptr[7].x = hash[0] 219 | assert bitwise_ptr[7].y = hash[1] 220 | let a = bitwise_ptr[7].x_and_y 221 | assert bitwise_ptr[8].x = hash[0] 222 | assert bitwise_ptr[8].y = hash[2] 223 | let b = bitwise_ptr[8].x_and_y 224 | assert bitwise_ptr[9].x = hash[1] 225 | assert bitwise_ptr[9].y = hash[2] 226 | let c = bitwise_ptr[9].x_and_y 227 | assert bitwise_ptr[10].x = a 228 | assert bitwise_ptr[10].y = b 229 | let a = bitwise_ptr[10].x_xor_y 230 | assert bitwise_ptr[11].x = a 231 | assert bitwise_ptr[11].y = c 232 | let maj : felt = bitwise_ptr[11].x_xor_y 233 | 234 | # additions are mod 2^32 235 | let (_, mod32bits) = unsigned_div_rem(temp1 + s0 + maj, 4294967296) 236 | assert hash[8] = mod32bits 237 | assert hash[9] = hash[0] 238 | assert hash[10] = hash[1] 239 | assert hash[11] = hash[2] 240 | let (_, mod32bits) = unsigned_div_rem(hash[3] + temp1, 4294967296) 241 | assert hash[12] = mod32bits 242 | assert hash[13] = hash[4] 243 | assert hash[14] = hash[5] 244 | assert hash[15] = hash[6] 245 | 246 | local bitwise_ptr : BitwiseBuiltin* = bitwise_ptr + 12 * BitwiseBuiltin.SIZE 247 | return compute_compression(hash + 8, w, k, index + 1) 248 | end 249 | 250 | func compute_message_schedule{bitwise_ptr : BitwiseBuiltin*}(message : felt*): 251 | # Code from Lior's implementation 252 | # Given an array of size 16, extends it to the message schedule array (of size 64) by writing 253 | # 48 more values. 254 | # Each element represents 7 32-bit words from 7 difference instances, starting at bits 255 | # 0, 35, 35 * 2, ..., 35 * 6. 256 | 257 | alloc_locals 258 | 259 | # Defining the following constants as local variables saves some instructions. 260 | local shift_mask3 = SHIFTS * (2 ** 32 - 2 ** 3) 261 | local shift_mask7 = SHIFTS * (2 ** 32 - 2 ** 7) 262 | local shift_mask10 = SHIFTS * (2 ** 32 - 2 ** 10) 263 | local shift_mask17 = SHIFTS * (2 ** 32 - 2 ** 17) 264 | local shift_mask18 = SHIFTS * (2 ** 32 - 2 ** 18) 265 | local shift_mask19 = SHIFTS * (2 ** 32 - 2 ** 19) 266 | local mask32ones = SHIFTS * (2 ** 32 - 1) 267 | 268 | # Loop variables. 269 | tempvar bitwise_ptr = bitwise_ptr 270 | tempvar message = message + 16 271 | tempvar n = 64 - 16 272 | 273 | loop: 274 | # Compute s0 = right_rot(w[i - 15], 7) ^ right_rot(w[i - 15], 18) ^ (w[i - 15] >> 3). 275 | tempvar w0 = message[-15] 276 | assert bitwise_ptr[0].x = w0 277 | assert bitwise_ptr[0].y = shift_mask7 278 | let w0_rot7 = (2 ** (32 - 7)) * w0 + (1 / 2 ** 7 - 2 ** (32 - 7)) * bitwise_ptr[0].x_and_y 279 | assert bitwise_ptr[1].x = w0 280 | assert bitwise_ptr[1].y = shift_mask18 281 | let w0_rot18 = (2 ** (32 - 18)) * w0 + (1 / 2 ** 18 - 2 ** (32 - 18)) * bitwise_ptr[1].x_and_y 282 | assert bitwise_ptr[2].x = w0 283 | assert bitwise_ptr[2].y = shift_mask3 284 | let w0_shift3 = (1 / 2 ** 3) * bitwise_ptr[2].x_and_y 285 | assert bitwise_ptr[3].x = w0_rot7 286 | assert bitwise_ptr[3].y = w0_rot18 287 | assert bitwise_ptr[4].x = bitwise_ptr[3].x_xor_y 288 | assert bitwise_ptr[4].y = w0_shift3 289 | let s0 = bitwise_ptr[4].x_xor_y 290 | let bitwise_ptr = bitwise_ptr + 5 * BitwiseBuiltin.SIZE 291 | 292 | # Compute s1 = right_rot(w[i - 2], 17) ^ right_rot(w[i - 2], 19) ^ (w[i - 2] >> 10). 293 | tempvar w1 = message[-2] 294 | assert bitwise_ptr[0].x = w1 295 | assert bitwise_ptr[0].y = shift_mask17 296 | let w1_rot17 = (2 ** (32 - 17)) * w1 + (1 / 2 ** 17 - 2 ** (32 - 17)) * bitwise_ptr[0].x_and_y 297 | assert bitwise_ptr[1].x = w1 298 | assert bitwise_ptr[1].y = shift_mask19 299 | let w1_rot19 = (2 ** (32 - 19)) * w1 + (1 / 2 ** 19 - 2 ** (32 - 19)) * bitwise_ptr[1].x_and_y 300 | assert bitwise_ptr[2].x = w1 301 | assert bitwise_ptr[2].y = shift_mask10 302 | let w1_shift10 = (1 / 2 ** 10) * bitwise_ptr[2].x_and_y 303 | assert bitwise_ptr[3].x = w1_rot17 304 | assert bitwise_ptr[3].y = w1_rot19 305 | assert bitwise_ptr[4].x = bitwise_ptr[3].x_xor_y 306 | assert bitwise_ptr[4].y = w1_shift10 307 | let s1 = bitwise_ptr[4].x_xor_y 308 | let bitwise_ptr = bitwise_ptr + 5 * BitwiseBuiltin.SIZE 309 | 310 | assert bitwise_ptr[0].x = message[-16] + s0 + message[-7] + s1 311 | assert bitwise_ptr[0].y = mask32ones 312 | assert message[0] = bitwise_ptr[0].x_and_y 313 | let bitwise_ptr = bitwise_ptr + BitwiseBuiltin.SIZE 314 | 315 | tempvar bitwise_ptr = bitwise_ptr 316 | tempvar message = message + 1 317 | tempvar n = n - 1 318 | jmp loop if n != 0 319 | 320 | return () 321 | end 322 | 323 | func get_constants() -> (data : felt*): 324 | let (data_address) = get_label_location(data_start) 325 | return (data=cast(data_address, felt*)) 326 | 327 | data_start: 328 | dw 0x428a2f98 329 | dw 0x71374491 330 | dw 0xb5c0fbcf 331 | dw 0xe9b5dba5 332 | dw 0x3956c25b 333 | dw 0x59f111f1 334 | dw 0x923f82a4 335 | dw 0xab1c5ed5 336 | dw 0xd807aa98 337 | dw 0x12835b01 338 | dw 0x243185be 339 | dw 0x550c7dc3 340 | dw 0x72be5d74 341 | dw 0x80deb1fe 342 | dw 0x9bdc06a7 343 | dw 0xc19bf174 344 | dw 0xe49b69c1 345 | dw 0xefbe4786 346 | dw 0x0fc19dc6 347 | dw 0x240ca1cc 348 | dw 0x2de92c6f 349 | dw 0x4a7484aa 350 | dw 0x5cb0a9dc 351 | dw 0x76f988da 352 | dw 0x983e5152 353 | dw 0xa831c66d 354 | dw 0xb00327c8 355 | dw 0xbf597fc7 356 | dw 0xc6e00bf3 357 | dw 0xd5a79147 358 | dw 0x06ca6351 359 | dw 0x14292967 360 | dw 0x27b70a85 361 | dw 0x2e1b2138 362 | dw 0x4d2c6dfc 363 | dw 0x53380d13 364 | dw 0x650a7354 365 | dw 0x766a0abb 366 | dw 0x81c2c92e 367 | dw 0x92722c85 368 | dw 0xa2bfe8a1 369 | dw 0xa81a664b 370 | dw 0xc24b8b70 371 | dw 0xc76c51a3 372 | dw 0xd192e819 373 | dw 0xd6990624 374 | dw 0xf40e3585 375 | dw 0x106aa070 376 | dw 0x19a4c116 377 | dw 0x1e376c08 378 | dw 0x2748774c 379 | dw 0x34b0bcb5 380 | dw 0x391c0cb3 381 | dw 0x4ed8aa4a 382 | dw 0x5b9cca4f 383 | dw 0x682e6ff3 384 | dw 0x748f82ee 385 | dw 0x78a5636f 386 | dw 0x84c87814 387 | dw 0x8cc70208 388 | dw 0x90befffa 389 | dw 0xa4506ceb 390 | dw 0xbef9a3f7 391 | dw 0xc67178f2 392 | end 393 | -------------------------------------------------------------------------------- /src/utils/target.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.uint256 import Uint256, uint256_mul 4 | from starkware.cairo.common.alloc import alloc 5 | from starkware.cairo.common.math import unsigned_div_rem, split_felt, assert_lt 6 | from starkware.cairo.common.pow import pow 7 | from starkware.cairo.common.math_cmp import is_le, is_not_zero 8 | from starkware.cairo.common.bool import TRUE, FALSE 9 | 10 | from utils.math import felt_to_Uint256 11 | 12 | # 13 | # The "compact" format is a representation of a whole 14 | # number N using an unsigned 32bit number similar to a 15 | # floating point format. 16 | # The most significant 8 bits are the unsigned exponent of base 256. 17 | # This exponent can be thought of as "number of bytes of N". 18 | # The lower 23 bits are the mantissa. 19 | # Bit number 24 (0x800000) represents the sign of N. 20 | # N = (-1^sign) * mantissa * 256^(exponent-3) 21 | # 22 | # Satoshi's original implementation used BN_bn2mpi() and BN_mpi2bn(). 23 | # MPI uses the most significant bit of the first byte as sign. 24 | # Thus 0x1234560000 is compact (0x05123456) 25 | # and 0xc0de000000 is compact (0x0600c0de) 26 | # 27 | # Bitcoin only uses this "compact" format for encoding difficulty 28 | # targets, which are unsigned 256bit quantities. Thus, all the 29 | # complexities of the sign bit and using base 256 are probably an 30 | # implementation accident. 31 | # 32 | func decode_target{range_check_ptr}(bits : felt) -> (res : Uint256): 33 | let (res : Uint256, negative : felt, overflow : felt) = internal.decode_target(bits) 34 | assert FALSE = negative 35 | assert FALSE = overflow 36 | return (res) 37 | end 38 | 39 | func encode_target{range_check_ptr}(target : Uint256) -> (bits): 40 | return internal.encode_target(target, FALSE) 41 | end 42 | 43 | namespace internal: 44 | func decode_target{range_check_ptr}(bits : felt) -> ( 45 | res : Uint256, negative : felt, overflow : felt 46 | ): 47 | alloc_locals 48 | let (size, remainder) = unsigned_div_rem(bits, 2 ** 24) 49 | let (neg_bit, local nword) = unsigned_div_rem(remainder, 2 ** 23) 50 | 51 | let (size_is_le_3) = is_le(size, 3) 52 | if size_is_le_3 == TRUE: 53 | let (exp) = pow(256, 3 - size) 54 | let (nword, _) = unsigned_div_rem(nword, exp) 55 | let (is_nword_not_zero) = is_not_zero(nword) 56 | let is_neg = is_nword_not_zero * neg_bit 57 | return (res=Uint256(nword, 0), negative=is_neg, overflow=FALSE) 58 | end 59 | 60 | let (is_nword_not_zero) = is_not_zero(nword) 61 | let is_neg = is_nword_not_zero * neg_bit 62 | 63 | # Check overflow 64 | let (size_is_gt_34) = is_le(35, size) 65 | let (size_is_gt_33) = is_le(34, size) 66 | let (size_is_gt_32) = is_le(33, size) 67 | let (nword_is_gt_0xff) = is_le(0xff + 1, nword) 68 | let (nword_is_gt_0xffff) = is_le(0xffff + 1, nword) 69 | 70 | let overflow = size_is_gt_34 71 | let overflow = overflow + size_is_gt_33 * nword_is_gt_0xff 72 | let overflow = overflow + size_is_gt_32 * nword_is_gt_0xffff 73 | let overflow = overflow * is_nword_not_zero 74 | let (overflow) = is_not_zero(overflow) 75 | 76 | let (exp) = pow(256, size - 3) 77 | let (exp_) = felt_to_Uint256(exp) 78 | let (nword_) = felt_to_Uint256(nword) 79 | let (mul_low, mul_high) = uint256_mul(nword_, exp_) 80 | return (mul_low, is_neg, overflow) 81 | end 82 | 83 | func encode_target{range_check_ptr}(target : Uint256, negative : felt) -> (bits): 84 | alloc_locals 85 | local bytes : felt* 86 | local size 87 | 88 | let (size_lo, bytes_lo) = get_bytes_128(target.low) 89 | assert bytes = bytes_lo 90 | 91 | if target.high == 0: 92 | assert size = size_lo 93 | tempvar range_check_ptr = range_check_ptr 94 | else: 95 | pad(16, size_lo, bytes_lo) 96 | let (size_hi) = _get_bytes_128(target.high, bytes_lo + 16, 16) 97 | assert size = size_hi 98 | tempvar range_check_ptr = range_check_ptr 99 | end 100 | tempvar range_check_ptr = range_check_ptr 101 | 102 | let (local compact) = get_truncated_target(3, size, bytes, 0) 103 | 104 | let (is_neg) = is_le(0x00800000, compact) 105 | 106 | # Assert the size doesn't overflow 107 | assert_lt(size + is_neg, 256) 108 | 109 | local bits 110 | if is_neg == 1: 111 | let (adj_compact, _) = unsigned_div_rem(compact, 256) 112 | assert bits = adj_compact + (size + 1) * 2 ** 24 113 | tempvar range_check_ptr = range_check_ptr 114 | else: 115 | assert bits = compact + size * 2 ** 24 116 | tempvar range_check_ptr = range_check_ptr 117 | end 118 | let bits = bits + negative * 2 ** 24 119 | return (bits=bits) 120 | end 121 | 122 | func pad(pad_to : felt, len : felt, bytes : felt*): 123 | if pad_to == 0: 124 | return () 125 | end 126 | 127 | if len == 0: 128 | assert [bytes] = 0 129 | return pad(pad_to - 1, 0, bytes + 1) 130 | end 131 | return pad(pad_to - 1, len - 1, bytes + 1) 132 | end 133 | 134 | func _get_bytes_128{range_check_ptr : felt}(x, bytes : felt*, bytes_len : felt) -> ( 135 | bytes_len : felt 136 | ): 137 | if x == 0: 138 | return (0) 139 | end 140 | let (q, r) = unsigned_div_rem(x, 256) 141 | [bytes] = r 142 | if q == 0: 143 | return (bytes_len + 1) 144 | else: 145 | return _get_bytes_128(q, bytes + 1, bytes_len + 1) 146 | end 147 | end 148 | 149 | func get_bytes_128{range_check_ptr}(x) -> (bytes_len, bytes : felt*): 150 | alloc_locals 151 | let (local bytes : felt*) = alloc() 152 | let (size) = _get_bytes_128(x, bytes, 0) 153 | return (size, bytes) 154 | end 155 | 156 | func get_truncated_target(size, bytes_len, bytes : felt*, acc : felt) -> (res): 157 | if size == 0: 158 | return (acc) 159 | end 160 | if bytes_len == 0: 161 | return get_truncated_target(size - 1, bytes_len, bytes, acc * 256) 162 | end 163 | 164 | return get_truncated_target( 165 | size - 1, bytes_len - 1, bytes, acc * 256 + bytes[bytes_len - 1] 166 | ) 167 | end 168 | end 169 | -------------------------------------------------------------------------------- /src/utils/test_target.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin 4 | from starkware.cairo.common.alloc import alloc 5 | from starkware.cairo.common.math import split_felt 6 | from starkware.cairo.common.uint256 import Uint256, uint256_eq 7 | from starkware.cairo.common.bool import TRUE, FALSE 8 | 9 | from utils.target import internal 10 | from utils.array import arr_eq 11 | from utils.math import felt_to_Uint256 12 | 13 | @view 14 | func test_target_genesis{ 15 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr, bitwise_ptr : BitwiseBuiltin* 16 | }(): 17 | alloc_locals 18 | let bits = 0x1d00ffff 19 | let (local target, negative : felt, overflow : felt) = internal.decode_target(bits) 20 | let (hi, lo) = split_felt(0x00000000ffff0000000000000000000000000000000000000000000000000000) 21 | let (is_eq) = uint256_eq(target, Uint256(lo, hi)) 22 | assert TRUE = is_eq 23 | return () 24 | end 25 | 26 | @view 27 | func test_target{ 28 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr, bitwise_ptr : BitwiseBuiltin* 29 | }(): 30 | alloc_locals 31 | let bits = 0x1729d72d 32 | let (local target, negative : felt, overflow : felt) = internal.decode_target(bits) 33 | let (hi, lo) = split_felt(0x00000000000000000029d72d0000000000000000000000000000000000000000) 34 | let (is_eq) = uint256_eq(target, Uint256(lo, hi)) 35 | assert TRUE = is_eq 36 | return () 37 | end 38 | 39 | @view 40 | func test_pad{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(): 41 | alloc_locals 42 | let (local arr) = alloc() 43 | assert arr[0] = 1 44 | assert arr[1] = 2 45 | assert arr[2] = 3 46 | assert arr[3] = 4 47 | internal.pad(6, 4, arr) 48 | local res : felt* = new (1, 2, 3, 4, 0, 0) 49 | arr_eq(arr, 6, res, 6) 50 | return () 51 | end 52 | 53 | @view 54 | func test_get_bytes_128{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(): 55 | alloc_locals 56 | let value = 0x123456789 57 | let (size, bytes) = internal.get_bytes_128(value) 58 | local res : felt* = new (89, 67, 45, 23, 01) 59 | arr_eq(bytes, size, res, size) 60 | 61 | let value = 0 62 | let (size, bytes) = internal.get_bytes_128(value) 63 | local res : felt* = new (0) 64 | arr_eq(bytes, size, res, size) 65 | let value = 1 66 | let (size, bytes) = internal.get_bytes_128(value) 67 | local res : felt* = new (1) 68 | arr_eq(bytes, size, res, size) 69 | 70 | let (local size, local bytes) = internal.get_bytes_128(0x0004444000077770000) 71 | local res : felt* = new (44, 44, 00, 00, 77, 77, 00, 00) 72 | arr_eq(bytes, size, res, size) 73 | 74 | let (local size, local bytes) = internal.get_bytes_128(2 ** 128 - 1) 75 | local res : felt* = new (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255) 76 | arr_eq(bytes, size, res, size) 77 | 78 | let (size) = internal._get_bytes_128(0x0004444000077770000, bytes + size, size) 79 | local res : felt* = new (255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 44, 44, 00, 00, 77, 77, 00, 00) 80 | arr_eq(bytes, size, res, size) 81 | return () 82 | end 83 | 84 | struct Target_test_vector: 85 | member target : Uint256 86 | member bits : felt 87 | end 88 | 89 | func rec_test_targets{range_check_ptr}(len, test_data_ptr : Target_test_vector*): 90 | if len == 0: 91 | return () 92 | end 93 | 94 | let (bits_computed) = internal.encode_target(test_data_ptr.target, FALSE) 95 | assert test_data_ptr.bits = bits_computed 96 | return rec_test_targets(len - 1, test_data_ptr + Target_test_vector.SIZE) 97 | end 98 | 99 | @view 100 | func test_encode_target_single{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(): 101 | let (target) = felt_to_Uint256(0x12) 102 | let bits = 0x01120000 103 | let (bits_computed) = internal.encode_target(target, FALSE) 104 | assert bits = bits_computed 105 | return () 106 | end 107 | 108 | @view 109 | func test_encode_target{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}(): 110 | alloc_locals 111 | let (local tests : Target_test_vector*) = alloc() 112 | 113 | let (target) = felt_to_Uint256( 114 | 0x00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 115 | ) 116 | assert tests[0] = Target_test_vector(target, 0x1d00ffff) 117 | let (target) = felt_to_Uint256( 118 | 0x00000000FFFF0000000000000000000000000000000000000000000000000000 119 | ) 120 | assert tests[1] = Target_test_vector(target, 0x1d00ffff) 121 | let (target) = felt_to_Uint256( 122 | 0x00000000d86a0000000000000000000000000000000000000000000000000000 123 | ) 124 | assert tests[2] = Target_test_vector(target, 0x1d00d86a) 125 | let (target) = felt_to_Uint256( 126 | 0x00000000be710000000000000000000000000000000000000000000000000000 127 | ) 128 | assert tests[3] = Target_test_vector(target, 0x1d00be71) 129 | let (target) = felt_to_Uint256( 130 | 0x0000000065465700000000000000000000000000000000000000000000000000 131 | ) 132 | assert tests[4] = Target_test_vector(target, 0x1c654657) 133 | let (target) = felt_to_Uint256( 134 | 0x00000000000e7256000000000000000000000000000000000000000000000000 135 | ) 136 | assert tests[5] = Target_test_vector(target, 0x1b0e7256) 137 | let (target) = felt_to_Uint256( 138 | 0x0000000000000abbcf0000000000000000000000000000000000000000000000 139 | ) 140 | assert tests[6] = Target_test_vector(target, 0x1a0abbcf) 141 | let (target) = felt_to_Uint256( 142 | 0x00000000000004fa620000000000000000000000000000000000000000000000 143 | ) 144 | assert tests[7] = Target_test_vector(target, 0x1a04fa62) 145 | let (target) = felt_to_Uint256( 146 | 0x000000000000000000ff18000000000000000000000000000000000000000000 147 | ) 148 | assert tests[8] = Target_test_vector(target, 0x1800ff18) 149 | let (target) = felt_to_Uint256(0xc0de000000) 150 | assert tests[9] = Target_test_vector(target, 0x0600c0de) 151 | let (target) = felt_to_Uint256(0x1234560000) 152 | assert tests[10] = Target_test_vector(target, 0x05123456) 153 | 154 | rec_test_targets(11, tests) 155 | 156 | return () 157 | end 158 | 159 | # Test cases taken from https://github.com/bitcoin/bitcoin/blob/master/src/test/arith_uint256_tests.cpp#L406 160 | # Test cases about negative bits have been removed. 161 | @view 162 | func test_encode_decode_target{ 163 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr, bitwise_ptr : BitwiseBuiltin* 164 | }(): 165 | alloc_locals 166 | 167 | test_internal.test_encode_decode_target(0x01123456, 0x00000012, FALSE, FALSE, 0x01120000) 168 | test_internal.test_encode_decode_target(0x02123456, 0x00001234, FALSE, FALSE, 0x02123400) 169 | test_internal.test_encode_decode_target(0x03123456, 0x00123456, FALSE, FALSE, 0x03123456) 170 | test_internal.test_encode_decode_target(0x04123456, 0x12345600, FALSE, FALSE, 0x04123456) 171 | test_internal.test_encode_decode_target(0x05009234, 0x92340000, FALSE, FALSE, 0x05009234) 172 | 173 | test_internal.test_encode_decode_target_Uint256( 174 | 0x20123456, 175 | Uint256(0x00000000000000000000000000000000, 0x12345600000000000000000000000000), 176 | FALSE, 177 | FALSE, 178 | 0x20123456, 179 | ) 180 | 181 | test_internal.test_encode_decode_target(0xff123456, 0, FALSE, TRUE, 0) 182 | 183 | test_internal.test_encode_decode_target(0, 0, FALSE, FALSE, 0) 184 | test_internal.test_encode_decode_target(0x00123456, 0, FALSE, FALSE, 0) 185 | test_internal.test_encode_decode_target(0x01003456, 0, FALSE, FALSE, 0) 186 | test_internal.test_encode_decode_target(0x02000056, 0, FALSE, FALSE, 0) 187 | test_internal.test_encode_decode_target(0x03000000, 0, FALSE, FALSE, 0) 188 | test_internal.test_encode_decode_target(0x04000000, 0, FALSE, FALSE, 0) 189 | test_internal.test_encode_decode_target(0x00923456, 0, FALSE, FALSE, 0) 190 | test_internal.test_encode_decode_target(0x01803456, 0, FALSE, FALSE, 0) 191 | test_internal.test_encode_decode_target(0x02800056, 0, FALSE, FALSE, 0) 192 | test_internal.test_encode_decode_target(0x03800000, 0, FALSE, FALSE, 0) 193 | test_internal.test_encode_decode_target(0x04800000, 0, FALSE, FALSE, 0) 194 | test_internal.test_encode_decode_target(0x01003456, 0, FALSE, FALSE, 0) 195 | 196 | # Make sure that we don't generate compacts with the 0x00800000 bit set 197 | let (local target0x80 : Uint256) = felt_to_Uint256(0x80) 198 | let (local bits) = internal.encode_target(target0x80, FALSE) 199 | with_attr error_message("For target 0x80, expected bits to be 0x02008000U, got {bits}"): 200 | assert 0x02008000 = bits 201 | end 202 | 203 | return () 204 | end 205 | 206 | namespace test_internal: 207 | func test_encode_decode_target{ 208 | syscall_ptr : felt*, 209 | pedersen_ptr : HashBuiltin*, 210 | range_check_ptr, 211 | bitwise_ptr : BitwiseBuiltin*, 212 | }( 213 | bits : felt, 214 | expected_decoded_target : felt, 215 | expected_negative : felt, 216 | expected_overflow : felt, 217 | expected_reencoded_bits : felt, 218 | ): 219 | alloc_locals 220 | let (local uint256_expected_decoded_target : Uint256) = felt_to_Uint256( 221 | expected_decoded_target 222 | ) 223 | test_encode_decode_target_Uint256( 224 | bits, 225 | uint256_expected_decoded_target, 226 | expected_negative, 227 | expected_overflow, 228 | expected_reencoded_bits, 229 | ) 230 | return () 231 | end 232 | 233 | func test_encode_decode_target_Uint256{ 234 | syscall_ptr : felt*, 235 | pedersen_ptr : HashBuiltin*, 236 | range_check_ptr, 237 | bitwise_ptr : BitwiseBuiltin*, 238 | }( 239 | bits : felt, 240 | expected_decoded_target : Uint256, 241 | expected_negative : felt, 242 | expected_overflow : felt, 243 | expected_reencoded_bits : felt, 244 | ): 245 | alloc_locals 246 | let ( 247 | local decoded_target : Uint256, negative : felt, overflow : felt 248 | ) = internal.decode_target(bits) 249 | 250 | with_attr error_message( 251 | "For target {bits}, expected overflow to be {expected_overflow}, got {overflow}"): 252 | assert expected_overflow = overflow 253 | end 254 | if overflow == TRUE: 255 | return () 256 | end 257 | 258 | with_attr error_message( 259 | "For target {bits}, expected negative to be {expected_negative}, got {negative}"): 260 | assert expected_negative = negative 261 | end 262 | 263 | let (decoded_are_equal) = uint256_eq(decoded_target, expected_decoded_target) 264 | 265 | with_attr error_message( 266 | "For target {bits}, expected decoded target to be {expected_decoded_target}, got {decoded_target}"): 267 | assert decoded_are_equal = TRUE 268 | end 269 | 270 | let (local reencoded_bits : felt) = internal.encode_target(decoded_target, negative) 271 | 272 | with_attr error_message( 273 | "For target {bits}, expected reencoded bits to be {expected_reencoded_bits}, got {reencoded_bits}"): 274 | assert expected_reencoded_bits = reencoded_bits 275 | end 276 | 277 | return () 278 | end 279 | end 280 | --------------------------------------------------------------------------------