Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6fb1f31
added bitcoin libs
aritkulova Aug 4, 2025
19185d8
renamed BitcoinHelper to EndianConverter
aritkulova Aug 5, 2025
17771d4
refactored natspec
aritkulova Aug 5, 2025
7d7d960
review fixes
aritkulova Aug 5, 2025
e5b9907
BtcTxParser -> TxParser
aritkulova Aug 5, 2025
a939321
updated readme
aritkulova Aug 5, 2025
e8c84a6
refactored tests for txParser
aritkulova Aug 6, 2025
de708d4
fixed parseBlockHeader LE numbers
aritkulova Aug 6, 2025
3724dfd
refactored endianConverter
aritkulova Aug 7, 2025
cd6f36f
added bytes to bytes conversion in EndianConverter
aritkulova Aug 7, 2025
7287fb1
cleaned headers data for tests
aritkulova Aug 7, 2025
91ef376
review fixes
aritkulova Aug 7, 2025
b99e7bb
package json fix
aritkulova Aug 7, 2025
94b8b9b
refactored parseCuint in TxParser
aritkulova Aug 7, 2025
1c13e90
refactored naming in EndianConverter
aritkulova Aug 7, 2025
de6acd4
natspec EndianConverter
aritkulova Aug 7, 2025
38bab11
removed convertion function from bytes to uint and vice versa
aritkulova Aug 8, 2025
479a94d
covereage for EndianConverter
aritkulova Aug 8, 2025
0041ac4
refactored _reverseUint128 in EndianConverter
aritkulova Aug 11, 2025
f4e1e02
fixed pragma solidity
aritkulova Aug 11, 2025
39c1655
Update ERC7947 interface (#154)
Hrom131 Aug 11, 2025
a3d217c
typo
aritkulova Aug 11, 2025
f568a05
added check for insertion attack in TxMerkleProof;
aritkulova Aug 12, 2025
a8e3a69
utilized addHexPrefix
aritkulova Aug 12, 2025
d4df626
TxMerkleProof now uses txIndex to calculate hashing directions
aritkulova Aug 13, 2025
f036370
Merge branch 'master' into feat/add-bitcoin-libs
aritkulova Aug 13, 2025
f1c6ee1
fixed typo
aritkulova Aug 13, 2025
d7c2f79
consistency in codestyle
aritkulova Aug 14, 2025
77d3075
updated version to 3.2.0
aritkulova Aug 14, 2025
1a1d703
removed unused test case
aritkulova Aug 14, 2025
0b0f426
removed excess comment
aritkulova Aug 14, 2025
890fa83
fixed natspec
aritkulova Aug 14, 2025
476e442
refactored helpers
aritkulova Aug 14, 2025
1981441
added reference for insertion attack
aritkulova Aug 14, 2025
ebab4af
update readme
Arvolear Aug 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ contracts
│ │ ├── ArrayHelper — "Common functions to work with arrays"
│ │ ├── Paginator — "Return array slices from view function"
│ │ └── SetHelper — "Array abstraction over sets"
│ ├── bitcoin
│ │ ├── BlockHeader — "Parse and format Bitcoin block headers"
│ │ ├── TxMerkleProof — "Verify transaction inclusion in Bitcoin block"
│ │ └── TxParser — "Parse and format Bitcoin transactions"
│ ├── bn
│ │ └── U512 — "A hyperoptimized uint512 implementation"
│ ├── crypto
Expand All @@ -66,6 +70,7 @@ contracts
│ │ └── Vector — "A pushable memory array"
│ ├── utils
│ │ ├── DecimalsConverter — "Simplify interaction with ERC-20 decimals"
│ │ ├── EndianConverter — "Convert between little-endian and big-endian formats"
│ │ ├── MemoryUtils — "Functions for memory manipulation"
│ │ ├── ReturnDataProxy — "Bypass extra returndata copy when returning data"
│ │ └── Typecaster — "Cast between various Solidity types"
Expand Down
25 changes: 13 additions & 12 deletions contracts/account-abstraction/AAccountRecovery.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {IRecoveryProvider} from "../interfaces/account-abstraction/IRecoveryProv
* You may use this module as a base contract for your own account recovery mechanism.
*
* The Account Recovery module allows to add recovery providers to the account.
* The recovery providers are used to recover the account ownership.
* The recovery providers are used to recover the account access.
*
* For more information please refer to [EIP-7947](https://eips.ethereum.org/EIPS/eip-7947).
*/
Expand All @@ -35,20 +35,23 @@ abstract contract AAccountRecovery is IAccountRecovery {
/**
* @inheritdoc IAccountRecovery
*/
function addRecoveryProvider(address provider_, bytes memory recoveryData_) external virtual;
function addRecoveryProvider(
address provider_,
bytes memory recoveryData_
) external payable virtual;

/**
* @inheritdoc IAccountRecovery
*/
function removeRecoveryProvider(address provider_) external virtual;
function removeRecoveryProvider(address provider_) external payable virtual;

/**
* @inheritdoc IAccountRecovery
*/
function recoverOwnership(
address newOwner,
address provider,
bytes memory proof
function recoverAccess(
bytes memory subject_,
address provider_,
bytes memory proof_
) external virtual returns (bool);

/**
Expand Down Expand Up @@ -99,20 +102,18 @@ abstract contract AAccountRecovery is IAccountRecovery {
}

/**
* @notice Should be called in the `recoverOwnership` function before updating the account owner
* @notice Should be called in the `recoverAccess` function before updating the account access
*/
function _validateRecovery(
address newOwner_,
bytes memory object_,
address provider_,
bytes memory proof_
) internal virtual {
if (newOwner_ == address(0)) revert ZeroAddress();

AAccountRecoveryStorage storage $ = _getAAccountRecoveryStorage();

if (!$.recoveryProviders.contains(provider_)) revert ProviderNotRegistered(provider_);

IRecoveryProvider(provider_).recover(newOwner_, proof_);
IRecoveryProvider(provider_).recover(object_, proof_);
}

/**
Expand Down
28 changes: 14 additions & 14 deletions contracts/interfaces/account-abstraction/IAccountRecovery.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,38 @@ pragma solidity ^0.8.21;
* For more information please refer to [EIP-7947](https://eips.ethereum.org/EIPS/eip-7947).
*/
interface IAccountRecovery {
event OwnershipRecovered(address indexed oldOwner, address indexed newOwner);
event AccessRecovered(bytes subject);
event RecoveryProviderAdded(address indexed provider);
event RecoveryProviderRemoved(address indexed provider);

/**
* @notice A function to add a new recovery provider.
* SHOULD be access controlled.
*
* @param provider the address of a recovery provider (ZKP verifier) to add.
* @param recoveryData custom data (commitment) for the recovery provider.
* @param provider_ the address of a recovery provider (ZKP verifier) to add.
* @param recoveryData_ custom data (commitment) for the recovery provider.
*/
function addRecoveryProvider(address provider, bytes memory recoveryData) external;
function addRecoveryProvider(address provider_, bytes memory recoveryData_) external payable;

/**
* @notice A function to remove an existing recovery provider.
* SHOULD be access controlled.
*
* @param provider the address of a previously added recovery provider to remove.
* @param provider_ the address of a previously added recovery provider to remove.
*/
function removeRecoveryProvider(address provider) external;
function removeRecoveryProvider(address provider_) external payable;

/**
* @notice A non-view function to recover ownership of a smart account.
* @param newOwner the address of a new owner.
* @param provider the address of a recovery provider.
* @param proof an encoded proof of recovery (ZKP/ZKAI, signature, etc).
* @notice A non-view function to recover access of a smart account.
* @param subject_ the recovery subject (encoded owner address, access control role, etc).
* @param provider_ the address of a recovery provider.
* @param proof_ an encoded proof of recovery (ZKP/ZKAI, signature, etc).
* @return `true` if recovery is successful, `false` (or revert) otherwise.
*/
function recoverOwnership(
address newOwner,
address provider,
bytes memory proof
function recoverAccess(
bytes memory subject_,
address provider_,
bytes memory proof_
) external returns (bool);

/**
Expand Down
16 changes: 8 additions & 8 deletions contracts/interfaces/account-abstraction/IRecoveryProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,31 @@ interface IRecoveryProvider {
* @notice A function that "subscribes" a smart account (msg.sender) to a recovery provider.
* SHOULD process and assign the `recoveryData` to the `msg.sender`.
*
* @param recoveryData a recovery commitment (hash/ZKP public output) to be used
* @param recoveryData_ a recovery commitment (hash/ZKP public output) to be used
* in the `recover` function to check a recovery proof validity.
*/
function subscribe(bytes memory recoveryData) external;
function subscribe(bytes memory recoveryData_) external payable;

/**
* @notice A function that revokes a smart account subscription.
*/
function unsubscribe() external;
function unsubscribe() external payable;

/**
* @notice A function that checks if a recovery of a smart account (msg.sender)
* to the `newOwner` is possible.
* SHOULD use `msg.sender`'s `recoveryData` to check the `proof` validity.
*
* @param newOwner the new owner to recover the `msg.sender` ownership to.
* @param proof the recovery proof.
* @param object_ the new object (may be different to subject) to recover the `msg.sender` access to.
* @param proof_ the recovery proof.
*/
function recover(address newOwner, bytes memory proof) external;
function recover(bytes memory object_, bytes memory proof_) external;

/**
* @notice A function to get a recovery data (commitment) of an account.
*
* @param account the account to get the recovery data of.
* @param account_ the account to get the recovery data of.
* @return the associated recovery data.
*/
function getRecoveryData(address account) external view returns (bytes memory);
function getRecoveryData(address account_) external view returns (bytes memory);
}
124 changes: 124 additions & 0 deletions contracts/libs/bitcoin/BlockHeader.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {EndianConverter} from "../utils/EndianConverter.sol";

/**
* @notice A utility library for handling Bitcoin block headers.
* Provides functions for parsing, hashing, and converting block header data
*/
library BlockHeader {
using EndianConverter for *;

/**
* @notice The standard length of a Bitcoin block header in bytes
*/
uint256 public constant BLOCK_HEADER_DATA_LENGTH = 80;

/**
* @notice Represents the essential data contained within a Bitcoin block header
* @param prevBlockHash The hash of the previous block
* @param merkleRoot The Merkle root of the transactions in the block
* @param version The block version number
* @param time The block's timestamp
* @param nonce The nonce used for mining
* @param bits The encoded difficulty target for the block
*/
struct HeaderData {
bytes32 prevBlockHash;
bytes32 merkleRoot;
uint32 version;
uint32 time;
uint32 nonce;
bytes4 bits;
}

/**
* @notice Emitted when the provided block header data has an invalid length.
* This error ensures that only correctly sized block headers are processed
*/
error InvalidBlockHeaderDataLength();

/**
* @notice Parses a raw byte array into a structured `HeaderData` and calculates its hash.
* It validates the length of the input and correctly decodes each field
* @param blockHeaderRaw_ The raw bytes of the block header
* @param returnInBEFormat_ Whether to return the hashes in big-endian encoding
* @return headerData_ The parsed `HeaderData` structure
* @return blockHash_ The calculated hash of the block header
*/
function parseBlockHeader(
bytes calldata blockHeaderRaw_,
bool returnInBEFormat_
) internal pure returns (HeaderData memory headerData_, bytes32 blockHash_) {
if (blockHeaderRaw_.length != BLOCK_HEADER_DATA_LENGTH)
revert InvalidBlockHeaderDataLength();

headerData_ = HeaderData({
version: uint32(bytes4(blockHeaderRaw_[0:4])),
prevBlockHash: bytes32(blockHeaderRaw_[4:36]),
merkleRoot: bytes32(blockHeaderRaw_[36:68]),
time: uint32(bytes4(blockHeaderRaw_[68:72])),
bits: bytes4(blockHeaderRaw_[72:76]),
nonce: uint32(bytes4(blockHeaderRaw_[76:80]))
});

blockHash_ = _getBlockHeaderHash(blockHeaderRaw_);

if (returnInBEFormat_) {
headerData_.version = headerData_.version.uint32LEtoBE();
headerData_.prevBlockHash = headerData_.prevBlockHash.bytes32LEtoBE();
headerData_.merkleRoot = headerData_.merkleRoot.bytes32LEtoBE();
headerData_.time = headerData_.time.uint32LEtoBE();
headerData_.bits = bytes4(uint32(headerData_.bits).uint32LEtoBE());
headerData_.nonce = headerData_.nonce.uint32LEtoBE();

blockHash_ = blockHash_.bytes32LEtoBE();
}
}

/**
* @notice Converts a `HeaderData` structure back into its raw byte representation.
* This function reconstructs the original byte sequence of the block header
* @param headerData_ The `HeaderData` structure to convert
* @param inputInBEFormat_ Whether headerData_ is expected to be in big-endian encoding
* @return The raw byte representation of the block header
*/
function toRawBytes(
HeaderData memory headerData_,
bool inputInBEFormat_
) internal pure returns (bytes memory) {
if (inputInBEFormat_) {
return
abi.encodePacked(
headerData_.version.uint32BEtoLE(),
headerData_.prevBlockHash.bytes32BEtoLE(),
headerData_.merkleRoot.bytes32BEtoLE(),
headerData_.time.uint32BEtoLE(),
(uint32(headerData_.bits)).uint32BEtoLE(),
headerData_.nonce.uint32BEtoLE()
);
}

return
abi.encodePacked(
headerData_.version,
headerData_.prevBlockHash,
headerData_.merkleRoot,
headerData_.time,
headerData_.bits,
headerData_.nonce
);
}

/**
* @notice Calculates the double SHA256 hash of a raw block header.
* This is the standard method for deriving a Bitcoin block hash
* @dev Returns block hash in little-endian encoding
*/
function _getBlockHeaderHash(bytes calldata blockHeaderRaw_) private pure returns (bytes32) {
bytes32 rawBlockHash_ = sha256(abi.encode(sha256(blockHeaderRaw_)));

return rawBlockHash_;
}
}
80 changes: 80 additions & 0 deletions contracts/libs/bitcoin/TxMerkleProof.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

import {TxParser} from "./TxParser.sol";

/**
* @notice A library for verifying transaction inclusion in Bitcoin block.
* Provides functions for processing and verifying Merkle tree proofs
*/
library TxMerkleProof {
using TxParser for bytes;

/**
* @notice Emitted when the concateneted hashes of the Merkle tree is a valid Bitcoin transaction.
* This error ensures that insertion attack is not possible
*/
error InvalidMerkleNode();

/**
* @notice Returns true if `leaf_` can be proven to be part of a Merkle tree
* defined by `root_`. Uses double SHA-256 hashing
* @param proof_ The array of sibling hashes from the leaf to the root
* @param root_ Merkle root in little-endian format
* @param leaf_ Element that need to be proven included in a tree
* @param txIndex_ The transaction index in the block, indicating hashing order for each pair
* @return Whether the leaf is the part of a Merkle tree
*/
function verify(
bytes32[] memory proof_,
bytes32 root_,
bytes32 leaf_,
uint256 txIndex_
) internal pure returns (bool) {
return processProof(proof_, leaf_, txIndex_) == root_;
}

/**
* @notice Returns the rebuilt hash obtained by traversing the Merkle tree
* from `leaf_` using `proof_`. A `proof_` is valid if and only if the rebuilt
* hash matches the given tree root. The pre-images are hashed in the order
* calculated by the `txIndex_` position. Uses double SHA-256 hashing
* @param proof_ The array of sibling hashes from the leaf to the root
* @param leaf_ The leaf of the Merkle tree
* @param txIndex_ The transaction index in the block, indicating hashing order for each pair
* @return The computed Merkle root
*/
function processProof(
bytes32[] memory proof_,
bytes32 leaf_,
uint256 txIndex_
) internal pure returns (bytes32) {
bytes32 computedHash_ = leaf_;
uint256 proofLength_ = proof_.length;
bytes memory pair_;

for (uint256 i = 0; i < proofLength_; ++i) {
pair_ = txIndex_ & 1 == 0
? abi.encodePacked(computedHash_, proof_[i])
: abi.encodePacked(proof_[i], computedHash_);

/*
* Check that the node is not a transaction to mitigate an insertion attack.
* More info at https://nvd.nist.gov/vuln/detail/CVE-2017-12842
*/
if (pair_.isTransaction()) revert InvalidMerkleNode();

computedHash_ = _doubleSHA256(pair_);
txIndex_ >>= 1;
}

return computedHash_;
}

/**
* @notice Double sha256 hashing
*/
function _doubleSHA256(bytes memory data_) private pure returns (bytes32) {
return sha256(abi.encodePacked(sha256(data_)));
}
}
Loading