-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from catalogfi/feat/spvlib
Add libraries and contracts for Bitcoin SPV
- Loading branch information
Showing
25 changed files
with
17,504 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
name: slither | ||
on: [push, pull_request, workflow_dispatch] | ||
|
||
jobs: | ||
analyze: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Code Checkout | ||
uses: actions/checkout@v4 | ||
|
||
- name: Setup Python | ||
uses: actions/setup-python@v4 | ||
with: | ||
python-version: '3.10' | ||
|
||
- name: Run Analysis | ||
uses: crytic/[email protected] | ||
with: | ||
fail-on: high |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
name: test | ||
|
||
on: [push, pull_request, workflow_dispatch] | ||
|
||
env: | ||
FOUNDRY_PROFILE: ci | ||
|
||
jobs: | ||
check: | ||
strategy: | ||
fail-fast: true | ||
|
||
name: Foundry project | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 | ||
with: | ||
submodules: recursive | ||
|
||
- name: Install Foundry | ||
uses: foundry-rs/foundry-toolchain@v1 | ||
with: | ||
version: nightly | ||
|
||
- name: Run Forge build | ||
run: | | ||
forge --version | ||
forge build --sizes | ||
id: build | ||
|
||
- name: Run Forge tests | ||
run: | | ||
forge test -vvv | ||
id: test | ||
|
||
- name: Run Forge coverage | ||
run: | | ||
forge coverage -vvv | ||
id: coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Compiler files | ||
cache/ | ||
out/ | ||
|
||
# Ignores development broadcast logs | ||
!/broadcast | ||
/broadcast/*/31337/ | ||
/broadcast/**/dry-run/ | ||
|
||
# Docs | ||
docs/ | ||
|
||
# Dotenv file | ||
.env | ||
.gas-snapshot | ||
|
||
# Coverage | ||
lcov.info |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[submodule "lib/forge-std"] | ||
path = lib/forge-std | ||
url = https://github.com/foundry-rs/forge-std | ||
[submodule "lib/openzeppelin-contracts"] | ||
path = lib/openzeppelin-contracts | ||
url = https://github.com/OpenZeppelin/openzeppelin-contracts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"solidity.remappings": [ | ||
"forge-std/=lib/forge-std/src/", | ||
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/" | ||
], | ||
"solidity.compileUsingRemoteVersion": "v0.8.20+commit.a1b79de6" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
# btc-utils | ||
# btc-utils |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Technical Specification Document | ||
|
||
# Objective | ||
|
||
Brief description of what the feature aims to achieve. | ||
|
||
# Functional Requirement | ||
|
||
1. Requirement | ||
2. Requirement | ||
3. Requirement | ||
1. Sub Requirement | ||
2. Sub Requirement | ||
4. Requirement | ||
|
||
# Non-Functional Requirement | ||
|
||
1. Performance Criteria | ||
2. Security Measures | ||
3. Scalability Approach | ||
4. Maintenance Requirement | ||
|
||
# Dependencies | ||
|
||
- [ ] Feature Dependency | ||
- [ ] Knowledge Transfer | ||
- [ ] Specific Tasks | ||
|
||
# Visual Representation | ||
|
||
## System Architecture | ||
|
||
## Flow Diagram | ||
|
||
# Exposed API/Interface | ||
|
||
# Test Cases | ||
|
||
Outline primary test scenarios to validate the functionality. | ||
|
||
Generally all repos. should have 80%+ unit test cases. Also, there should be details how we can test something that is not present in the test cases. | ||
|
||
# Discussions/Issues | ||
|
||
Planning to setup all GitHub repos. to support it there. | ||
|
||
```markdown | ||
# Action Items for Team Members | ||
|
||
- [ ] All team members are required to create spec sheets for all active projects they're involved in. | ||
- [ ] Each spec sheet must be reviewed by Reetik before implementation begins and any changes way forward/ | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[profile.default] | ||
src = "src" | ||
out = "out" | ||
libs = ["lib"] | ||
remappings = [ | ||
'@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/', | ||
'forge-std/=lib/forge-std/src/', | ||
] | ||
fs_permissions = [{ access = "read", path = "./test/fixtures"}] | ||
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options |
Submodule openzeppelin-contracts
added at
dbb610
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"filter_paths": "lib" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity 0.8.20; | ||
|
||
struct BlockHeader { | ||
bytes32 merkleRootHash; | ||
bytes4 nBits; | ||
bytes4 nonce; | ||
bytes32 previousBlockHash; | ||
bytes4 timestamp; | ||
bytes4 version; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.20; | ||
|
||
import {BlockHeader} from "./Types.sol"; | ||
|
||
library Utils { | ||
function convertBytesToUint(bytes memory b) internal pure returns (uint256) { | ||
uint256 number; | ||
uint256 length = b.length; | ||
require(length <= 32, "SPVLib: length cannot be greater than 32 bytes"); | ||
for (uint256 i = 0; i < length; i++) { | ||
number = number + uint256(uint8(b[i])) * (2 ** (8 * (length - (i + 1)))); | ||
} | ||
return number; | ||
} | ||
|
||
function convertToBigEndian(bytes memory bytesLE) internal pure returns (bytes memory) { | ||
uint256 length = bytesLE.length; | ||
bytes memory bytesBE = new bytes(length); | ||
for (uint256 i = 0; i < length; i++) { | ||
bytesBE[length - i - 1] = bytesLE[i]; | ||
} | ||
return bytesBE; | ||
} | ||
|
||
function convertnBitsToTarget(bytes memory nBitsBytes) internal pure returns (uint256) { | ||
uint256 nBits = convertBytesToUint(convertToBigEndian(nBitsBytes)); | ||
uint256 exp = uint256(nBits) >> 24; | ||
uint256 c = nBits & 0xffffff; | ||
uint256 target = uint256((c * 2 ** (8 * (exp - 3)))); | ||
return target; | ||
} | ||
|
||
function doubleHash(bytes memory data) internal pure returns (bytes32) { | ||
return sha256(abi.encodePacked(sha256(abi.encodePacked(data)))); | ||
} | ||
|
||
function convertToBytes32(bytes memory data) internal pure returns (bytes32 result) { | ||
assembly { | ||
// Copy 32 bytes from data into result | ||
result := mload(add(data, 32)) | ||
} | ||
} | ||
|
||
function parseBlockHeader(bytes calldata blockHeader) internal pure returns (BlockHeader memory parsedHeader) { | ||
parsedHeader.version = bytes4(blockHeader[:4]); | ||
parsedHeader.previousBlockHash = bytes32(blockHeader[4:36]); | ||
parsedHeader.merkleRootHash = bytes32(blockHeader[36:68]); | ||
parsedHeader.timestamp = bytes4(blockHeader[68:72]); | ||
parsedHeader.nBits = bytes4(blockHeader[72:76]); | ||
parsedHeader.nonce = bytes4(blockHeader[76:]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity 0.8.20; | ||
|
||
import {IVerifySPV} from "./interfaces/IVerifySPV.sol"; | ||
import {BlockHeader, SPVLib} from "./libraries/SPVLib.sol"; | ||
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; | ||
import {Utils} from "./Utils.sol"; | ||
|
||
contract VerifySPV is IVerifySPV { | ||
using SPVLib for BlockHeader; | ||
using Utils for bytes; | ||
|
||
mapping(bytes32 => BlockHeader) public blockHeaders; | ||
// every difficulty epoch's block hash | ||
// updates for every 2016th block = every 28 epochs | ||
bytes32 public LDEBlockHash; | ||
// epoch is incremented for every block register ,1 epoch = 72 blocks | ||
uint256 public epoch; | ||
|
||
event BlockRegistered(bytes32 blockHash); | ||
|
||
constructor(BlockHeader memory genesisHeader) { | ||
LDEBlockHash = genesisHeader.calculateBlockHash(); | ||
blockHeaders[genesisHeader.calculateBlockHash()] = genesisHeader; | ||
epoch = 0; | ||
} | ||
|
||
function registerBlock(BlockHeader[] calldata newEpoch) public { | ||
require(newEpoch.length == 76, "VerifySPV: invalid epoch, should contain previous 72 blocks and next 3 blocks"); | ||
|
||
require( | ||
blockHeaders[newEpoch[0].calculateBlockHash()].previousBlockHash != bytes32(0x0), | ||
"VerifySPV: invalid epoch, starting block not on chain" | ||
); | ||
|
||
epoch++; | ||
|
||
verifySequence(newEpoch); | ||
|
||
bytes32 newEpochBlockHash = newEpoch[72].calculateBlockHash(); | ||
blockHeaders[newEpochBlockHash] = newEpoch[72]; | ||
|
||
emit BlockRegistered(newEpochBlockHash); | ||
} | ||
|
||
function verifyTxInclusion( | ||
BlockHeader[] calldata blockSequence, | ||
uint256 blockIndex, | ||
uint256 txIndex, | ||
bytes32 txHash, | ||
bytes32[] memory proof | ||
) public view returns (bool) { | ||
require(blockSequence.length == 73, "VerifySPV: inclusion verification needs all 72 blocks in the epoch"); | ||
|
||
require( | ||
blockHeaders[blockSequence[0].calculateBlockHash()].previousBlockHash != bytes32(0x0), | ||
"VerifySPV: invalid epoch, starting block not on chain" | ||
); | ||
|
||
require( | ||
blockHeaders[blockSequence[72].calculateBlockHash()].previousBlockHash != bytes32(0x0), | ||
"VerifySPV: invalid epoch, ending block not on chain" | ||
); | ||
uint256 target = (abi.encodePacked((blockSequence[0].nBits))).convertnBitsToTarget(); | ||
verifySubSequence(blockSequence[0:72], target); | ||
return blockSequence[blockIndex].verifyProof(txHash, txIndex, proof); | ||
} | ||
|
||
function verifySequence(BlockHeader[] calldata blockSequence) internal { | ||
uint256 target = (abi.encodePacked((blockHeaders[LDEBlockHash].nBits))).convertnBitsToTarget(); | ||
if (epoch % 28 == 0) { | ||
require( | ||
verifySubSequence(blockSequence[:72], target), "VerifySPV: pre subsequence in difficulty epoch failed" | ||
); | ||
uint256 adjustedTarget = blockSequence[72].calculateNewTarget(target, blockHeaders[LDEBlockHash].timestamp); | ||
uint256 newTarget = (abi.encodePacked((blockSequence[72].nBits))).convertnBitsToTarget(); | ||
require( | ||
SPVLib.verifyDifficultyEpochTarget(adjustedTarget, newTarget), | ||
"VerifySPV: adjusted difficulty is not in allowed range" | ||
); | ||
require( | ||
blockSequence[71].calculateBlockHash() == blockSequence[72].previousBlockHash | ||
&& blockSequence[72].verifyWork(), | ||
"VerifySPV: difficulty epoch validation failed" | ||
); | ||
require( | ||
verifySubSequence(blockSequence[73:], newTarget), | ||
"VerifySPV: post subsequence in difficulty epoch failed" | ||
); | ||
LDEBlockHash = blockSequence[72].calculateBlockHash(); | ||
} else { | ||
require(verifySubSequence(blockSequence, target), "VerifySPV: sequence verification failed"); | ||
} | ||
} | ||
|
||
function verifySubSequence(BlockHeader[] calldata blockSequence, uint256 target) internal pure returns (bool) { | ||
for (uint256 i = 1; i < blockSequence.length; i++) { | ||
if ( | ||
!( | ||
blockSequence[i - 1].calculateBlockHash() == blockSequence[i].previousBlockHash | ||
&& blockSequence[i].verifyTarget(target) && blockSequence[i].verifyWork() | ||
) | ||
) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// SPDX-License-Identifier: Unlicense | ||
pragma solidity ^0.8.20; | ||
|
||
import {BlockHeader} from "../libraries/SPVLib.sol"; | ||
|
||
interface IVerifySPV { | ||
function registerBlock(BlockHeader[] calldata newEpoch) external; | ||
|
||
function verifyTxInclusion( | ||
BlockHeader[] calldata blockSequence, | ||
uint256 blockIndex, | ||
uint256 txIndex, | ||
bytes32 txHash, | ||
bytes32[] memory proof | ||
) external view returns (bool); | ||
} |
Oops, something went wrong.