Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Qa test -> Stable-Test #43

Open
wants to merge 18 commits into
base: Stable-Test
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1

- name: Use Node.js 20.12.0
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
- name: 'Dependency Review'
uses: actions/dependency-review-action@72eb03d02c7872a771aacd928f3123ac62ad6d3a # v4.3.3
8 changes: 4 additions & 4 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,25 @@ jobs:

steps:
- name: "Checkout code"
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1
with:
persist-credentials: false

- name: "Run analysis"
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
with:
results_file: results.sarif
results_format: sarif
publish_results: true

- name: "Upload artifact"
uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
with:
name: SARIF file
path: results.sarif
retention-days: 5

- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.24.9
uses: github/codeql-action/upload-sarif@461ef6c76dfe95d5c364de2f431ddbd31a417628 # v3.26.9
with:
sarif_file: results.sarif
4 changes: 2 additions & 2 deletions .github/workflows/slither.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
permissions:
security-events: write
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1

- name: Run Slither
uses: crytic/slither-action@6ef3a33e56de4e8f59488cf60858b5c1bf4967c0 # v0.3.0
Expand All @@ -21,6 +21,6 @@ jobs:
target: contracts/

- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@85b07cf1e13dd512be7c27c37a33c5864c252fcc # v2
uses: github/codeql-action/upload-sarif@461ef6c76dfe95d5c364de2f431ddbd31a417628 # v3.26.9
with:
sarif_file: ${{ steps.slither.outputs.sarif }}
149 changes: 138 additions & 11 deletions contracts/BtcUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,26 @@ library BtcUtils {
uint8 private constant MAX_COMPACT_SIZE_LENGTH = 252;
uint8 private constant MAX_BYTES_USED_FOR_COMPACT_SIZE = 8;

uint private constant HASH160_SIZE = 20;
uint private constant SHA256_SIZE = 32;
uint private constant TAPROOT_PUBKEY_SIZE = 32;

uint8 private constant OUTPOINT_SIZE = 36;
uint8 private constant OUTPUT_VALUE_SIZE = 8;

uint8 private constant PUBKEY_HASH_SIZE = 20;
uint8 private constant PUBKEY_HASH_START = 3;
bytes1 private constant PUBKEY_HASH_MAINNET_BYTE = 0x00;
bytes1 private constant PUBKEY_HASH_TESTNET_BYTE = 0x6f;

uint8 private constant SCRIPT_HASH_SIZE = 20;
uint8 private constant SCRIPT_HASH_START = 2;
bytes1 private constant SCRIPT_HASH_MAINNET_BYTE = 0x05;
bytes1 private constant SCRIPT_HASH_TESTNET_BYTE = 0xc4;

uint private constant BECH32_WORD_SIZE = 5;
uint private constant BYTE_SIZE = 8;

bytes1 private constant WITNESS_VERSION_0 = 0x00;
bytes1 private constant WITNESS_VERSION_1 = 0x01;


/**
Expand All @@ -39,6 +46,10 @@ library BtcUtils {
uint256 totalSize;
}

function version() external pure returns (string memory) {
return "0.2.1";
}

/// @notice Parse a raw transaction to get an array of its outputs in a structured representation
/// @param rawTx the raw transaction
/// @return An array of `TxRawOutput` with the outputs of the transaction
Expand Down Expand Up @@ -103,18 +114,26 @@ library BtcUtils {
if (isP2SHOutput(outputScript)) {
return parsePayToScriptHash(outputScript, mainnet);
}
// TODO add here P2WPKH, P2WSH and P2TR
if (isP2WPKHOutput(outputScript)) {
return parsePayToWitnessPubKeyHash(outputScript);
}
if (isP2WSHOutput(outputScript)) {
return parsePayToWitnessScriptHash(outputScript);
}
if (isP2TROutput(outputScript)) {
return parsePayToTaproot(outputScript);
}
revert("Unsupported script type");
}

/// @notice Check if a raw output script is a pay-to-public-key-hash output
/// @param pkScript the fragment of the raw transaction containing the raw output script
/// @return Whether the script has a pay-to-public-key-hash output structure or not
function isP2PKHOutput(bytes memory pkScript) public pure returns (bool) {
return pkScript.length == 25 &&
return pkScript.length == 5 + HASH160_SIZE &&
pkScript[0] == OpCodes.OP_DUP &&
pkScript[1] == OpCodes.OP_HASH160 &&
uint8(pkScript[2]) == PUBKEY_HASH_SIZE &&
uint8(pkScript[2]) == HASH160_SIZE &&
pkScript[23] == OpCodes.OP_EQUALVERIFY &&
pkScript[24] == OpCodes.OP_CHECKSIG;
}
Expand All @@ -123,12 +142,40 @@ library BtcUtils {
/// @param pkScript the fragment of the raw transaction containing the raw output script
/// @return Whether the script has a pay-to-script-hash output structure or not
function isP2SHOutput(bytes memory pkScript) public pure returns (bool) {
return pkScript.length == 23 &&
return pkScript.length == 3 + HASH160_SIZE &&
pkScript[0] == OpCodes.OP_HASH160 &&
uint8(pkScript[1]) == SCRIPT_HASH_SIZE &&
uint8(pkScript[1]) == HASH160_SIZE &&
pkScript[22] == OpCodes.OP_EQUAL;
}

/// @notice Check if a raw output script is a pay-to-witness-pubkey-hash output
/// @param pkScript the fragment of the raw transaction containing the raw output script
/// @return Whether the script has a pay-to-witness-pubkey-hash output structure or not
function isP2WPKHOutput(bytes memory pkScript) public pure returns (bool) {
return pkScript.length == 2 + HASH160_SIZE &&
pkScript[0] == OpCodes.OP_0 &&
uint8(pkScript[1]) == HASH160_SIZE;
}

/// @notice Check if a raw output script is a pay-to-witness-script-hash output
/// @param pkScript the fragment of the raw transaction containing the raw output script
/// @return Whether the script has a pay-to-witness-script-hash output structure or not
function isP2WSHOutput(bytes memory pkScript) public pure returns (bool) {
return pkScript.length == 2 + SHA256_SIZE &&
pkScript[0] == OpCodes.OP_0 &&
uint8(pkScript[1]) == SHA256_SIZE;
}

/// @notice Check if a raw output script is a pay-to-taproot output
/// @notice Reference for implementation: https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki
/// @param pkScript the fragment of the raw transaction containing the raw output script
/// @return Whether the script has a pay-to-taproot output structure or not
function isP2TROutput(bytes memory pkScript) public pure returns (bool) {
return pkScript.length == 2 + TAPROOT_PUBKEY_SIZE &&
pkScript[0] == OpCodes.OP_1 &&
uint8(pkScript[1]) == TAPROOT_PUBKEY_SIZE;
}

/// @notice Parse a raw pay-to-public-key-hash output script to get the corresponding address,
/// the resulting byte array doesn't include the checksum bytes of the base58check encoding at
/// the end
Expand All @@ -138,8 +185,8 @@ library BtcUtils {
function parsePayToPubKeyHash(bytes calldata outputScript, bool mainnet) public pure returns (bytes memory) {
require(isP2PKHOutput(outputScript), "Script hasn't the required structure");

bytes memory destinationAddress = new bytes(PUBKEY_HASH_SIZE + 1);
for(uint8 i = PUBKEY_HASH_START; i < PUBKEY_HASH_SIZE + PUBKEY_HASH_START; i++) {
bytes memory destinationAddress = new bytes(HASH160_SIZE + 1);
for(uint8 i = PUBKEY_HASH_START; i < HASH160_SIZE + PUBKEY_HASH_START; i++) {
destinationAddress[i - PUBKEY_HASH_START + 1] = outputScript[i];
}

Expand All @@ -156,15 +203,63 @@ library BtcUtils {
function parsePayToScriptHash(bytes calldata outputScript, bool mainnet) public pure returns (bytes memory) {
require(isP2SHOutput(outputScript), "Script hasn't the required structure");

bytes memory destinationAddress = new bytes(SCRIPT_HASH_SIZE + 1);
for(uint8 i = SCRIPT_HASH_START; i < SCRIPT_HASH_SIZE + SCRIPT_HASH_START; i++) {
bytes memory destinationAddress = new bytes(HASH160_SIZE + 1);
for(uint8 i = SCRIPT_HASH_START; i < HASH160_SIZE + SCRIPT_HASH_START; i++) {
destinationAddress[i - SCRIPT_HASH_START + 1] = outputScript[i];
}

destinationAddress[0] = mainnet? SCRIPT_HASH_MAINNET_BYTE : SCRIPT_HASH_TESTNET_BYTE;
return destinationAddress;
}

/// @notice Parse a raw pay-to-witness-pubkey-hash output script to get the corresponding address words,
/// the resulting words are only the data part of the bech32 encoding and doesn't include the HRP
/// @param outputScript the fragment of the raw transaction containing the raw output script
/// @return The address bech32 words generated using the pubkey hash
function parsePayToWitnessPubKeyHash(bytes calldata outputScript) public pure returns (bytes memory) {
require(isP2WPKHOutput(outputScript), "Script hasn't the required structure");
uint length = 1 + total5BitWords(HASH160_SIZE);
bytes memory result = new bytes(length);
result[0] = WITNESS_VERSION_0;
bytes memory words = to5BitWords(outputScript[2:]);
for (uint i = 1; i < length; i++) {
result[i] = words[i - 1];
}
return result;
}

/// @notice Parse a raw pay-to-witness-script-hash output script to get the corresponding address words,
/// the resulting words are only the data part of the bech32 encoding and doesn't include the HRP
/// @param outputScript the fragment of the raw transaction containing the raw output script
/// @return The address bech32 words generated using the script hash
function parsePayToWitnessScriptHash(bytes calldata outputScript) public pure returns (bytes memory) {
require(isP2WSHOutput(outputScript), "Script hasn't the required structure");
uint length = 1 + total5BitWords(SHA256_SIZE);
bytes memory result = new bytes(length);
result[0] = WITNESS_VERSION_0;
bytes memory words = to5BitWords(outputScript[2:]);
for (uint i = 1; i < length; i++) {
result[i] = words[i - 1];
}
return result;
}

/// @notice Parse a raw pay-to-taproot output script to get the corresponding address words,
/// the resulting words are only the data part of the bech32m encoding and doesn't include the HRP
/// @param outputScript the fragment of the raw transaction containing the raw output script
/// @return The address bech32m words generated using the taproot pubkey hash
function parsePayToTaproot(bytes calldata outputScript) public pure returns (bytes memory) {
require(isP2TROutput(outputScript), "Script hasn't the required structure");
uint length = 1 + total5BitWords(TAPROOT_PUBKEY_SIZE);
bytes memory result = new bytes(length);
result[0] = WITNESS_VERSION_1;
bytes memory words = to5BitWords(outputScript[2:]);
for (uint i = 1; i < length; i++) {
result[i] = words[i - 1];
}
return result;
}

/// @notice Parse a raw null-data output script to get its content
/// @param outputScript the fragment of the raw transaction containing the raw output script
/// @return The content embedded inside the script
Expand Down Expand Up @@ -271,4 +366,36 @@ library BtcUtils {
}
return result;
}

/// @notice Referece for implementation: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
function to5BitWords(bytes memory byteArray) private pure returns(bytes memory) {
uint8 MAX_VALUE = 31;

uint currentValue = 0;
uint bitCount = 0;
uint8 resultIndex = 0;
bytes memory result = new bytes(total5BitWords(byteArray.length));

for (uint i = 0; i < byteArray.length; ++i) {
currentValue = (currentValue << BYTE_SIZE) | uint8(byteArray[i]);
bitCount += BYTE_SIZE;
while (bitCount >= BECH32_WORD_SIZE) {
bitCount -= BECH32_WORD_SIZE;
// this mask ensures that the result will always have 5 bits
result[resultIndex] = bytes1(uint8((currentValue >> bitCount) & MAX_VALUE));
resultIndex++;
}
}

if (bitCount > 0) {
result[resultIndex] = bytes1(uint8((currentValue << (BECH32_WORD_SIZE - bitCount)) & MAX_VALUE));
}
return result;
}

function total5BitWords(uint numberOfBytes) private pure returns(uint) {
uint total = (numberOfBytes * BYTE_SIZE) / BECH32_WORD_SIZE;
bool extraWord = (numberOfBytes * BYTE_SIZE) % BECH32_WORD_SIZE == 0;
return total + (extraWord? 0 : 1);
}
}
3 changes: 3 additions & 0 deletions contracts/OpCodes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ library OpCodes {
bytes1 public constant OP_CHECKSIG = 0xac;
bytes1 public constant OP_RETURN = 0x6a;
bytes1 public constant OP_EQUAL = 0x87;

bytes1 public constant OP_0 = 0x00;
bytes1 public constant OP_1 = 0x51;
}
Loading