Skip to content

Commit

Permalink
Merge pull request #5 from bob-collective/feat/witness
Browse files Browse the repository at this point in the history
feat: add witness parsing utils
  • Loading branch information
gregdhill authored Feb 12, 2024
2 parents 98264ce + c1d1168 commit 8f37525
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 13 deletions.
133 changes: 122 additions & 11 deletions src/SegWitUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,150 @@ library SegWitUtils {

bytes6 public constant WITNESS_MAGIC_BYTES = hex"6a24aa21a9ed";
uint256 public constant COINBASE_WITNESS_PK_SCRIPT_LENGTH = 38;
bytes1 public constant TAPROOT_ANNEX_PREFIX = 0x50;

function isWitnessCommitment(bytes memory pkScript) internal pure returns (bool) {
return pkScript.length >= COINBASE_WITNESS_PK_SCRIPT_LENGTH && bytes6(pkScript.slice32(0)) == WITNESS_MAGIC_BYTES;
function isWitnessCommitment(
bytes memory pkScript
) internal pure returns (bool) {
return
pkScript.length >= COINBASE_WITNESS_PK_SCRIPT_LENGTH &&
bytes6(pkScript.slice32(0)) == WITNESS_MAGIC_BYTES;
}

// https://github.com/btcsuite/btcd/blob/80f5a0ffdf363cfff27d550f9e38aa262667a7f1/blockchain/merkle.go#L192
function extractWitnessCommitment(bytes memory _vout) internal pure returns (bytes32) {
function extractWitnessCommitment(
bytes memory _vout
) internal pure returns (bytes32) {
uint256 _varIntDataLen;
uint256 _nOuts;

(_varIntDataLen, _nOuts) = _vout.parseVarInt();
require(_varIntDataLen != BTCUtils.ERR_BAD_ARG, "Read overrun during VarInt parsing");
require(
_varIntDataLen != BTCUtils.ERR_BAD_ARG,
"Read overrun during VarInt parsing"
);

uint256 _len = 0;
uint256 _offset = 1 + _varIntDataLen;

for (uint256 _i = 0; _i < _nOuts; _i ++) {
for (uint256 _i = 0; _i < _nOuts; _i++) {
_len = _vout.determineOutputLengthAt(_offset);
require(_len != BTCUtils.ERR_BAD_ARG, "Bad VarInt in scriptPubkey");
bytes memory _output = _vout.slice(_offset, _len);
if (_output[8] == hex"26") {
// skip 8-byte value
bytes memory _pkScript = _output.slice(9, COINBASE_WITNESS_PK_SCRIPT_LENGTH);
bytes memory _pkScript = _output.slice(
9,
COINBASE_WITNESS_PK_SCRIPT_LENGTH
);
if (isWitnessCommitment(_pkScript)) {
return bytes32(_pkScript.slice(
WITNESS_MAGIC_BYTES.length,
COINBASE_WITNESS_PK_SCRIPT_LENGTH - WITNESS_MAGIC_BYTES.length
));
return
bytes32(
_pkScript.slice(
WITNESS_MAGIC_BYTES.length,
COINBASE_WITNESS_PK_SCRIPT_LENGTH -
WITNESS_MAGIC_BYTES.length
)
);
}
}
_offset += _len;
}

return hex"";
}
}

/// @notice Determines the length of a witness from its elements,
/// starting at the specified position
/// @param _witness The byte array containing the witness
/// @param _at The position of the witness in the array
/// @return The length of the witness in bytes
function determineWitnessLengthAt(
bytes memory _witness,
uint256 _at
) internal pure returns (uint256) {
uint256 _varIntDataLen;
uint256 _nWit;

(_varIntDataLen, _nWit) = _witness.parseVarIntAt(_at);
require(
_varIntDataLen != BTCUtils.ERR_BAD_ARG,
"Read overrun during VarInt parsing"
);

uint256 _len = 0;
uint256 _offset = 1 + _varIntDataLen;

for (uint256 _i = 0; _i < _nWit; _i++) {
(_varIntDataLen, _len) = _witness.parseVarIntAt(_at + _offset);
require(_len != BTCUtils.ERR_BAD_ARG, "Bad VarInt in witness");
_offset = _offset + 1 + _varIntDataLen + _len;
}

return _offset;
}

/// @notice Extracts the nth witness from the witness vector (0-indexed)
/// @dev Iterates over the witness vector. If you need to extract several, write a custom function
/// @param _witnessVector The witness as a tightly-packed byte array
/// @param _index The 0-indexed location of the witness to extract
/// @return The witness vector as a byte array
function extractWitnessAtIndex(
bytes memory _witnessVector,
uint256 _index
) internal pure returns (bytes memory) {
uint256 _len = 0;
uint256 _offset = 0;

// NOTE: there is no VarInt preceeding the witness vector
// if you want to know the number of elements check the input
for (uint256 _i = 0; _i < _index; _i++) {
_len = determineWitnessLengthAt(_witnessVector, _offset);
require(_len != BTCUtils.ERR_BAD_ARG, "Bad VarInt in witness");
_offset = _offset + _len;
}

_len = determineWitnessLengthAt(_witnessVector, _offset);
require(_len != BTCUtils.ERR_BAD_ARG, "Bad VarInt in scriptSig");
return _witnessVector.slice(_offset, _len);
}

// https://github.com/rust-bitcoin/rust-bitcoin/blob/8aa5501827a0dd5b27abf304a5f9bdefb07a2cc6/bitcoin/src/blockdata/witness.rs#L386-L406
function extractTapscript(
bytes memory _witness
) internal pure returns (bytes memory) {
uint256 _varIntDataLen;
uint256 _nWit;

(_varIntDataLen, _nWit) = _witness.parseVarInt();
require(
_varIntDataLen != BTCUtils.ERR_BAD_ARG,
"Read overrun during VarInt parsing"
);

uint256[] memory _offsets = new uint256[](_nWit);

uint256 _len = 0;
uint256 _offset = 1 + _varIntDataLen;

for (uint256 _i = 0; _i < _nWit; _i++) {
_offsets[_i] = _offset;
(_varIntDataLen, _len) = _witness.parseVarIntAt(_offset);
require(_len != BTCUtils.ERR_BAD_ARG, "Bad VarInt in witness");
_offset = _offset + 1 + _varIntDataLen + _len;
}

uint256 scriptPosFromLast = 2;
if (
_nWit >= 2 && _witness[_offsets[_nWit - 1]] == TAPROOT_ANNEX_PREFIX
) {
scriptPosFromLast = 3;
}

_offset = _offsets[_nWit - scriptPosFromLast];
(, _len) = _witness.parseVarIntAt(_offset);
require(_len != BTCUtils.ERR_BAD_ARG, "Bad VarInt in witness");

return _witness.slice(_offset, _len);
}
}
32 changes: 30 additions & 2 deletions test/SegWitUtils.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,42 @@ contract SegWitUtilsTest is Test {
using SegWitUtils for bytes;

function test_IsWitnessTxOut() public {
bytes memory pkScript = hex"6a24aa21a9edaf8dcb9588f94a3adb462e80f1306d96ef6ffad72160b33cd5e90045d81e0d77";
bytes
memory pkScript = hex"6a24aa21a9edaf8dcb9588f94a3adb462e80f1306d96ef6ffad72160b33cd5e90045d81e0d77";
assert(pkScript.isWitnessCommitment());
}

function test_ExtractWitnessCommitmentFromTxOut() public {
bytes memory outputVector = hex"020593cb260000000016001435f6de260c9f3bdee47524c473a6016c0c055cb90000000000000000266a24aa21a9edaf8dcb9588f94a3adb462e80f1306d96ef6ffad72160b33cd5e90045d81e0d77";
bytes
memory outputVector = hex"020593cb260000000016001435f6de260c9f3bdee47524c473a6016c0c055cb90000000000000000266a24aa21a9edaf8dcb9588f94a3adb462e80f1306d96ef6ffad72160b33cd5e90045d81e0d77";
bytes32 witnessCommitment = outputVector.extractWitnessCommitment();
bytes32 coinbaseWitnessCommitment = hex"af8dcb9588f94a3adb462e80f1306d96ef6ffad72160b33cd5e90045d81e0d77";
assertEq(coinbaseWitnessCommitment, witnessCommitment);
}

// 120c1b297faedd14568b7f02cc15725864d0ec2a9d6a42af764c03827ccc1c72
function test_ExtractWitnessAtIndex() public {
bytes
memory witnessVector = hex"0247304402203c643747293b2f1e3f42253e8307147fa46086f065bdaddb7540c0862e7133c2022031e9cc284d643ae1c0fc139dad929136edc7bb3454512f11a0cbc4117240e604012103608b21667ba574f0177cd505c7b33c657b0fdb62ebb913fdd537a5358a22c6ec0247304402203cbccb97dc9eac21436325d551960ae41a55f00edcb0a39e1099ecf8ab3cbf9f02200f04f061eb50de21ea0ef98ad6ba913c3ee43306f9e2d2ce57576a6beaa2e8c0012103d2296e51b33a43425c5c905afee8997a5fbfdaa8c10add4e56ea8bc5ceea2fcf";
assertEq(
keccak256(witnessVector.extractWitnessAtIndex(0)),
0x42d6cb8ef768d22410c78498a0370193b833b87da6985435f6ba299fa84f9ab7
);
assertEq(
keccak256(witnessVector.extractWitnessAtIndex(1)),
0xf43ff1046b987727b344b2646dc121f22e6b5b6e71b5c528c377cbbb093ce687
);
}

function test_ExtractTapscript() public {
bytes
memory witnessVector = hex"03406c00eb3c4d35fedd257051333b4ca81d1a25a37a9af4891f1fec2869edd56b14180eafbda8851d63138a724c9b15384bc5f0536de658bd294d426a36212e6f08a5209e2849b90a2353691fccedd467215c88eec89a5d0dcf468e6cf37abed344d746ac0063036f7264010118746578742f706c61696e3b636861727365743d7574662d38004c5e7b200a20202270223a20226272632d3230222c0a2020226f70223a20226465706c6f79222c0a2020227469636b223a20226f726469222c0a2020226d6178223a20223231303030303030222c0a2020226c696d223a202231303030220a7d6821c19e2849b90a2353691fccedd467215c88eec89a5d0dcf468e6cf37abed344d746";

assertEq(
keccak256(
witnessVector.extractWitnessAtIndex(0).extractTapscript()
),
0xf4573d2a3472df97b6a2d9ecb0dab17eef1bebb903ecad628de8e96b0cc7ab30
);
}
}

0 comments on commit 8f37525

Please sign in to comment.