├── .gitignore ├── __pycache__ └── lightclient.cpython-39.pyc ├── starknet_compile.sh ├── starknet_deploy.sh ├── starknet_invoke.sh ├── utils └── array_comparison.cairo ├── block0.json ├── Makefile ├── block1.json ├── README.md ├── starknet_contract.cairo ├── sha256 ├── sha256_contract.cairo ├── packed_sha256.cairo └── sha256.cairo ├── lightclient.py ├── starknet_run.py ├── utils.cairo └── btc_header.cairo /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.sw* 3 | build/** 4 | -------------------------------------------------------------------------------- /__pycache__/lightclient.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samlaf/starknet-btc-lightclient/HEAD/__pycache__/lightclient.cpython-39.pyc -------------------------------------------------------------------------------- /starknet_compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | starknet-compile starknet_contract.cairo \ 4 | --disable_hint_validation \ 5 | --output build/starknet_contract_compiled.json \ 6 | --abi build/starknet_contract_abi.json 7 | -------------------------------------------------------------------------------- /starknet_deploy.sh: -------------------------------------------------------------------------------- 1 | export STARKNET_GATEWAY_URL=http://localhost:5000/ 2 | export STARKNET_FEEDER_GATEWAY_URL=http://localhost:5000/ 3 | #export STARKNET_CHAIN_ID=SN_GOERLI 4 | #export STARKNET_NETWORK_ID=hackathon-0 5 | # unset STARKNET_NETWORK 6 | 7 | starknet deploy --contract build/starknet_contract_compiled.json 8 | -------------------------------------------------------------------------------- /starknet_invoke.sh: -------------------------------------------------------------------------------- 1 | starknet invoke --address $CONTRACT_ADDRESS --abi build/starknet_contract_abi.json \ 2 | --function process_block --inputs 0 20 0x01000000 0x00000000 0x00000000 0x00000000 0x00000000 0x0 0x0 0x0 0x0 0x3ba3edfd 0x7a7b12b2 0x7ac72c3e 0x67768f61 0x7fc81bc3 0x888a5132 0x3a9fb8aa 0x4b1e5e4a 0x29ab5f49 0xffff001d 0x1dac2b7c -------------------------------------------------------------------------------- /utils/array_comparison.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) -> (res: felt): 12 | if current_index == -1: 13 | return (1) 14 | end 15 | 16 | if a[current_index] != b[current_index]: 17 | return (0) 18 | end 19 | return _arr_eq(a=a, a_len=a_len, b=b, b_len=b_len, current_index=current_index-1) 20 | end 21 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @echo "Compiling.." 3 | @cairo-compile btc_header.cairo --output build/btc_header.json 4 | @echo "Running.." 5 | @cairo-run --program build/btc_header.json --print_output --layout=all --print_info 6 | run: 7 | @echo "Running.." 8 | @cairo-run --program btc_header.json --print_output --layout=all --print_info 9 | 10 | sha: 11 | @echo "Compiling.." 12 | @cairo-compile sha256/sha256_contract.cairo --output build/sha256_cairo_contract_compiled.json 13 | @echo "Running.." 14 | @cairo-run --program build/sha256_cairo_contract_compiled.json --print_output --layout=all --print_info 15 | 16 | compile_deploy: 17 | @echo "Compiling.." 18 | @./starknet_compile.sh 19 | @starknet-devnet & 20 | @echo "Deploying..." 21 | @./starknet_deploy.sh 22 | 23 | demo: 24 | @python3 starknet_run.py 25 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | 1. pip install starknet-devnet 4 | 2. mkdir build 5 | 3. Run `make compile_deploy` in a terminal. 6 | 4. Export the printed contract address: `export CONTRACT_ADDR= 7 | 5. Run `make demo` in a separate terminal. 8 | 9 | # Querying for blocks: 10 | 11 | 1. get an api-key by registering to getblock.io. 12 | 2. get the block hash of the block you want to query (here: 0) 13 | 14 | ``` 15 | curl --location --request POST 'https://btc.getblock.io/mainnet/' \ (py39) 16 | --header 'x-api-key: ' \ 17 | --header 'Content-Type: application/json' \ 18 | --data-raw '{"jsonrpc": "2.0", 19 | "method": "getblockhash", 20 | "params": [0], 21 | "id": "getblock.io"}' 22 | ``` 23 | 24 | 3. get the block by hash 25 | 26 | ``` 27 | curl --location --request POST 'https://btc.getblock.io/mainnet/' \ (py39) 28 | --header 'x-api-key: ' \ 29 | --header 'Content-Type: application/json' \ 30 | --data-raw '{"jsonrpc": "2.0", 31 | "method": "getblock", 32 | "params": ["000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"], 33 | "id" : "getblock.io"}' 34 | ``` 35 | 36 | # References 37 | 38 | See https://en.bitcoin.it/wiki/Block_hashing_algorithm for block hashing algorithm 39 | -------------------------------------------------------------------------------- /starknet_contract.cairo: -------------------------------------------------------------------------------- 1 | %lang starknet 2 | 3 | from starkware.cairo.common.alloc import alloc 4 | from starkware.cairo.common.cairo_builtins import HashBuiltin 5 | from starkware.cairo.common.cairo_builtins import BitwiseBuiltin 6 | 7 | from btc_header import BTCHeader, process_header, prepare_header 8 | 9 | @storage_var 10 | func block_header_lo(number : felt) -> (hash_lo : felt): 11 | end 12 | 13 | @storage_var 14 | func block_header_hi(number : felt) -> (hash_hi : felt): 15 | end 16 | 17 | @external 18 | func process_block{ 19 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, bitwise_ptr : BitwiseBuiltin*, range_check_ptr 20 | }(height : felt, data_len : felt, data : felt*): 21 | alloc_locals 22 | 23 | # Retrieve previous block header hash (or zero hash if genesis) 24 | let (local prev_hash : felt*) = alloc() 25 | if height == 0: 26 | assert prev_hash[0] = 0 27 | assert prev_hash[1] = 0 28 | tempvar syscall_ptr = syscall_ptr 29 | tempvar range_check_ptr = range_check_ptr 30 | tempvar pedersen_ptr = pedersen_ptr 31 | else: 32 | let (lo) = block_header_lo.read(height - 1) 33 | let (hi) = block_header_hi.read(height - 1) 34 | assert prev_hash[0] = lo 35 | assert prev_hash[1] = hi 36 | tempvar syscall_ptr = syscall_ptr 37 | tempvar range_check_ptr = range_check_ptr 38 | tempvar pedersen_ptr = pedersen_ptr 39 | end 40 | tempvar syscall_ptr = syscall_ptr 41 | tempvar range_check_ptr = range_check_ptr 42 | tempvar pedersen_ptr = pedersen_ptr 43 | # Verify provided block header 44 | let (local header) = prepare_header(data) 45 | let (local block_hash) = process_header(header, prev_hash) 46 | 47 | local syscall_ptr : felt* = syscall_ptr 48 | # Write current header to storage 49 | block_header_lo.write(height, block_hash[0]) 50 | block_header_hi.write(height, block_hash[1]) 51 | 52 | return () 53 | end 54 | -------------------------------------------------------------------------------- /sha256/sha256_contract.cairo: -------------------------------------------------------------------------------- 1 | #%builtins range_check bitwise 2 | 3 | from sha256.sha256 import finalize_sha256, sha256 4 | from starkware.cairo.common.alloc import alloc 5 | from starkware.cairo.common.cairo_builtins import BitwiseBuiltin 6 | 7 | # Computes the SHA256 hash of the given input (up to 55 bytes). 8 | # input should consist of a list of 32-bit integers (each representing 4 bytes, in big endian). 9 | # n_bytes should be the number of input bytes (for example, it should be between 4*input_len - 3 and 10 | # 4*input_len). 11 | # Returns the 256 output bits as 2 128-bit big-endian integers. 12 | func compute_sha256{range_check_ptr, bitwise_ptr : BitwiseBuiltin*}( 13 | input : felt*, n_bytes : felt 14 | ) -> (res0 : felt, res1 : felt): 15 | alloc_locals 16 | 17 | let (local sha256_ptr_start : felt*) = alloc() 18 | let sha256_ptr = sha256_ptr_start 19 | 20 | let (local output : felt*) = sha256{sha256_ptr=sha256_ptr}(input, n_bytes) 21 | # TODO: make finalize work 22 | #finalize_sha256(sha256_ptr_start=sha256_ptr_start, sha256_ptr_end=sha256_ptr) 23 | 24 | return ( 25 | output[3] + 2 ** 32 * output[2] + 2 ** 64 * output[1] + 2 ** 96 * output[0], 26 | output[7] + 2 ** 32 * output[6] + 2 ** 64 * output[5] + 2 ** 96 * output[4], 27 | ) 28 | end 29 | 30 | struct IntArray32: 31 | member elements : felt* 32 | member byte_len : felt # total number of bytes 33 | end 34 | 35 | func main{range_check_ptr, bitwise_ptr : BitwiseBuiltin*}(): 36 | alloc_locals 37 | 38 | local input : IntArray32 39 | 40 | %{ 41 | from math import ceil 42 | from starkware.cairo.common.cairo_secp.secp_utils import split 43 | 44 | def pack_intarray32(base_addr, hex_input): 45 | elements = segments.add() 46 | for j in range(0, len(hex_input) // 8 + 1): 47 | hex_str = hex_input[j*8 : (j+1) * 8] 48 | if len(hex_str) > 0: 49 | memory[elements + j] = int(hex_str, 16) 50 | memory[base_addr + ids.IntArray32.elements] = elements 51 | memory[base_addr + ids.IntArray32.byte_len] = ceil(len(hex_input) / 2) 52 | 53 | # 16 byte preimage 54 | pack_intarray32( 55 | ids.input.address_, 56 | "f" * 55 * 2) 57 | %} 58 | 59 | let (out1, out2) = compute_sha256(input.elements, input.byte_len) 60 | %{ 61 | print(hex(ids.out1)) 62 | print(hex(ids.out2)) 63 | %} 64 | 65 | return () 66 | end 67 | -------------------------------------------------------------------------------- /lightclient.py: -------------------------------------------------------------------------------- 1 | import json 2 | from hashlib import sha256 3 | from binascii import unhexlify 4 | 5 | with open("block0.json") as block0_file: 6 | block0 = json.load(block0_file) 7 | with open("block1.json") as block1_file: 8 | block1 = json.load(block1_file) 9 | 10 | # from bitcoinlib.blocks import Block 11 | # b = Block(block1['hash'], block1['version'], block1['previousblockhash'], 12 | # block1['merkleroot'], block1['time'], block1['bits'], block1['nonce']) 13 | # print(b.target_hex) 14 | 15 | # block header 16 | # [version, previousblockhash, merkleroot, time, bits, nonce] 17 | # [4, 32, 32, 4, 4, 4 ] 18 | # [int, string, string, int, string, int] 19 | # all little endian 20 | 21 | ENDIANNESS = 'little' 22 | 23 | 24 | def to_bytes(string, unhexlify=True): 25 | if not string: 26 | return b'' 27 | if unhexlify: 28 | try: 29 | if isinstance(string, bytes): 30 | string = string.decode() 31 | s = bytes.fromhex(string) 32 | return s 33 | except (TypeError, ValueError): 34 | pass 35 | if isinstance(string, bytes): 36 | return string 37 | else: 38 | return bytes(string, 'utf8') 39 | 40 | 41 | def double_sha256(string, as_hex=False): 42 | if not as_hex: 43 | return sha256(sha256(string).digest()).digest() 44 | else: 45 | return sha256(sha256(string).digest()).hexdigest() 46 | 47 | 48 | def big_to_little_endian(s): 49 | return bytes.fromhex(s)[::-1].hex() 50 | 51 | 52 | def verifyBlock(block): 53 | versionHex = big_to_little_endian(block['versionHex']) 54 | previousBlockHashHex = big_to_little_endian( 55 | block['previousblockhash']) if 'previousblockhash' in block else (0).to_bytes(32, ENDIANNESS).hex() 56 | merkleRootHex = big_to_little_endian(block['merkleroot']) 57 | timeHex = block['time'].to_bytes(4, ENDIANNESS).hex() 58 | bitsHex = big_to_little_endian(block['bits']) 59 | nonceB = block['nonce'].to_bytes(4, ENDIANNESS).hex() 60 | 61 | header_hex = versionHex + previousBlockHashHex + \ 62 | merkleRootHex + timeHex + bitsHex + nonceB 63 | print(len(header_hex), header_hex) 64 | header_bin = unhexlify(header_hex) 65 | hash = sha256(sha256(header_bin).digest()).digest() 66 | print(hash[::-1].hex()) 67 | 68 | 69 | def header_to_cairo(block): 70 | versionHex = big_to_little_endian(block['versionHex']) 71 | previousBlockHashHex = big_to_little_endian( 72 | block['previousblockhash']) if 'previousblockhash' in block else (0).to_bytes(32, ENDIANNESS).hex() 73 | merkleRootHex = big_to_little_endian(block['merkleroot']) 74 | timeHex = block['time'].to_bytes(4, ENDIANNESS).hex() 75 | bitsHex = big_to_little_endian(block['bits']) 76 | nonceB = block['nonce'].to_bytes(4, ENDIANNESS).hex() 77 | 78 | header_hex = versionHex + previousBlockHashHex + \ 79 | merkleRootHex + timeHex + bitsHex + nonceB 80 | header_bin = unhexlify(header_hex) 81 | 82 | data = header_bin.hex() 83 | # print(data) 84 | 85 | # return [data[8*i:8*(i+1)] for i in range(160//8)] 86 | tmp = [int(data[8*i:8*(i+1)], 16) for i in range(160//8)] 87 | # for i,d in enumerate(tmp): 88 | # print(f'data[{i}] = {d}') 89 | return tmp 90 | 91 | 92 | if __name__ == "__main__": 93 | print(header_to_cairo(block0)) 94 | print(header_to_cairo(block1)) 95 | 96 | verifyBlock(block0) 97 | print(block0['hash']) 98 | verifyBlock(block1) 99 | print(block1['hash']) 100 | -------------------------------------------------------------------------------- /starknet_run.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import json 4 | import subprocess 5 | import requests 6 | from dotenv import load_dotenv 7 | 8 | from lightclient import header_to_cairo 9 | 10 | def subprocess_run(cmd): 11 | result = subprocess.run(cmd, stdout=subprocess.PIPE) 12 | result = result.stdout.decode('utf-8')[:-1] 13 | return result 14 | 15 | def verify_block(contract_addr, height, data): 16 | data = " ".join([str(i) for i in data]) 17 | cmd = f"starknet invoke --network_id=hackathon-0 --gateway_url=http://localhost:5000 --feeder_gateway_url=http://localhost:5000 --address {contract_addr} --abi build/starknet_contract_abi.json --function process_block --inputs {height} 20 " + data 18 | cmd = cmd.split(' ') 19 | ret = subprocess_run(cmd) 20 | ret = ret.split(': ') 21 | tx_hash = ret[-1] 22 | return tx_hash 23 | 24 | def poll_until_accepted(list_of_tx_hashes, interval_in_sec): 25 | accepted_list = [False for _ in list_of_tx_hashes] 26 | 27 | while True: 28 | all_accepted = True 29 | print(f'> begin polling tx status.') 30 | for i, tx_hash in enumerate(list_of_tx_hashes): 31 | if accepted_list[i]: 32 | continue 33 | cmd = f"starknet tx_status --network_id=hackathon-0 --gateway_url=http://localhost:5000 --feeder_gateway_url=http://localhost:5000 --hash={tx_hash}".split(' ') 34 | ret = subprocess_run(cmd) 35 | ret = json.loads(ret) 36 | if ret['tx_status'] != 'ACCEPTED_ON_L2': 37 | print(f"> {tx_hash} ({ret['tx_status']}) not accepted onchain yet.") 38 | all_accepted = False 39 | break 40 | else: 41 | print(f"> {i}th hash {tx_hash} is accepted onchain.") 42 | accepted_list[i] = True 43 | if all_accepted: 44 | break 45 | else: 46 | print(f'> retry polling in {interval_in_sec} seconds.') 47 | time.sleep(interval_in_sec) 48 | print('> all tx hashes are accepted onchain.') 49 | return 50 | 51 | def get_block_by_height(height): 52 | # Retrieve block hash from height 53 | block_hash = requests.post( 54 | 'https://btc.getblock.io/mainnet/', 55 | headers={ 56 | 'Content-Type': 'application/json', 57 | 'x-api-key': API_KEY 58 | }, 59 | json={ 60 | "jsonrpc": "2.0", 61 | "method": "getblockhash", 62 | "params": [height], 63 | "id": "getblock.io" 64 | }).json()['result'] 65 | 66 | # Retrieve block header from hash 67 | block = requests.post( 68 | 'https://btc.getblock.io/mainnet/', 69 | headers={ 70 | 'Content-Type': 'application/json', 71 | 'x-api-key': API_KEY 72 | }, 73 | json={ 74 | "jsonrpc": "2.0", 75 | "method": "getblock", 76 | "params": [block_hash], 77 | "id": "getblock.io" 78 | }).json()['result'] 79 | return block 80 | 81 | if __name__ == "__main__": 82 | load_dotenv() 83 | API_KEY = os.environ["GETBLOCK_API_KEY"] 84 | CONTRACT_ADDR = os.environ["CONTRACT_ADDR"] 85 | 86 | block_height = 0 87 | while block_height < 733581: 88 | print(f"Importing block from height {block_height}") 89 | block = get_block_by_height(block_height) 90 | data = header_to_cairo(block) 91 | print(f"Verifying block from height {block_height}") 92 | txhash = verify_block(CONTRACT_ADDR, block_height, data) 93 | print("Waiting for transaction to be mined") 94 | poll_until_accepted([txhash], 1) 95 | block_height += 1 96 | -------------------------------------------------------------------------------- /utils.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 | 9 | struct IntsSequence: 10 | member element : felt* 11 | member element_size_words : felt 12 | member element_size_bytes : felt 13 | end 14 | 15 | func pow{range_check_ptr}(base : felt, p : felt) -> (res : felt): 16 | if p == 0: 17 | return (1) 18 | end 19 | 20 | let (accumulator) = pow(base=base, p=p - 1) 21 | return (base * accumulator) 22 | end 23 | 24 | func get_target{range_check_ptr}(bits : felt) -> (res : Uint256): 25 | alloc_locals 26 | let (exponent, local mantissa) = unsigned_div_rem(bits, 2 ** 24) 27 | # %{ 28 | # print("bits", hex(ids.bits)) 29 | # print("exponent", ids.exponent) 30 | # print("mantissa", ids.mantissa) 31 | # print(hex(ids.mantissa*256**(ids.exponent - 3))) 32 | # %} 33 | let (exp) = pow(256, exponent - 3) 34 | let tmp = mantissa * exp 35 | let res_target = split_felt(tmp) 36 | return (Uint256(res_target.low, res_target.high)) 37 | end 38 | 39 | # Split a Uint256 into 8 32bit words ugly 40 | func prepare_hash{range_check_ptr}(inp : Uint256) -> (res : felt*): 41 | alloc_locals 42 | let (res : felt*) = alloc() 43 | let (lolo, lohi) = unsigned_div_rem(inp.low, 2 ** 64) 44 | let (lololo, lolohi) = unsigned_div_rem(lolo, 2 ** 32) 45 | let (lohilo, lohihi) = unsigned_div_rem(lohi, 2 ** 32) 46 | assert res[0] = lololo 47 | assert res[1] = lolohi 48 | assert res[2] = lohilo 49 | assert res[3] = lohihi 50 | 51 | let (hilo, hihi) = unsigned_div_rem(inp.high, 2 ** 64) 52 | let (hilolo, hilohi) = unsigned_div_rem(hilo, 2 ** 32) 53 | let (hihilo, hihihi) = unsigned_div_rem(hihi, 2 ** 32) 54 | assert res[4] = hilolo 55 | assert res[5] = hilohi 56 | assert res[6] = hihilo 57 | assert res[7] = hihihi 58 | return (res) 59 | end 60 | 61 | # func swap_endianness_many_words{ range_check_ptr, bitwise_ptr : BitwiseBuiltin* }(input: IntsSequence) -> (output: IntsSequence): 62 | # alloc_locals 63 | # let (local acc : felt*) = alloc() 64 | # swap_endianness_many_words_rec(input, acc, 0) 65 | # local res: IntsSequence = IntsSequence(acc, input.element_size_words, input.element_size_bytes) 66 | # return (res) 67 | # end 68 | 69 | func swap_endianness_four_words{range_check_ptr, bitwise_ptr : BitwiseBuiltin*}( 70 | input : IntsSequence 71 | ) -> (output : IntsSequence): 72 | alloc_locals 73 | let (local acc : felt*) = alloc() 74 | 75 | let (local swapped_input_0) = swap_endianness_64(input.element[0], 8) 76 | assert acc[0] = swapped_input_0 77 | 78 | let (local swapped_input_1) = swap_endianness_64(input.element[1], 8) 79 | assert acc[1] = swapped_input_1 80 | 81 | let (local swapped_input_2) = swap_endianness_64(input.element[2], 8) 82 | assert acc[2] = swapped_input_2 83 | 84 | let (local swapped_input_3) = swap_endianness_64(input.element[3], 8) 85 | assert acc[3] = swapped_input_3 86 | 87 | local res : IntsSequence = IntsSequence(acc, input.element_size_words, input.element_size_bytes) 88 | return (res) 89 | end 90 | 91 | # func swap_endianness_many_words_rec{ range_check_ptr, bitwise_ptr : BitwiseBuiltin* }( 92 | # input: IntsSequence, 93 | # acc: felt*, 94 | # current_word: felt): 95 | # alloc_locals 96 | 97 | # if current_word == input.element_size_words: 98 | # return () 99 | # end 100 | 101 | # # Is 0 when it is last word 102 | # let is_last_word = input.element_size_words - current_word - 1 103 | # local proper_len 104 | 105 | # if is_last_word == 0: 106 | # let (_, last_word_len) = unsigned_div_rem(input.element_size_bytes, 8) 107 | 108 | # if last_word_len == 0: 109 | # proper_len = 8 110 | # else: 111 | # proper_len = last_word_len 112 | # end 113 | # else: 114 | # proper_len = 8 115 | # end 116 | 117 | # let swapped: felt = swap_endianness_64(input.element[current_word], proper_len) 118 | # assert acc[current_word] = swapped 119 | # return swap_endianness_many_words_rec(input, acc, current_word + 1) 120 | # end 121 | 122 | func swap_endianness_64{range_check_ptr, bitwise_ptr : BitwiseBuiltin*}( 123 | input : felt, size : felt 124 | ) -> (output : felt): 125 | alloc_locals 126 | let (local output : felt*) = alloc() 127 | 128 | # verifies word fits in 64bits 129 | assert_le(input, 2 ** 64 - 1) 130 | 131 | # swapped_bytes = ((word & 0xFF00FF00FF00FF00) >> 8) | ((word & 0x00FF00FF00FF00FF) << 8) 132 | let (left_part, _) = unsigned_div_rem(input, 256) 133 | 134 | assert bitwise_ptr[0].x = left_part 135 | assert bitwise_ptr[0].y = 0x00FF00FF00FF00FF 136 | 137 | assert bitwise_ptr[1].x = input * 256 138 | assert bitwise_ptr[1].y = 0xFF00FF00FF00FF00 139 | 140 | let swapped_bytes = bitwise_ptr[0].x_and_y + bitwise_ptr[1].x_and_y 141 | 142 | # swapped_2byte_pair = ((swapped_bytes & 0xFFFF0000FFFF0000) >> 16) | ((swapped_bytes & 0x0000FFFF0000FFFF) << 16) 143 | let (left_part2, _) = unsigned_div_rem(swapped_bytes, 2 ** 16) 144 | 145 | assert bitwise_ptr[2].x = left_part2 146 | assert bitwise_ptr[2].y = 0x0000FFFF0000FFFF 147 | 148 | assert bitwise_ptr[3].x = swapped_bytes * 2 ** 16 149 | assert bitwise_ptr[3].y = 0xFFFF0000FFFF0000 150 | 151 | let swapped_2bytes = bitwise_ptr[2].x_and_y + bitwise_ptr[3].x_and_y 152 | 153 | # swapped_4byte_pair = (swapped_2byte_pair >> 32) | ((swapped_2byte_pair << 32) % 2**64) 154 | let (left_part4, _) = unsigned_div_rem(swapped_2bytes, 2 ** 32) 155 | 156 | assert bitwise_ptr[4].x = swapped_2bytes * 2 ** 32 157 | assert bitwise_ptr[4].y = 0xFFFFFFFF00000000 158 | 159 | let swapped_4bytes = left_part4 + bitwise_ptr[4].x_and_y 160 | 161 | let bitwise_ptr = bitwise_ptr + 5 * BitwiseBuiltin.SIZE 162 | 163 | # Some Shiva-inspired code here 164 | let (local shift) = pow(2, ((8 - size) * 8)) 165 | 166 | if size == 8: 167 | return (swapped_4bytes) 168 | else: 169 | let (shifted_4bytes, _) = unsigned_div_rem(swapped_4bytes, shift) 170 | return (shifted_4bytes) 171 | end 172 | end 173 | -------------------------------------------------------------------------------- /btc_header.cairo: -------------------------------------------------------------------------------- 1 | # %builtins range_check bitwise 2 | from starkware.cairo.common.alloc import alloc 3 | from starkware.cairo.common.uint256 import Uint256, uint256_lt 4 | from utils import swap_endianness_64, get_target, prepare_hash 5 | from sha256.sha256_contract import compute_sha256 6 | from utils.array_comparison import arr_eq 7 | from starkware.cairo.common.cairo_builtins import BitwiseBuiltin 8 | 9 | struct BTCHeader: 10 | member version : felt 11 | member previous : felt* 12 | member merkle_root : felt* 13 | member time : felt 14 | member bits : felt 15 | member nonce : felt 16 | member data : felt* 17 | end 18 | 19 | # version : 4 bytes 20 | # previous hash : 32 bytes 21 | # merkle root : 32 bytes 22 | # time : 4 bytes 23 | # bits : 4 bytes 24 | # nonce : 4 bytes 25 | 26 | # Assuming data is the header packed as an array of 4 bytes 27 | func prepare_header{range_check_ptr, bitwise_ptr : BitwiseBuiltin*}(data : felt*) -> ( 28 | res : BTCHeader 29 | ): 30 | alloc_locals 31 | let (previous : felt*) = alloc() 32 | let (merkle_root : felt*) = alloc() 33 | let (version) = swap_endianness_64(data[0], 4) 34 | 35 | let (prev0) = swap_endianness_64(data[7] * 2 ** 32 + data[8], 8) 36 | let (prev1) = swap_endianness_64(data[5] * 2 ** 32 + data[6], 8) 37 | let (prev2) = swap_endianness_64(data[3] * 2 ** 32 + data[4], 8) 38 | let (prev3) = swap_endianness_64(data[1] * 2 ** 32 + data[2], 8) 39 | assert previous[0] = prev0 40 | assert previous[1] = prev1 41 | assert previous[2] = prev2 42 | assert previous[3] = prev3 43 | 44 | let (merkle0) = swap_endianness_64(data[15] * 2 ** 32 + data[16], 8) 45 | let (merkle1) = swap_endianness_64(data[13] * 2 ** 32 + data[14], 8) 46 | let (merkle2) = swap_endianness_64(data[11] * 2 ** 32 + data[12], 8) 47 | let (merkle3) = swap_endianness_64(data[09] * 2 ** 32 + data[10], 8) 48 | 49 | assert merkle_root[0] = merkle0 50 | assert merkle_root[1] = merkle1 51 | assert merkle_root[2] = merkle2 52 | assert merkle_root[3] = merkle3 53 | let (time) = swap_endianness_64(data[17], 4) 54 | let (bits) = swap_endianness_64(data[18], 4) 55 | let (nonce) = swap_endianness_64(data[19], 4) 56 | 57 | local header : BTCHeader = BTCHeader(version, previous, merkle_root, time, bits, nonce, data) 58 | return (header) 59 | end 60 | 61 | func process_header{range_check_ptr, bitwise_ptr : BitwiseBuiltin*}( 62 | header : BTCHeader, prev_header_hash : felt* 63 | ) -> (current_header_hash : felt*): 64 | alloc_locals 65 | 66 | # WIP: Compute SHA256 of serialized header (big endian) 67 | let header_bytes = header.data 68 | let (tmp1, tmp2) = compute_sha256(header_bytes, 80) 69 | let (spliced_tmp) = prepare_hash(Uint256(tmp1, tmp2)) 70 | let (tmpout1, tmpout2) = compute_sha256(spliced_tmp, 32) # Second hash 71 | # TODO Cairo way to do endianness 72 | local out1 73 | local out2 74 | %{ 75 | data = f'{ids.tmpout1:032x}{ids.tmpout2:032x}' 76 | data = "".join(data[::-1]) 77 | ids.out2 = int(data[:32], 16) 78 | ids.out1 = int(data[32:], 16) 79 | %} 80 | 81 | let (local curr_header_hash : felt*) = alloc() 82 | assert curr_header_hash[0] = out1 83 | assert curr_header_hash[1] = out2 84 | 85 | # Verify previous block header with provided hash 86 | let (prev_hash_eq) = arr_eq(prev_header_hash, 2, curr_header_hash, 2) 87 | # assert prev_hash_eq = 1 88 | 89 | # TODO: Verify difficulty target 90 | # - Parse bits into target and convert to Uint256 91 | 92 | let (target) = get_target(header.bits) 93 | %{ 94 | print(hex(ids.out1), hex(ids.out2)) 95 | print(hex(ids.target.low), hex(ids.target.high)) 96 | %} 97 | let hash = Uint256(out1, out2) 98 | let (res) = uint256_lt(hash, target) 99 | assert res = 1 100 | 101 | # TODO: Verify difficulty target interval using timestamps 102 | # TODO: Return current header hash 103 | 104 | return (curr_header_hash) 105 | end 106 | 107 | # NOTE: Uncomment builtin directive on line 1 of this file to run with Makefile 108 | func main{range_check_ptr, bitwise_ptr : BitwiseBuiltin*}(): 109 | alloc_locals 110 | 111 | # Block 0 112 | let (header_data0 : felt*) = alloc() 113 | assert header_data0[0] = 16777216 114 | assert header_data0[1] = 0 115 | assert header_data0[2] = 0 116 | assert header_data0[3] = 0 117 | assert header_data0[4] = 0 118 | assert header_data0[5] = 0 119 | assert header_data0[6] = 0 120 | assert header_data0[7] = 0 121 | assert header_data0[8] = 0 122 | assert header_data0[9] = 1000599037 123 | assert header_data0[10] = 2054886066 124 | assert header_data0[11] = 2059873342 125 | assert header_data0[12] = 1735823201 126 | assert header_data0[13] = 2143820739 127 | assert header_data0[14] = 2290766130 128 | assert header_data0[15] = 983546026 129 | assert header_data0[16] = 1260281418 130 | assert header_data0[17] = 699096905 131 | assert header_data0[18] = 4294901789 132 | assert header_data0[19] = 497822588 133 | let (block_header0) = prepare_header(header_data0) 134 | %{ 135 | print(f'version = {ids.block_header0.version:x}') 136 | print('previous', list(map(hex, memory.get_range(ids.block_header0.previous, 4)))) 137 | print('merkle', list(map(hex, memory.get_range(ids.block_header0.merkle_root, 4)))) 138 | print(f'time = {ids.block_header0.time:x}') 139 | print(f'bits = {ids.block_header0.bits:x}') 140 | print(f'nonce = {ids.block_header0.nonce:x}') 141 | %} 142 | 143 | # Block 1 144 | let (header_data1 : felt*) = alloc() 145 | assert header_data1[0] = 16777216 146 | assert header_data1[1] = 1877117962 147 | assert header_data1[2] = 3069293426 148 | assert header_data1[3] = 3248923206 149 | assert header_data1[4] = 2925786959 150 | assert header_data1[5] = 2468250469 151 | assert header_data1[6] = 3780774044 152 | assert header_data1[7] = 1758861568 153 | assert header_data1[8] = 0 154 | assert header_data1[9] = 2552254973 155 | assert header_data1[10] = 508274500 156 | assert header_data1[11] = 3149817870 157 | assert header_data1[12] = 535696487 158 | assert header_data1[13] = 2074190787 159 | assert header_data1[14] = 1410070449 160 | assert header_data1[15] = 3451258600 161 | assert header_data1[16] = 1461927438 162 | assert header_data1[17] = 1639736905 163 | assert header_data1[18] = 4294901789 164 | assert header_data1[19] = 31679129 165 | let (block_header1) = prepare_header(header_data1) 166 | %{ 167 | print(f'version = {ids.block_header1.version:x}') 168 | print('previous', list(map(hex, memory.get_range(ids.block_header1.previous, 4)))) 169 | print('merkle', list(map(hex, memory.get_range(ids.block_header1.merkle_root, 4)))) 170 | print(f'time = {ids.block_header1.time:x}') 171 | print(f'bits = {ids.block_header1.bits:x}') 172 | print(f'nonce = {ids.block_header1.nonce:x}') 173 | %} 174 | 175 | let (zero_hash : felt*) = alloc() 176 | assert zero_hash[0] = 0 177 | assert zero_hash[1] = 0 178 | 179 | let (block_hash0) = process_header(block_header0, zero_hash) 180 | let (block_hash1) = process_header(block_header1, block_hash0) 181 | 182 | return () 183 | end 184 | -------------------------------------------------------------------------------- /sha256/packed_sha256.cairo: -------------------------------------------------------------------------------- 1 | from starkware.cairo.common.alloc import alloc 2 | from starkware.cairo.common.cairo_builtins import BitwiseBuiltin 3 | from starkware.cairo.common.registers import get_fp_and_pc 4 | 5 | const BLOCK_SIZE = 7 6 | const ALL_ONES = 2 ** 251 - 1 7 | # Pack the different instances with offsets of 35 bits. This is the maximal possible offset for 8 | # 7 32-bit words and it allows space for carry bits in integer addition operations (up to 9 | # 8 summands). 10 | const SHIFTS = 1 + 2 ** 35 + 2 ** (35 * 2) + 2 ** (35 * 3) + 2 ** (35 * 4) + 2 ** (35 * 5) + 11 | 2 ** (35 * 6) 12 | 13 | # Given an array of size 16, extends it to the message schedule array (of size 64) by writing 14 | # 48 more values. 15 | # Each element represents 7 32-bit words from 7 difference instances, starting at bits 16 | # 0, 35, 35 * 2, ..., 35 * 6. 17 | func compute_message_schedule{bitwise_ptr : BitwiseBuiltin*}(message : felt*): 18 | alloc_locals 19 | 20 | # Defining the following constants as local variables saves some instructions. 21 | local shift_mask3 = SHIFTS * (2 ** 32 - 2 ** 3) 22 | local shift_mask7 = SHIFTS * (2 ** 32 - 2 ** 7) 23 | local shift_mask10 = SHIFTS * (2 ** 32 - 2 ** 10) 24 | local shift_mask17 = SHIFTS * (2 ** 32 - 2 ** 17) 25 | local shift_mask18 = SHIFTS * (2 ** 32 - 2 ** 18) 26 | local shift_mask19 = SHIFTS * (2 ** 32 - 2 ** 19) 27 | local mask32ones = SHIFTS * (2 ** 32 - 1) 28 | 29 | # Loop variables. 30 | tempvar bitwise_ptr = bitwise_ptr 31 | tempvar message = message + 16 32 | tempvar n = 64 - 16 33 | 34 | loop: 35 | # Compute s0 = right_rot(w[i - 15], 7) ^ right_rot(w[i - 15], 18) ^ (w[i - 15] >> 3). 36 | tempvar w0 = message[-15] 37 | assert bitwise_ptr[0].x = w0 38 | assert bitwise_ptr[0].y = shift_mask7 39 | let w0_rot7 = (2 ** (32 - 7)) * w0 + (1 / 2 ** 7 - 2 ** (32 - 7)) * bitwise_ptr[0].x_and_y 40 | assert bitwise_ptr[1].x = w0 41 | assert bitwise_ptr[1].y = shift_mask18 42 | let w0_rot18 = (2 ** (32 - 18)) * w0 + (1 / 2 ** 18 - 2 ** (32 - 18)) * bitwise_ptr[1].x_and_y 43 | assert bitwise_ptr[2].x = w0 44 | assert bitwise_ptr[2].y = shift_mask3 45 | let w0_shift3 = (1 / 2 ** 3) * bitwise_ptr[2].x_and_y 46 | assert bitwise_ptr[3].x = w0_rot7 47 | assert bitwise_ptr[3].y = w0_rot18 48 | assert bitwise_ptr[4].x = bitwise_ptr[3].x_xor_y 49 | assert bitwise_ptr[4].y = w0_shift3 50 | let s0 = bitwise_ptr[4].x_xor_y 51 | let bitwise_ptr = bitwise_ptr + 5 * BitwiseBuiltin.SIZE 52 | 53 | # Compute s1 = right_rot(w[i - 2], 17) ^ right_rot(w[i - 2], 19) ^ (w[i - 2] >> 10). 54 | tempvar w1 = message[-2] 55 | assert bitwise_ptr[0].x = w1 56 | assert bitwise_ptr[0].y = shift_mask17 57 | let w1_rot17 = (2 ** (32 - 17)) * w1 + (1 / 2 ** 17 - 2 ** (32 - 17)) * bitwise_ptr[0].x_and_y 58 | assert bitwise_ptr[1].x = w1 59 | assert bitwise_ptr[1].y = shift_mask19 60 | let w1_rot19 = (2 ** (32 - 19)) * w1 + (1 / 2 ** 19 - 2 ** (32 - 19)) * bitwise_ptr[1].x_and_y 61 | assert bitwise_ptr[2].x = w1 62 | assert bitwise_ptr[2].y = shift_mask10 63 | let w1_shift10 = (1 / 2 ** 10) * bitwise_ptr[2].x_and_y 64 | assert bitwise_ptr[3].x = w1_rot17 65 | assert bitwise_ptr[3].y = w1_rot19 66 | assert bitwise_ptr[4].x = bitwise_ptr[3].x_xor_y 67 | assert bitwise_ptr[4].y = w1_shift10 68 | let s1 = bitwise_ptr[4].x_xor_y 69 | let bitwise_ptr = bitwise_ptr + 5 * BitwiseBuiltin.SIZE 70 | 71 | assert bitwise_ptr[0].x = message[-16] + s0 + message[-7] + s1 72 | assert bitwise_ptr[0].y = mask32ones 73 | assert message[0] = bitwise_ptr[0].x_and_y 74 | let bitwise_ptr = bitwise_ptr + BitwiseBuiltin.SIZE 75 | 76 | tempvar bitwise_ptr = bitwise_ptr 77 | tempvar message = message + 1 78 | tempvar n = n - 1 79 | jmp loop if n != 0 80 | 81 | return () 82 | end 83 | 84 | func sha2_compress{bitwise_ptr : BitwiseBuiltin*}( 85 | state : felt*, message : felt*, round_constants : felt*) -> (new_state : felt*): 86 | alloc_locals 87 | 88 | # Defining the following constants as local variables saves some instructions. 89 | local shift_mask2 = SHIFTS * (2 ** 32 - 2 ** 2) 90 | local shift_mask13 = SHIFTS * (2 ** 32 - 2 ** 13) 91 | local shift_mask22 = SHIFTS * (2 ** 32 - 2 ** 22) 92 | local shift_mask6 = SHIFTS * (2 ** 32 - 2 ** 6) 93 | local shift_mask11 = SHIFTS * (2 ** 32 - 2 ** 11) 94 | local shift_mask25 = SHIFTS * (2 ** 32 - 2 ** 25) 95 | local mask32ones = SHIFTS * (2 ** 32 - 1) 96 | 97 | tempvar a = state[0] 98 | tempvar b = state[1] 99 | tempvar c = state[2] 100 | tempvar d = state[3] 101 | tempvar e = state[4] 102 | tempvar f = state[5] 103 | tempvar g = state[6] 104 | tempvar h = state[7] 105 | tempvar round_constants = round_constants 106 | tempvar message = message 107 | tempvar bitwise_ptr = bitwise_ptr 108 | tempvar n = 64 109 | 110 | loop: 111 | # Compute s0 = right_rot(a, 2) ^ right_rot(a, 13) ^ right_rot(a, 22). 112 | assert bitwise_ptr[0].x = a 113 | assert bitwise_ptr[0].y = shift_mask2 114 | let a_rot2 = (2 ** (32 - 2)) * a + (1 / 2 ** 2 - 2 ** (32 - 2)) * bitwise_ptr[0].x_and_y 115 | assert bitwise_ptr[1].x = a 116 | assert bitwise_ptr[1].y = shift_mask13 117 | let a_rot13 = (2 ** (32 - 13)) * a + (1 / 2 ** 13 - 2 ** (32 - 13)) * bitwise_ptr[1].x_and_y 118 | assert bitwise_ptr[2].x = a 119 | assert bitwise_ptr[2].y = shift_mask22 120 | let a_rot22 = (2 ** (32 - 22)) * a + (1 / 2 ** 22 - 2 ** (32 - 22)) * bitwise_ptr[2].x_and_y 121 | assert bitwise_ptr[3].x = a_rot2 122 | assert bitwise_ptr[3].y = a_rot13 123 | assert bitwise_ptr[4].x = bitwise_ptr[3].x_xor_y 124 | assert bitwise_ptr[4].y = a_rot22 125 | let s0 = bitwise_ptr[4].x_xor_y 126 | let bitwise_ptr = bitwise_ptr + 5 * BitwiseBuiltin.SIZE 127 | 128 | # Compute s1 = right_rot(e, 6) ^ right_rot(e, 11) ^ right_rot(e, 25). 129 | assert bitwise_ptr[0].x = e 130 | assert bitwise_ptr[0].y = shift_mask6 131 | let e_rot6 = (2 ** (32 - 6)) * e + (1 / 2 ** 6 - 2 ** (32 - 6)) * bitwise_ptr[0].x_and_y 132 | assert bitwise_ptr[1].x = e 133 | assert bitwise_ptr[1].y = shift_mask11 134 | let e_rot11 = (2 ** (32 - 11)) * e + (1 / 2 ** 11 - 2 ** (32 - 11)) * bitwise_ptr[1].x_and_y 135 | assert bitwise_ptr[2].x = e 136 | assert bitwise_ptr[2].y = shift_mask25 137 | let e_rot25 = (2 ** (32 - 25)) * e + (1 / 2 ** 25 - 2 ** (32 - 25)) * bitwise_ptr[2].x_and_y 138 | assert bitwise_ptr[3].x = e_rot6 139 | assert bitwise_ptr[3].y = e_rot11 140 | assert bitwise_ptr[4].x = bitwise_ptr[3].x_xor_y 141 | assert bitwise_ptr[4].y = e_rot25 142 | let s1 = bitwise_ptr[4].x_xor_y 143 | let bitwise_ptr = bitwise_ptr + 5 * BitwiseBuiltin.SIZE 144 | 145 | # Compute ch = (e & f) ^ ((~e) & g). 146 | assert bitwise_ptr[0].x = e 147 | assert bitwise_ptr[0].y = f 148 | assert bitwise_ptr[1].x = ALL_ONES - e 149 | assert bitwise_ptr[1].y = g 150 | let ch = bitwise_ptr[0].x_and_y + bitwise_ptr[1].x_and_y 151 | let bitwise_ptr = bitwise_ptr + 2 * BitwiseBuiltin.SIZE 152 | 153 | # Compute maj = (a & b) ^ (a & c) ^ (b & c). 154 | assert bitwise_ptr[0].x = a 155 | assert bitwise_ptr[0].y = b 156 | assert bitwise_ptr[1].x = bitwise_ptr[0].x_xor_y 157 | assert bitwise_ptr[1].y = c 158 | let maj = (a + b + c - bitwise_ptr[1].x_xor_y) / 2 159 | let bitwise_ptr = bitwise_ptr + 2 * BitwiseBuiltin.SIZE 160 | 161 | tempvar temp1 = h + s1 + ch + round_constants[0] + message[0] 162 | tempvar temp2 = s0 + maj 163 | 164 | assert bitwise_ptr[0].x = temp1 + temp2 165 | assert bitwise_ptr[0].y = mask32ones 166 | let new_a = bitwise_ptr[0].x_and_y 167 | assert bitwise_ptr[1].x = d + temp1 168 | assert bitwise_ptr[1].y = mask32ones 169 | let new_e = bitwise_ptr[1].x_and_y 170 | let bitwise_ptr = bitwise_ptr + 2 * BitwiseBuiltin.SIZE 171 | 172 | tempvar new_a = new_a 173 | tempvar new_b = a 174 | tempvar new_c = b 175 | tempvar new_d = c 176 | tempvar new_e = new_e 177 | tempvar new_f = e 178 | tempvar new_g = f 179 | tempvar new_h = g 180 | tempvar round_constants = round_constants + 1 181 | tempvar message = message + 1 182 | tempvar bitwise_ptr = bitwise_ptr 183 | tempvar n = n - 1 184 | jmp loop if n != 0 185 | 186 | # Add the compression result to the original state: 187 | let (res) = alloc() 188 | assert bitwise_ptr[0].x = state[0] + new_a 189 | assert bitwise_ptr[0].y = mask32ones 190 | assert res[0] = bitwise_ptr[0].x_and_y 191 | assert bitwise_ptr[1].x = state[1] + new_b 192 | assert bitwise_ptr[1].y = mask32ones 193 | assert res[1] = bitwise_ptr[1].x_and_y 194 | assert bitwise_ptr[2].x = state[2] + new_c 195 | assert bitwise_ptr[2].y = mask32ones 196 | assert res[2] = bitwise_ptr[2].x_and_y 197 | assert bitwise_ptr[3].x = state[3] + new_d 198 | assert bitwise_ptr[3].y = mask32ones 199 | assert res[3] = bitwise_ptr[3].x_and_y 200 | assert bitwise_ptr[4].x = state[4] + new_e 201 | assert bitwise_ptr[4].y = mask32ones 202 | assert res[4] = bitwise_ptr[4].x_and_y 203 | assert bitwise_ptr[5].x = state[5] + new_f 204 | assert bitwise_ptr[5].y = mask32ones 205 | assert res[5] = bitwise_ptr[5].x_and_y 206 | assert bitwise_ptr[6].x = state[6] + new_g 207 | assert bitwise_ptr[6].y = mask32ones 208 | assert res[6] = bitwise_ptr[6].x_and_y 209 | assert bitwise_ptr[7].x = state[7] + new_h 210 | assert bitwise_ptr[7].y = mask32ones 211 | assert res[7] = bitwise_ptr[7].x_and_y 212 | let bitwise_ptr = bitwise_ptr + 8 * BitwiseBuiltin.SIZE 213 | 214 | return (res) 215 | end 216 | 217 | # Returns the 64 round constants of SHA256. 218 | func get_round_constants() -> (round_constants : felt*): 219 | alloc_locals 220 | let (__fp__, _) = get_fp_and_pc() 221 | local round_constants = 0x428A2F98 * SHIFTS 222 | local a = 0x71374491 * SHIFTS 223 | local a = 0xB5C0FBCF * SHIFTS 224 | local a = 0xE9B5DBA5 * SHIFTS 225 | local a = 0x3956C25B * SHIFTS 226 | local a = 0x59F111F1 * SHIFTS 227 | local a = 0x923F82A4 * SHIFTS 228 | local a = 0xAB1C5ED5 * SHIFTS 229 | local a = 0xD807AA98 * SHIFTS 230 | local a = 0x12835B01 * SHIFTS 231 | local a = 0x243185BE * SHIFTS 232 | local a = 0x550C7DC3 * SHIFTS 233 | local a = 0x72BE5D74 * SHIFTS 234 | local a = 0x80DEB1FE * SHIFTS 235 | local a = 0x9BDC06A7 * SHIFTS 236 | local a = 0xC19BF174 * SHIFTS 237 | local a = 0xE49B69C1 * SHIFTS 238 | local a = 0xEFBE4786 * SHIFTS 239 | local a = 0x0FC19DC6 * SHIFTS 240 | local a = 0x240CA1CC * SHIFTS 241 | local a = 0x2DE92C6F * SHIFTS 242 | local a = 0x4A7484AA * SHIFTS 243 | local a = 0x5CB0A9DC * SHIFTS 244 | local a = 0x76F988DA * SHIFTS 245 | local a = 0x983E5152 * SHIFTS 246 | local a = 0xA831C66D * SHIFTS 247 | local a = 0xB00327C8 * SHIFTS 248 | local a = 0xBF597FC7 * SHIFTS 249 | local a = 0xC6E00BF3 * SHIFTS 250 | local a = 0xD5A79147 * SHIFTS 251 | local a = 0x06CA6351 * SHIFTS 252 | local a = 0x14292967 * SHIFTS 253 | local a = 0x27B70A85 * SHIFTS 254 | local a = 0x2E1B2138 * SHIFTS 255 | local a = 0x4D2C6DFC * SHIFTS 256 | local a = 0x53380D13 * SHIFTS 257 | local a = 0x650A7354 * SHIFTS 258 | local a = 0x766A0ABB * SHIFTS 259 | local a = 0x81C2C92E * SHIFTS 260 | local a = 0x92722C85 * SHIFTS 261 | local a = 0xA2BFE8A1 * SHIFTS 262 | local a = 0xA81A664B * SHIFTS 263 | local a = 0xC24B8B70 * SHIFTS 264 | local a = 0xC76C51A3 * SHIFTS 265 | local a = 0xD192E819 * SHIFTS 266 | local a = 0xD6990624 * SHIFTS 267 | local a = 0xF40E3585 * SHIFTS 268 | local a = 0x106AA070 * SHIFTS 269 | local a = 0x19A4C116 * SHIFTS 270 | local a = 0x1E376C08 * SHIFTS 271 | local a = 0x2748774C * SHIFTS 272 | local a = 0x34B0BCB5 * SHIFTS 273 | local a = 0x391C0CB3 * SHIFTS 274 | local a = 0x4ED8AA4A * SHIFTS 275 | local a = 0x5B9CCA4F * SHIFTS 276 | local a = 0x682E6FF3 * SHIFTS 277 | local a = 0x748F82EE * SHIFTS 278 | local a = 0x78A5636F * SHIFTS 279 | local a = 0x84C87814 * SHIFTS 280 | local a = 0x8CC70208 * SHIFTS 281 | local a = 0x90BEFFFA * SHIFTS 282 | local a = 0xA4506CEB * SHIFTS 283 | local a = 0xBEF9A3F7 * SHIFTS 284 | local a = 0xC67178F2 * SHIFTS 285 | return (&round_constants) 286 | end 287 | -------------------------------------------------------------------------------- /sha256/sha256.cairo: -------------------------------------------------------------------------------- 1 | from sha256.packed_sha256 import ( 2 | BLOCK_SIZE, 3 | compute_message_schedule, 4 | sha2_compress, 5 | get_round_constants, 6 | ) 7 | from starkware.cairo.common.alloc import alloc 8 | from starkware.cairo.common.registers import get_fp_and_pc 9 | from starkware.cairo.common.cairo_builtins import BitwiseBuiltin 10 | from starkware.cairo.common.math import assert_nn_le, unsigned_div_rem 11 | from starkware.cairo.common.memset import memset 12 | from starkware.cairo.common.pow import pow 13 | 14 | const SHA256_INPUT_CHUNK_SIZE_FELTS = 16 15 | const SHA256_STATE_SIZE_FELTS = 8 16 | # Each instance consists of 32 words (1024 bits) of message 17 | # + 8 words for the input state 18 | # + 8 words for the output state. 19 | 20 | # Computes SHA256 of 'input'. Inputs of up to 55 bytes are supported. 21 | # To use this function, split the input into (up to) 14 words of 32 bits (big endian). 22 | # For example, to compute sha256('Hello world'), use: 23 | # input = [1214606444, 1864398703, 1919706112] 24 | # where: 25 | # 1214606444 == int.from_bytes(b'Hell', 'big') 26 | # 1864398703 == int.from_bytes(b'o wo', 'big') 27 | # 1919706112 == int.from_bytes(b'rld\x00', 'big') # Note the '\x00' padding. 28 | # 29 | # output is an array of 8 32-bit words (big endian). 30 | # 31 | # Assumption: n_bytes <= 55. 32 | # 33 | # Note: You must call finalize_sha2() at the end of the program. Otherwise, this function 34 | # is not sound and a malicious prover may return a wrong result. 35 | # Note: the interface of this function may change in the future. 36 | # TODO: currently only works for n_bytes that are multiples of 4 (or 8?) Fix this. 37 | func sha256{range_check_ptr, sha256_ptr : felt*}(input : felt*, n_bytes : felt) -> (output : felt*): 38 | alloc_locals 39 | # we hardcode to 80 for the btc lightclient 40 | assert_nn_le(n_bytes, 80) 41 | let sha256_start = sha256_ptr 42 | local num_chunks 43 | %{ 44 | # print("input:", memory.get_range(ids.input, ids.n_bytes // 4)) 45 | # print("n_bytes:", ids.n_bytes) 46 | # print() 47 | from math import ceil 48 | ids.num_chunks = ceil(ids.n_bytes / 4 / ids.SHA256_INPUT_CHUNK_SIZE_FELTS) 49 | # print(ids.num_chunks) 50 | %} 51 | # NOTE: We want to fill the input with SHA256_INPUT_CHUNK_SIZE_FELTS words 52 | # starting from the initial n_bytes, and filling the rest according to 53 | # the spec 54 | _sha256_input( 55 | input=input, n_bytes=n_bytes, n_words=num_chunks * SHA256_INPUT_CHUNK_SIZE_FELTS - 2 56 | ) 57 | assert sha256_ptr[0] = 0 58 | assert sha256_ptr[1] = n_bytes * 8 59 | let sha256_ptr = sha256_ptr + 2 60 | 61 | # Set the initial state to IV. 62 | assert sha256_ptr[0] = 0x6A09E667 63 | assert sha256_ptr[1] = 0xBB67AE85 64 | assert sha256_ptr[2] = 0x3C6EF372 65 | assert sha256_ptr[3] = 0xA54FF53A 66 | assert sha256_ptr[4] = 0x510E527F 67 | assert sha256_ptr[5] = 0x9B05688C 68 | assert sha256_ptr[6] = 0x1F83D9AB 69 | assert sha256_ptr[7] = 0x5BE0CD19 70 | let sha256_ptr = sha256_ptr + SHA256_STATE_SIZE_FELTS 71 | 72 | let output = sha256_ptr 73 | %{ 74 | from starkware.cairo.common.cairo_sha256.sha256_utils import ( 75 | IV, compute_message_schedule, sha2_compress_function) 76 | 77 | new_state = IV 78 | for i in range(ids.num_chunks): 79 | w = compute_message_schedule(memory.get_range( 80 | ids.sha256_start + i*ids.SHA256_INPUT_CHUNK_SIZE_FELTS, ids.SHA256_INPUT_CHUNK_SIZE_FELTS)) 81 | new_state = sha2_compress_function(new_state, w) 82 | 83 | segments.write_arg(ids.output, new_state) 84 | %} 85 | let sha256_ptr = sha256_ptr + SHA256_STATE_SIZE_FELTS 86 | return (output) 87 | end 88 | 89 | # this function essentially performs 90 | # https://blog.boot.dev/cryptography/how-sha-2-works-step-by-step-sha-256/#step-1---pre-processing 91 | # except that it doesn't take a 512 bits input... not sure why 92 | func _sha256_input{range_check_ptr, sha256_ptr : felt*}( 93 | input : felt*, n_bytes : felt, n_words : felt 94 | ): 95 | alloc_locals 96 | 97 | local full_word 98 | %{ ids.full_word = int(ids.n_bytes >= 4) %} 99 | 100 | if full_word != 0: 101 | assert sha256_ptr[0] = input[0] 102 | let sha256_ptr = sha256_ptr + 1 103 | return _sha256_input(input=input + 1, n_bytes=n_bytes - 4, n_words=n_words - 1) 104 | end 105 | 106 | # This is the last input word, so we should add a byte '0x80' at the end and fill the rest with 107 | # zeros. 108 | 109 | if n_bytes == 0: 110 | assert sha256_ptr[0] = 0x80000000 111 | memset(dst=sha256_ptr + 1, value=0, n=n_words - 1) 112 | let sha256_ptr = sha256_ptr + n_words 113 | return () 114 | end 115 | 116 | assert_nn_le(n_bytes, 3) 117 | let (padding) = pow(256, 3 - n_bytes) 118 | local range_check_ptr = range_check_ptr 119 | 120 | assert sha256_ptr[0] = input[0] + padding * 0x80 121 | 122 | memset(dst=sha256_ptr + 1, value=0, n=n_words - 1) 123 | let sha256_ptr = sha256_ptr + n_words 124 | return () 125 | end 126 | 127 | # TODO: 1 should be num_blocks... make this a dynamic constant inside the finalize fcts 128 | const SHA256_INSTANCE_SIZE = 1 * SHA256_INPUT_CHUNK_SIZE_FELTS + 2 * SHA256_STATE_SIZE_FELTS 129 | # Handles n blocks of BLOCK_SIZE SHA256 instances. 130 | func _finalize_sha256_inner{range_check_ptr, bitwise_ptr : BitwiseBuiltin*}( 131 | sha256_ptr : felt*, n : felt, round_constants : felt* 132 | ): 133 | if n == 0: 134 | return () 135 | end 136 | 137 | alloc_locals 138 | 139 | local MAX_VALUE = 2 ** 32 - 1 140 | 141 | let sha256_start = sha256_ptr 142 | 143 | let (local message_start : felt*) = alloc() 144 | let (local input_state_start : felt*) = alloc() 145 | 146 | # Handle message. 147 | 148 | tempvar message = message_start 149 | tempvar sha256_ptr = sha256_ptr 150 | tempvar range_check_ptr = range_check_ptr 151 | tempvar m = SHA256_INPUT_CHUNK_SIZE_FELTS 152 | 153 | message_loop: 154 | tempvar x0 = sha256_ptr[0 * SHA256_INSTANCE_SIZE] 155 | assert [range_check_ptr + 0] = x0 156 | assert [range_check_ptr + 1] = MAX_VALUE - x0 157 | tempvar x1 = sha256_ptr[1 * SHA256_INSTANCE_SIZE] 158 | assert [range_check_ptr + 2] = x1 159 | assert [range_check_ptr + 3] = MAX_VALUE - x1 160 | tempvar x2 = sha256_ptr[2 * SHA256_INSTANCE_SIZE] 161 | assert [range_check_ptr + 4] = x2 162 | assert [range_check_ptr + 5] = MAX_VALUE - x2 163 | tempvar x3 = sha256_ptr[3 * SHA256_INSTANCE_SIZE] 164 | assert [range_check_ptr + 6] = x3 165 | assert [range_check_ptr + 7] = MAX_VALUE - x3 166 | tempvar x4 = sha256_ptr[4 * SHA256_INSTANCE_SIZE] 167 | assert [range_check_ptr + 8] = x4 168 | assert [range_check_ptr + 9] = MAX_VALUE - x4 169 | tempvar x5 = sha256_ptr[5 * SHA256_INSTANCE_SIZE] 170 | assert [range_check_ptr + 10] = x5 171 | assert [range_check_ptr + 11] = MAX_VALUE - x5 172 | tempvar x6 = sha256_ptr[6 * SHA256_INSTANCE_SIZE] 173 | assert [range_check_ptr + 12] = x6 174 | assert [range_check_ptr + 13] = MAX_VALUE - x6 175 | assert message[0] = x0 + 2 ** 35 * x1 + 2 ** (35 * 2) * x2 + 2 ** (35 * 3) * x3 + 176 | 2 ** (35 * 4) * x4 + 2 ** (35 * 5) * x5 + 2 ** (35 * 6) * x6 177 | 178 | tempvar message = message + 1 179 | tempvar sha256_ptr = sha256_ptr + 1 180 | tempvar range_check_ptr = range_check_ptr + 14 181 | tempvar m = m - 1 182 | jmp message_loop if m != 0 183 | 184 | # Handle input state. 185 | 186 | tempvar input_state = input_state_start 187 | tempvar sha256_ptr = sha256_ptr 188 | tempvar range_check_ptr = range_check_ptr 189 | tempvar m = SHA256_STATE_SIZE_FELTS 190 | 191 | input_state_loop: 192 | tempvar x0 = sha256_ptr[0 * SHA256_INSTANCE_SIZE] 193 | assert [range_check_ptr + 0] = x0 194 | assert [range_check_ptr + 1] = MAX_VALUE - x0 195 | tempvar x1 = sha256_ptr[1 * SHA256_INSTANCE_SIZE] 196 | assert [range_check_ptr + 2] = x1 197 | assert [range_check_ptr + 3] = MAX_VALUE - x1 198 | tempvar x2 = sha256_ptr[2 * SHA256_INSTANCE_SIZE] 199 | assert [range_check_ptr + 4] = x2 200 | assert [range_check_ptr + 5] = MAX_VALUE - x2 201 | tempvar x3 = sha256_ptr[3 * SHA256_INSTANCE_SIZE] 202 | assert [range_check_ptr + 6] = x3 203 | assert [range_check_ptr + 7] = MAX_VALUE - x3 204 | tempvar x4 = sha256_ptr[4 * SHA256_INSTANCE_SIZE] 205 | assert [range_check_ptr + 8] = x4 206 | assert [range_check_ptr + 9] = MAX_VALUE - x4 207 | tempvar x5 = sha256_ptr[5 * SHA256_INSTANCE_SIZE] 208 | assert [range_check_ptr + 10] = x5 209 | assert [range_check_ptr + 11] = MAX_VALUE - x5 210 | tempvar x6 = sha256_ptr[6 * SHA256_INSTANCE_SIZE] 211 | assert [range_check_ptr + 12] = x6 212 | assert [range_check_ptr + 13] = MAX_VALUE - x6 213 | assert input_state[0] = x0 + 2 ** 35 * x1 + 2 ** (35 * 2) * x2 + 2 ** (35 * 3) * x3 + 214 | 2 ** (35 * 4) * x4 + 2 ** (35 * 5) * x5 + 2 ** (35 * 6) * x6 215 | 216 | tempvar input_state = input_state + 1 217 | tempvar sha256_ptr = sha256_ptr + 1 218 | tempvar range_check_ptr = range_check_ptr + 14 219 | tempvar m = m - 1 220 | jmp input_state_loop if m != 0 221 | 222 | # Run sha256 on the 7 instances. 223 | 224 | local sha256_ptr : felt* = sha256_ptr 225 | local range_check_ptr = range_check_ptr 226 | compute_message_schedule(message_start) 227 | let (outputs) = sha2_compress(input_state_start, message_start, round_constants) 228 | local bitwise_ptr : BitwiseBuiltin* = bitwise_ptr 229 | 230 | # Handle outputs. 231 | 232 | tempvar outputs = outputs 233 | tempvar sha256_ptr = sha256_ptr 234 | tempvar range_check_ptr = range_check_ptr 235 | tempvar m = SHA256_STATE_SIZE_FELTS 236 | 237 | output_loop: 238 | tempvar x0 = sha256_ptr[0 * SHA256_INSTANCE_SIZE] 239 | assert [range_check_ptr] = x0 240 | assert [range_check_ptr + 1] = MAX_VALUE - x0 241 | tempvar x1 = sha256_ptr[1 * SHA256_INSTANCE_SIZE] 242 | assert [range_check_ptr + 2] = x1 243 | assert [range_check_ptr + 3] = MAX_VALUE - x1 244 | tempvar x2 = sha256_ptr[2 * SHA256_INSTANCE_SIZE] 245 | assert [range_check_ptr + 4] = x2 246 | assert [range_check_ptr + 5] = MAX_VALUE - x2 247 | tempvar x3 = sha256_ptr[3 * SHA256_INSTANCE_SIZE] 248 | assert [range_check_ptr + 6] = x3 249 | assert [range_check_ptr + 7] = MAX_VALUE - x3 250 | tempvar x4 = sha256_ptr[4 * SHA256_INSTANCE_SIZE] 251 | assert [range_check_ptr + 8] = x4 252 | assert [range_check_ptr + 9] = MAX_VALUE - x4 253 | tempvar x5 = sha256_ptr[5 * SHA256_INSTANCE_SIZE] 254 | assert [range_check_ptr + 10] = x5 255 | assert [range_check_ptr + 11] = MAX_VALUE - x5 256 | tempvar x6 = sha256_ptr[6 * SHA256_INSTANCE_SIZE] 257 | assert [range_check_ptr + 12] = x6 258 | assert [range_check_ptr + 13] = MAX_VALUE - x6 259 | assert outputs[0] = x0 + 2 ** 35 * x1 + 2 ** (35 * 2) * x2 + 2 ** (35 * 3) * x3 + 260 | 2 ** (35 * 4) * x4 + 2 ** (35 * 5) * x5 + 2 ** (35 * 6) * x6 261 | 262 | tempvar outputs = outputs + 1 263 | tempvar sha256_ptr = sha256_ptr + 1 264 | tempvar range_check_ptr = range_check_ptr + 14 265 | tempvar m = m - 1 266 | jmp output_loop if m != 0 267 | 268 | return _finalize_sha256_inner( 269 | sha256_ptr=sha256_start + SHA256_INSTANCE_SIZE * BLOCK_SIZE, 270 | n=n - 1, 271 | round_constants=round_constants, 272 | ) 273 | end 274 | 275 | # Verifies that the results of sha256() are valid. 276 | func finalize_sha256{range_check_ptr, bitwise_ptr : BitwiseBuiltin*}( 277 | sha256_ptr_start : felt*, sha256_ptr_end : felt* 278 | ): 279 | alloc_locals 280 | 281 | let (__fp__, _) = get_fp_and_pc() 282 | 283 | let (round_constants) = get_round_constants() 284 | 285 | tempvar n = (sha256_ptr_end - sha256_ptr_start) / SHA256_INSTANCE_SIZE 286 | if n == 0: 287 | return () 288 | end 289 | 290 | %{ 291 | # Add dummy pairs of input and output. 292 | from starkware.cairo.common.cairo_sha256.sha256_utils import ( 293 | IV, compute_message_schedule, sha2_compress_function) 294 | 295 | _block_size = int(ids.BLOCK_SIZE) 296 | assert 0 <= _block_size < 20 297 | _sha256_input_chunk_size_felts = int(ids.SHA256_INPUT_CHUNK_SIZE_FELTS) 298 | assert 0 <= _sha256_input_chunk_size_felts < 100 299 | 300 | message = [0] * _sha256_input_chunk_size_felts 301 | w = compute_message_schedule(message) 302 | output = sha2_compress_function(IV, w) 303 | padding = (message + IV + output) * (_block_size - 1) 304 | segments.write_arg(ids.sha256_ptr_end, padding) 305 | %} 306 | 307 | # Compute the amount of blocks (rounded up). 308 | let (local q, r) = unsigned_div_rem(n + BLOCK_SIZE - 1, BLOCK_SIZE) 309 | _finalize_sha256_inner(sha256_ptr_start, n=q, round_constants=round_constants) 310 | return () 311 | end 312 | --------------------------------------------------------------------------------