Skip to content

Commit b5da2aa

Browse files
authored
added witness removal to txid calculation; (#168)
separated txid and wtxid calculations
1 parent 9ba834e commit b5da2aa

4 files changed

Lines changed: 53 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## [patch]
4+
5+
- Separated txid and wtxid calculations in `TxParser` library.
6+
37
## [3.2.4]
48

59
- Added `ARecoverableAccount` contract, an all-in-one EIP-7702/ERC-4337 account with ERC-7821 batching, ERC-4337 gas sponsorship, and ERC-7947 recovery.

contracts/libs/bitcoin/TxParser.sol

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,15 @@ library TxParser {
4444
* @return The transaction ID
4545
*/
4646
function calculateTxId(bytes calldata data_) internal pure returns (bytes32) {
47+
return _doubleSHA256(_removeWitness(data_));
48+
}
49+
50+
/**
51+
* @notice Calculate witness transaction ID (hash with witness data)
52+
* @param data_ The raw transaction data
53+
* @return The witness transaction ID
54+
*/
55+
function calculateWTxId(bytes calldata data_) internal pure returns (bytes32) {
4756
return _doubleSHA256(data_);
4857
}
4958

@@ -157,7 +166,7 @@ library TxParser {
157166
* @return The formatted transaction bytes
158167
*/
159168
function formatTransaction(
160-
Transaction calldata tx_,
169+
Transaction memory tx_,
161170
bool withWitness_
162171
) internal pure returns (bytes memory) {
163172
bool includeWitness_ = withWitness_ && tx_.hasWitness;
@@ -439,7 +448,7 @@ library TxParser {
439448
* @notice Format a transaction input into raw bytes
440449
*/
441450
function _formatTransactionInput(
442-
TransactionInput calldata input_
451+
TransactionInput memory input_
443452
) private pure returns (bytes memory) {
444453
bytes memory prevHash_ = abi.encodePacked((input_.previousHash).bytes32BEtoLE());
445454
bytes memory previousIndex_ = abi.encodePacked(input_.previousIndex.uint32BEtoLE());
@@ -459,14 +468,30 @@ library TxParser {
459468
* @notice Format a transaction output into raw bytes
460469
*/
461470
function _formatTransactionOutput(
462-
TransactionOutput calldata output_
471+
TransactionOutput memory output_
463472
) private pure returns (bytes memory) {
464473
bytes memory value_ = abi.encodePacked(output_.value.uint64BEtoLE());
465474

466475
return
467476
abi.encodePacked(value_, formatCuint(uint64(output_.script.length)), output_.script);
468477
}
469478

479+
/**
480+
* @notice Remove witness from a raw transaction
481+
*/
482+
function _removeWitness(bytes calldata data_) private pure returns (bytes memory) {
483+
(TxParser.Transaction memory tx_, ) = parseTransaction(data_);
484+
485+
if (tx_.hasWitness) {
486+
return formatTransaction(tx_, false);
487+
}
488+
489+
return data_;
490+
}
491+
492+
/**
493+
* @notice Parse a compact unsigned integer (Bitcoin's variable length encoding)
494+
*/
470495
function _parseCuint(
471496
bytes memory data_,
472497
uint256 offset_

contracts/mock/libs/bitcoin/TxParserMock.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ contract TxParserMock {
1010
return data_.calculateTxId();
1111
}
1212

13+
function calculateWTxId(bytes calldata data_) external pure returns (bytes32) {
14+
return data_.calculateWTxId();
15+
}
16+
1317
function parseBTCTransaction(
1418
bytes calldata txBytes_
1519
) external pure returns (TxParser.Transaction memory tx_) {

test/libs/bitcoin/TxParser.test.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,27 @@ describe("Transaction Parser", () => {
4444

4545
describe("#calculateTxId", () => {
4646
it("should calculate correctly", async () => {
47-
const rawTx =
47+
const rawTxWithoutWitness =
4848
"0x01000000013652ebc8c4efec4015c4c2e6f7f693bf5307bcc68f59510957e302b89208eb0c060000006a47304402202955e3df921fa6893b898db5117ec92441757d90b485ed3015d427a1aa7631a6022063859ae9fb657b3ceb28c1771076da62e967b3872db6599a08ac982f62ef67810121037a35df3a9314039a2361bd37df3ee846c7a259a7c83f522704517bb7e8748f1effffffff0138ed000000000000160014b9edb07641abc03085f3971e48c6cafbdc854baf00000000";
4949

50-
const expectedTxid = reverseBytes("0x16a7875987d0be57af2283de4c38f0f4b1ad9c65b936cbc36e101665d8dff891");
51-
const txid = await parser.calculateTxId(rawTx);
50+
let expectedTxid = reverseBytes("0x16a7875987d0be57af2283de4c38f0f4b1ad9c65b936cbc36e101665d8dff891");
51+
let txid = await parser.calculateTxId(rawTxWithoutWitness);
52+
let wtxid = await parser.calculateWTxId(rawTxWithoutWitness);
5253

5354
expect(txid).to.be.eq(expectedTxid);
55+
expect(wtxid).to.be.eq(expectedTxid);
56+
57+
const rawTxWithWitness =
58+
"0x02000000000101bb9bf2f5ad81afc67adacddcdb8c23aa357a7f471a6e3902a48d16b7fb69681b0000000000ffffffff02a896010000000000160014d9f6bfe50ad0a73647031d933e05fab69b3752b9dfec1c0000000000160014b9ab9ccd954d138c3f3aae3ec91070f4c9dd966f040047304402203e6e965c7bbced9bcd286c7ce37a27de012d3d9dc691ad4c77fff48191ae4bbe022077a0c132f28c4d2d691654acd28a3035fa8e7a737007ebcd67056e50727a1c0801473044022058daa76ae784f860aae0433fe9926831643284840fb6774f43ad72ee11c6aadb0220331c74132820b15fac6ff2a88789a3325cee83d68ccdec9b38dea2a8b5462eaf01475221024c48ca1fb71fac84400643b0e921ece39abdeea5026d9e01d8281bad9cddfaf92103674b3be7da8a4fdb363fd471a6bf507c38f39962322370ce7338c0719d06df7252ae00000000";
59+
60+
expectedTxid = reverseBytes("a3b23982331300ef8d23bd90b64e939758835515642a65ba64234bf2cae2ac7a");
61+
const expectedWTxid = reverseBytes("72d691c88f88df22874bd380d2b46040ff632ab076c4db14441e5f7c1b82c542");
62+
63+
txid = await parser.calculateTxId(rawTxWithWitness);
64+
wtxid = await parser.calculateWTxId(rawTxWithWitness);
65+
66+
expect(txid).to.be.eq(expectedTxid);
67+
expect(wtxid).to.be.eq(expectedWTxid);
5468
});
5569
});
5670

0 commit comments

Comments
 (0)