From 19c07d009d01be55a298223b5d7446fa721445ab Mon Sep 17 00:00:00 2001 From: James Prestwich <10149425+prestwich@users.noreply.github.com> Date: Thu, 27 Feb 2020 12:37:29 -0800 Subject: [PATCH] Remove be and parse (#127) * refactor: start prep for no_std version * refactor: remove BE functions from BTCUtils and Parse functions from ValidateSPV in all languages * bug: delete removed method declarations from header file --- c/csrc/btcspv.c | 15 --- c/csrc/btcspv.h | 21 --- c/csrc/check_btcspv.c | 44 ------- golang/btcspv/bitcoin_spv.go | 19 --- golang/btcspv/bitcoin_spv_test.go | 38 ------ golang/btcspv/types.go | 36 +----- golang/btcspv/utils.go | 38 ------ golang/btcspv/utils_test.go | 54 -------- golang/btcspv/validate_spv.go | 75 ----------- golang/btcspv/validate_spv_test.go | 62 --------- golang/cli/header.go | 31 ++++- golang/cli/vin.go | 72 ++++++++++- golang/cli/vout.go | 86 +++++++++++-- js/src/BTCUtils.js | 41 ------ js/src/ValidateSPV.js | 111 ---------------- js/test/BTCUtils.test.js | 33 +---- js/test/ValidateSPV.test.js | 66 ---------- python/btcspv/test/test_validate_spv.py | 24 +--- solidity/contracts/BTCUtils.sol | 26 ---- solidity/contracts/BTCUtilsDelegate.sol | 24 ---- solidity/contracts/ValidateSPV.sol | 91 ------------- solidity/contracts/ValidateSPVDelegate.sol | 30 ----- solidity/contracts/test/BTCUtilsTest.sol | 24 ---- solidity/contracts/test/ValidateSPVTest.sol | 49 ------- solidity/test/BTCUtils.test.js | 16 +-- solidity/test/ValidateSPV.test.js | 83 ------------ testVectors.json | 136 +------------------- 27 files changed, 202 insertions(+), 1143 deletions(-) diff --git a/c/csrc/btcspv.c b/c/csrc/btcspv.c index 9127ad7b..97aac9b4 100644 --- a/c/csrc/btcspv.c +++ b/c/csrc/btcspv.c @@ -183,11 +183,6 @@ byte_view_t btcspv_extract_input_tx_id_le(const_view_t *tx_in) { return tx_id_le; } -void btcspv_extract_input_tx_id_be(uint256 hash, const_view_t *tx_in) { - const_view_t le = btcspv_extract_input_tx_id_le(tx_in); - buf_rev(hash, le.loc, le.len); -} - byte_view_t btcspv_extract_tx_index_le(const_view_t *tx_in) { byte_view_t idx = {tx_in->loc + 32, 4}; return idx; @@ -361,11 +356,6 @@ byte_view_t btcspv_extract_merkle_root_le(const_view_t *header) { return root; } -void btcspv_extract_merkle_root_be(uint256 hash, const_view_t *header) { - const_view_t le = btcspv_extract_merkle_root_le(header); - buf_rev(hash, le.loc, le.len); -} - void btcspv_extract_target_le(uint256 target, const_view_t *header) { uint8_t exponent = header->loc[75] - 3; target[exponent + 0] = header->loc[72]; @@ -393,11 +383,6 @@ byte_view_t btcspv_extract_prev_block_hash_le(const_view_t *header) { return prev_hash; } -void btcspv_extract_prev_block_hash_be(uint256 hash, const_view_t *header) { - const_view_t le = btcspv_extract_prev_block_hash_le(header); - buf_rev(hash, le.loc, le.len); -} - byte_view_t btcspv_extract_timestamp_le(const_view_t *header) { const_view_t timestamp = {header->loc + 68, 4}; return timestamp; diff --git a/c/csrc/btcspv.h b/c/csrc/btcspv.h index 20bf029f..94d3c5ba 100644 --- a/c/csrc/btcspv.h +++ b/c/csrc/btcspv.h @@ -181,13 +181,6 @@ byte_view_t btcspv_extract_outpoint(const_view_t *tx_in); /// @return The tx id (little-endian bytes) byte_view_t btcspv_extract_input_tx_id_le(const_view_t *tx_in); -/// @brief Extracts the outpoint index from an input -/// @note 32 byte tx id from outpoint -/// @param input The input -/// @warning overwrites `hash` -/// @warning caller must ensure `hash` is allocated and can hold 32 bytes -void btcspv_extract_input_tx_id_be(uint256 hash, const_view_t *tx_in); - /// @brief Extracts the LE tx input index from the input in a tx /// @note 4 byte tx index /// @param input The input @@ -276,13 +269,6 @@ bool btcspv_validate_vout(const_view_t *vout); /// @return The merkle root (little-endian) byte_view_t btcspv_extract_merkle_root_le(const_view_t *header); -/// @brief Extracts the transaction merkle root from a block header -/// @note Use verifyHash256Merkle to verify proofs with this root -/// @param header The header -/// @warning overwrites `hash` with the merkle root -/// @warning caller must ensure `hash` is allocated and can hold 32 bytes -void btcspv_extract_merkle_root_be(uint256 hash, const_view_t *header); - /// @brief Extracts the target from a block header /// @note Target is a 256 bit number encoded as a 3-byte mantissa and 1 byte exponent /// @param header The header @@ -312,13 +298,6 @@ uint64_t btcspv_calculate_difficulty(uint256 target); /// @return The previous block's hash (little-endian) byte_view_t btcspv_extract_prev_block_hash_le(const_view_t *header); -/// @brief Extracts the previous block's hash from a block header -/// @note Block headers do NOT include block number :( -/// @param header The header -/// @warning overwrites `hash` with the block header hash -/// @warning caller must ensure `hash` is allocated and can hold 32 bytes -void btcspv_extract_prev_block_hash_be(uint256 hash, const_view_t *header); - /// @brief Extracts the timestamp from a block header /// @note Time is not 100% reliable /// @param header The header diff --git a/c/csrc/check_btcspv.c b/c/csrc/check_btcspv.c index 42289209..fddded1a 100644 --- a/c/csrc/check_btcspv.c +++ b/c/csrc/check_btcspv.c @@ -781,27 +781,6 @@ START_TEST(validate_vout) { } END_TEST -START_TEST(extract_merkle_root_be) { - TEST_LOOP_START("extractMerkleRootBE") - uint8_t *input_buf; - const uint32_t input_len = token_as_hex_buf(&input_buf, input_tok); - const_view_t input = {input_buf, input_len}; - - uint8_t *expected_buf; - const uint32_t expected_len = token_as_hex_buf(&expected_buf, output_tok); - const_view_t expected = {expected_buf, expected_len}; - - uint8_t hash[32] = {0}; - btcspv_extract_merkle_root_be(hash, &input); - - ck_assert(view_eq_buf(&expected, hash, 32)); - - free(input_buf); - free(expected_buf); - TEST_LOOP_END -} -END_TEST - START_TEST(extract_target) { TEST_LOOP_START("extractTarget") uint8_t *input_buf; @@ -828,27 +807,6 @@ START_TEST(extract_target) { } END_TEST -START_TEST(extract_prev_block_hash_be) { - TEST_LOOP_START("extractPrevBlockBE") - uint8_t *input_buf; - const uint32_t input_len = token_as_hex_buf(&input_buf, input_tok); - const_view_t input = {input_buf, input_len}; - - uint8_t *expected_buf; - const uint32_t expected_len = token_as_hex_buf(&expected_buf, output_tok); - const_view_t expected = {expected_buf, expected_len}; - - uint8_t hash[32] = {0}; - btcspv_extract_prev_block_hash_be(hash, &input); - - ck_assert(view_eq_buf(&expected, hash, 32)); - - free(input_buf); - free(expected_buf); - TEST_LOOP_END -} -END_TEST - START_TEST(extract_timestamp) { TEST_LOOP_START("extractTimestamp") @@ -1020,9 +978,7 @@ int main(int argc, char *argv[]) { tcase_add_test(btcspv_case, extract_hash_error); tcase_add_test(btcspv_case, validate_vin); tcase_add_test(btcspv_case, validate_vout); - tcase_add_test(btcspv_case, extract_merkle_root_be); tcase_add_test(btcspv_case, extract_target); - tcase_add_test(btcspv_case, extract_prev_block_hash_be); tcase_add_test(btcspv_case, extract_timestamp); tcase_add_test(btcspv_case, hash256_merkle_step); tcase_add_test(btcspv_case, verify_hash256_merkle); diff --git a/golang/btcspv/bitcoin_spv.go b/golang/btcspv/bitcoin_spv.go index 844b01f7..d1bb1148 100644 --- a/golang/btcspv/bitcoin_spv.go +++ b/golang/btcspv/bitcoin_spv.go @@ -176,13 +176,6 @@ func ExtractInputTxIDLE(input []byte) Hash256Digest { return res } -// ExtractInputTxID returns the input tx id bytes -func ExtractInputTxID(input []byte) Hash256Digest { - LE := ExtractInputTxIDLE(input) - txID := ReverseHash256Endianness(LE) - return txID -} - // ExtractTxIndexLE extracts the LE tx input index from the input in a tx // Returns the tx index as a little endian []byte func ExtractTxIndexLE(input []byte) []byte { @@ -351,12 +344,6 @@ func ExtractMerkleRootLE(header RawHeader) Hash256Digest { return res } -// ExtractMerkleRootBE returns the transaction merkle root from a given block header -// The returned merkle root is big-endian -func ExtractMerkleRootBE(header RawHeader) Hash256Digest { - return ReverseHash256Endianness(ExtractMerkleRootLE(header)) -} - // ExtractTarget returns the target from a given block hedaer func ExtractTarget(header RawHeader) sdk.Uint { // nBits encoding. 3 byte mantissa, 1 byte exponent @@ -391,12 +378,6 @@ func ExtractPrevBlockHashLE(header RawHeader) Hash256Digest { return res } -// ExtractPrevBlockHashBE returns the previous block's hash from a block header -// Returns the hash as a big endian []byte -func ExtractPrevBlockHashBE(header RawHeader) Hash256Digest { - return ReverseHash256Endianness(ExtractPrevBlockHashLE(header)) -} - // ExtractTimestampLE returns the timestamp from a block header // It returns the timestamp as a little endian []byte // Time is not 100% reliable diff --git a/golang/btcspv/bitcoin_spv_test.go b/golang/btcspv/bitcoin_spv_test.go index c3c61c1e..d40edbec 100644 --- a/golang/btcspv/bitcoin_spv_test.go +++ b/golang/btcspv/bitcoin_spv_test.go @@ -346,7 +346,6 @@ func (suite *UtilsSuite) TestExtractOpReturnData() { testCase := fixtureError[i] expected := testCase.ErrorMessage.(string) actual, err := ExtractOpReturnData(testCase.Input.([]byte)) - log.Println(testCase.Input, cap(testCase.Input.([]byte)), actual, err) suite.Nil(actual) suite.EqualError(err, expected) } @@ -442,17 +441,6 @@ func (suite *UtilsSuite) TestExtractInputTxIDLE() { } } -func (suite *UtilsSuite) TestExtractInputTxID() { - fixture := suite.Fixtures["extractInputTxId"] - - for i := range fixture { - testCase := fixture[i] - expected := testCase.Output.(Hash256Digest) - actual := ExtractInputTxID(testCase.Input.([]byte)) - suite.Equal(expected, actual) - } -} - func (suite *UtilsSuite) TestExtractTxIndexLE() { fixture := suite.Fixtures["extractTxIndexLE"] @@ -521,17 +509,6 @@ func (suite *UtilsSuite) TestExtractOutputAtIndex() { suite.EqualError(err, expected) } -func (suite *UtilsSuite) TestExtractMerkleRootBE() { - fixture := suite.Fixtures["extractMerkleRootBE"] - - for i := range fixture { - testCase := fixture[i] - expected := testCase.Output.(Hash256Digest) - actual := ExtractMerkleRootBE(testCase.Input.(RawHeader)) - suite.Equal(expected, actual) - } -} - func (suite *UtilsSuite) TestExtractTarget() { fixture := suite.Fixtures["extractTarget"] @@ -553,21 +530,6 @@ func (suite *UtilsSuite) TestExtractTarget() { } } -func (suite *UtilsSuite) TestExtractPrevBlockHashBE() { - fixture := suite.Fixtures["retargetAlgorithm"] - - for i := range fixture { - testCase := fixture[i] - input := testCase.Input.([]interface{}) - for j := range input { - h := input[j].(map[string]interface{}) - actual := ExtractPrevBlockHashBE(h["hex"].(RawHeader)) - expected := h["prev_block"].(Hash256Digest) - suite.Equal(expected, actual) - } - } -} - func (suite *UtilsSuite) TestExtractTimestamp() { fixture := suite.Fixtures["extractTimestamp"] diff --git a/golang/btcspv/types.go b/golang/btcspv/types.go index 4c5180e4..88d4ece8 100644 --- a/golang/btcspv/types.go +++ b/golang/btcspv/types.go @@ -42,31 +42,6 @@ type SPVProof struct { IntermediateNodes HexBytes `json:"intermediate_nodes"` } -// InputType an enum of types of bitcoin inputs -type InputType int - -// possible input types -const ( - InputNone InputType = 0 - Legacy InputType = 1 - Compatibility InputType = 2 - Witness InputType = 3 -) - -// OutputType an enum of types of bitcoin outputs -type OutputType int - -// possible output types -const ( - OutputNone OutputType = 0 - WPKH OutputType = 1 - WSH OutputType = 2 - OpReturn OutputType = 3 - PKH OutputType = 4 - SH OutputType = 5 - Nonstandard OutputType = 6 -) - // NewHash160Digest instantiates a Hash160Digest from a byte slice func NewHash160Digest(b []byte) (Hash160Digest, error) { var h Hash160Digest @@ -102,16 +77,19 @@ func HeaderFromRaw(raw RawHeader, height uint32) BitcoinHeader { digestLE := Hash256(raw[:]) digestBE := ReverseHash256Endianness(digestLE) prevhashLE := ExtractPrevBlockHashLE(raw) - prevhash := ReverseHash256Endianness(prevhashLE) + prevhashBE := ReverseHash256Endianness(prevhashLE) + merkleRootLE := ExtractMerkleRootLE(raw) + merkleRootBE := ReverseHash256Endianness(merkleRootLE) + return BitcoinHeader{ raw, digestBE, digestLE, height, - prevhash, + prevhashBE, prevhashLE, - ExtractMerkleRootBE(raw), - ExtractMerkleRootLE(raw), + merkleRootBE, + merkleRootLE, } } diff --git a/golang/btcspv/utils.go b/golang/btcspv/utils.go index aedbc2df..0ae72aaa 100644 --- a/golang/btcspv/utils.go +++ b/golang/btcspv/utils.go @@ -32,44 +32,6 @@ func DecodeIfHex(s string) []byte { return res } -// GetOutputType returns the name of the output type associated with the number -func GetOutputType(outputType OutputType) string { - var typeString string - switch outputType { - case OutputNone: - typeString = "Output None" - case WPKH: - typeString = "WPKH" - case WSH: - typeString = "WSH" - case OpReturn: - typeString = "Op Return" - case PKH: - typeString = "PKH" - case SH: - typeString = "SH" - case Nonstandard: - typeString = "Nonstandard" - } - return typeString -} - -// GetInputType returns the name of the input type associated with the number -func GetInputType(inputType InputType) string { - var typeString string - switch inputType { - case InputNone: - typeString = "Input None" - case Legacy: - typeString = "Legacy" - case Compatibility: - typeString = "Compatibility" - case Witness: - typeString = "Witness" - } - return typeString -} - // EncodeP2SH turns a scripthash into an address func EncodeP2SH(sh []byte) (string, error) { if len(sh) != 20 { diff --git a/golang/btcspv/utils_test.go b/golang/btcspv/utils_test.go index e226f3e6..b33b2fb3 100644 --- a/golang/btcspv/utils_test.go +++ b/golang/btcspv/utils_test.go @@ -30,60 +30,6 @@ func (suite *UtilsSuite) TestDecodeIfHex() { } -func (suite *UtilsSuite) TestGetOutputType() { - var expected string - var actual string - - expected = "Output None" - actual = GetOutputType(0) - suite.Equal(expected, actual) - - expected = "WPKH" - actual = GetOutputType(1) - suite.Equal(expected, actual) - - expected = "WSH" - actual = GetOutputType(2) - suite.Equal(expected, actual) - - expected = "Op Return" - actual = GetOutputType(3) - suite.Equal(expected, actual) - - expected = "PKH" - actual = GetOutputType(4) - suite.Equal(expected, actual) - - expected = "SH" - actual = GetOutputType(5) - suite.Equal(expected, actual) - - expected = "Nonstandard" - actual = GetOutputType(6) - suite.Equal(expected, actual) -} - -func (suite *UtilsSuite) TestGetInputType() { - var expected string - var actual string - - expected = "Input None" - actual = GetInputType(0) - suite.Equal(expected, actual) - - expected = "Legacy" - actual = GetInputType(1) - suite.Equal(expected, actual) - - expected = "Compatibility" - actual = GetInputType(2) - suite.Equal(expected, actual) - - expected = "Witness" - actual = GetInputType(3) - suite.Equal(expected, actual) -} - func (suite *UtilsSuite) TestEncodeP2SH() { fixture := suite.Fixtures["encodeP2SH"] diff --git a/golang/btcspv/validate_spv.go b/golang/btcspv/validate_spv.go index 83a45789..4b9a1038 100644 --- a/golang/btcspv/validate_spv.go +++ b/golang/btcspv/validate_spv.go @@ -32,81 +32,6 @@ func CalculateTxID(version, vin, vout, locktime []byte) Hash256Digest { return Hash256(txid) } -// ParseInput returns human-readable information about an input -func ParseInput(input []byte) (uint, Hash256Digest, uint, InputType) { - // NB: If the scriptsig is exactly 00, we are Witness. - // Otherwise we are Compatibility or Legacy - var sequence uint - var witnessTag []byte - var inputType InputType - - if input[36] != 0 { - sequence = ExtractSequenceLegacy(input) - witnessTag = input[36:39] - - if bytes.Equal(witnessTag, []byte{34, 0, 32}) || bytes.Equal(witnessTag, []byte{22, 0, 20}) { - inputType = Compatibility - } else { - inputType = Legacy - } - } else { - sequence = ExtractSequenceWitness(input) - inputType = Witness - } - - inputID := ExtractInputTxID(input) - inputIndex := ExtractTxIndex(input) - - return sequence, inputID, inputIndex, inputType -} - -// ParseOutput extracts human-readable information from an output -func ParseOutput(output []byte) (uint, OutputType, []byte) { - value := ExtractValue(output) - var outputType OutputType - var payload []byte - - if output[9] == 0x6a { - outputType = OpReturn - payload, _ = ExtractOpReturnData(output) - } else { - prefixHash := output[8:10] - if bytes.Equal(prefixHash, []byte{0x22, 0x00}) { - outputType = WSH - payload = output[11:43] - } else if bytes.Equal(prefixHash, []byte{0x16, 0x00}) { - outputType = WPKH - payload = output[11:31] - } else if bytes.Equal(prefixHash, []byte{0x19, 0x76}) { - outputType = PKH - payload = output[12:32] - } else if bytes.Equal(prefixHash, []byte{0x17, 0xa9}) { - outputType = SH - payload = output[11:31] - } else { - outputType = Nonstandard - payload = []byte{} - } - } - - return value, outputType, payload -} - -// ParseHeader parses a block header struct from a bytestring -func ParseHeader(header RawHeader) (Hash256Digest, uint, Hash256Digest, Hash256Digest, uint, sdk.Uint, uint, error) { - digestLE := Hash256(header[:]) - - digest := ReverseHash256Endianness(digestLE) - version := BytesToUint(ReverseEndianness(header[0:4])) - prevHash := ExtractPrevBlockHashLE(header) - merkleRoot := ExtractMerkleRootLE(header) - timestamp := ExtractTimestamp(header) - target := ExtractTarget(header) - nonce := BytesToUint(ReverseEndianness(header[76:80])) - - return digest, version, prevHash, merkleRoot, timestamp, target, nonce, nil -} - // ValidateHeaderWork checks validity of header work func ValidateHeaderWork(digest Hash256Digest, target sdk.Uint) bool { if bytes.Equal(digest[:], bytes.Repeat([]byte{0}, 32)) { diff --git a/golang/btcspv/validate_spv_test.go b/golang/btcspv/validate_spv_test.go index f017f2b6..3ac5fdda 100644 --- a/golang/btcspv/validate_spv_test.go +++ b/golang/btcspv/validate_spv_test.go @@ -51,68 +51,6 @@ func (suite *UtilsSuite) TestCalculateTxId() { } } -func (suite *UtilsSuite) TestParseInput() { - fixture := suite.Fixtures["parseInput"] - - for i := range fixture { - testCase := fixture[i] - expected := testCase.Output.(map[string]interface{}) - expectedSequence := uint(expected["sequence"].(int)) - expectedTxID := expected["txId"].(Hash256Digest) - expectedIndex := uint(expected["index"].(int)) - expectedType := InputType(expected["type"].(int)) - input := testCase.Input.([]byte) - actualSequence, actualTxID, actualIndex, actualType := ParseInput(input) - suite.Equal(expectedSequence, actualSequence) - suite.Equal(expectedTxID, actualTxID) - suite.Equal(expectedIndex, actualIndex) - suite.Equal(expectedType, actualType) - } -} - -func (suite *UtilsSuite) TestParseOutput() { - fixture := suite.Fixtures["parseOutput"] - - for i := range fixture { - testCase := fixture[i] - expected := testCase.Output.(map[string]interface{}) - expectedValue := uint(expected["value"].(int)) - expectedOutputType := OutputType(expected["type"].(int)) - expectedPayload := normalizeToByteSlice(expected["payload"]) - input := normalizeToByteSlice(testCase.Input) - actualValue, actualOutputType, actualPayload := ParseOutput(input) - suite.Equal(expectedValue, actualValue) - suite.Equal(expectedPayload, actualPayload) - suite.Equal(expectedOutputType, actualOutputType) - } -} - -func (suite *UtilsSuite) TestParseHeader() { - fixture := suite.Fixtures["parseHeader"] - - for i := range fixture { - testCase := fixture[i] - expected := testCase.Output.(map[string]interface{}) - expectedDigest := expected["digest"].(Hash256Digest) - expectedVersion := uint(expected["version"].(int)) - expectedPrevHash := expected["prevHash"].(Hash256Digest) - expectedMerkleRoot := expected["merkleRoot"].(Hash256Digest) - expectedTimestamp := uint(expected["timestamp"].(int)) - expectedTarget := BytesToBigUint(expected["target"].([]byte)) - expectedNonce := uint(expected["nonce"].(int)) - input := testCase.Input.(RawHeader) - actualDigest, actualVersion, actualPrevHash, actualMerkleRoot, actualTimestamp, actualTarget, actualNonce, err := ParseHeader(input) - suite.Nil(err) - suite.Equal(expectedDigest, actualDigest) - suite.Equal(expectedVersion, actualVersion) - suite.Equal(expectedPrevHash, actualPrevHash) - suite.Equal(expectedMerkleRoot, actualMerkleRoot) - suite.Equal(expectedTimestamp, actualTimestamp) - suite.Equal(expectedTarget, actualTarget) - suite.Equal(expectedNonce, actualNonce) - } -} - func (suite *UtilsSuite) TestValidateHeaderWork() { var target sdk.Uint fixture := suite.Fixtures["validateHeaderWork"] diff --git a/golang/cli/header.go b/golang/cli/header.go index e31c813b..5be701c3 100644 --- a/golang/cli/header.go +++ b/golang/cli/header.go @@ -44,7 +44,7 @@ func prettifyHeaderData( // ParseHeader takes in a header and returns information about that header: digest, version, previous header hash, merkle root, timestamp, target and nonce func ParseHeader(header btcspv.RawHeader) string { // Get information about the header using ParseHeader - digest, version, prevHash, merkleRoot, timestamp, target, nonce, err := btcspv.ParseHeader(header) + digest, version, prevHash, merkleRoot, timestamp, target, nonce, err := parseHeader(header) // Check for errors if err != nil { return fmt.Sprintf("%s\n", err) @@ -68,3 +68,32 @@ func ValidateHeaderChain(headers []byte) string { // Return the total difficulty return fmt.Sprintf("\nTotal Difficulty: %d\n", totalDifficulty) } + + +// ExtractMerkleRootBE returns the transaction merkle root from a given block header +// The returned merkle root is big-endian +func ExtractMerkleRootBE(header btcspv.RawHeader) btcspv.Hash256Digest { + return btcspv.ReverseHash256Endianness(btcspv.ExtractMerkleRootLE(header)) +} + + +// ExtractPrevBlockHashBE returns the previous block's hash from a block header +// Returns the hash as a big endian []byte +func ExtractPrevBlockHashBE(header btcspv.RawHeader) btcspv.Hash256Digest { + return btcspv.ReverseHash256Endianness(btcspv.ExtractPrevBlockHashLE(header)) +} + +// ParseHeader parses a block header struct from a bytestring +func parseHeader(header btcspv.RawHeader) (btcspv.Hash256Digest, uint, btcspv.Hash256Digest, btcspv.Hash256Digest, uint, sdk.Uint, uint, error) { + digestLE := btcspv.Hash256(header[:]) + + digest := btcspv.ReverseHash256Endianness(digestLE) + version := btcspv.BytesToUint(btcspv.ReverseEndianness(header[0:4])) + prevHash := btcspv.ExtractPrevBlockHashLE(header) + merkleRoot := btcspv.ExtractMerkleRootLE(header) + timestamp := btcspv.ExtractTimestamp(header) + target := btcspv.ExtractTarget(header) + nonce := btcspv.BytesToUint(btcspv.ReverseEndianness(header[76:80])) + + return digest, version, prevHash, merkleRoot, timestamp, target, nonce, nil +} diff --git a/golang/cli/vin.go b/golang/cli/vin.go index e2394680..b8276596 100644 --- a/golang/cli/vin.go +++ b/golang/cli/vin.go @@ -1,17 +1,31 @@ package main import ( + "bytes" "encoding/hex" "fmt" btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" ) -func prettifyInput(numInput int, outpoint btcspv.Hash256Digest, index uint, inputType btcspv.InputType, sequence uint) string { + +// InputType an enum of types of bitcoin inputs +type InputType int + +// possible input types +const ( + InputNone InputType = 0 + Legacy InputType = 1 + Compatibility InputType = 2 + Witness InputType = 3 +) + + +func prettifyInput(numInput int, outpoint btcspv.Hash256Digest, index uint, inputType InputType, sequence uint) string { outpointStr := hex.EncodeToString(outpoint[:]) // Get the input type in readable format - inputTypeString := btcspv.GetInputType(inputType) + inputTypeString := GetInputType(inputType) dataStr := fmt.Sprintf("\nInput #%d:\n Outpoint: %s,\n Index: %d,\n Type: %s,\n Sequence: %d\n", numInput, outpointStr, index, inputTypeString, sequence) @@ -33,7 +47,7 @@ func ParseVin(vin []byte) string { vin := btcspv.ExtractInputAtIndex(vin, uint8(i)) // Use ParseInput to get more information about the vin - sequence, inputID, inputIndex, inputType := btcspv.ParseInput(vin) + sequence, inputID, inputIndex, inputType := parseInput(vin) // Format information about the vin numInput := i + 1 @@ -45,3 +59,55 @@ func ParseVin(vin []byte) string { return formattedInputs } + + +// ExtractInputTxID returns the input tx id bytes +func ExtractInputTxID(input []byte) btcspv.Hash256Digest { + LE := btcspv.ExtractInputTxIDLE(input) + txID := btcspv.ReverseHash256Endianness(LE) + return txID +} + +// ParseInput returns human-readable information about an input +func parseInput(input []byte) (uint, btcspv.Hash256Digest, uint, InputType) { + // NB: If the scriptsig is exactly 00, we are Witness. + // Otherwise we are Compatibility or Legacy + var sequence uint + var witnessTag []byte + var inputType InputType + + if input[36] != 0 { + sequence = btcspv.ExtractSequenceLegacy(input) + witnessTag = input[36:39] + + if bytes.Equal(witnessTag, []byte{34, 0, 32}) || bytes.Equal(witnessTag, []byte{22, 0, 20}) { + inputType = Compatibility + } else { + inputType = Legacy + } + } else { + sequence = btcspv.ExtractSequenceWitness(input) + inputType = Witness + } + + inputID := ExtractInputTxID(input) + inputIndex := btcspv.ExtractTxIndex(input) + + return sequence, inputID, inputIndex, inputType +} + +// GetInputType returns the name of the input type associated with the number +func GetInputType(inputType InputType) string { + var typeString string + switch inputType { + case InputNone: + typeString = "Input None" + case Legacy: + typeString = "Legacy" + case Compatibility: + typeString = "Compatibility" + case Witness: + typeString = "Witness" + } + return typeString +} diff --git a/golang/cli/vout.go b/golang/cli/vout.go index 7ba16806..250134c4 100644 --- a/golang/cli/vout.go +++ b/golang/cli/vout.go @@ -1,22 +1,37 @@ package main import ( + "bytes" "encoding/hex" "fmt" btcspv "github.com/summa-tx/bitcoin-spv/golang/btcspv" ) +// OutputType an enum of types of bitcoin outputs +type OutputType int + +// possible output types +const ( + OutputNone OutputType = 0 + WPKH OutputType = 1 + WSH OutputType = 2 + OpReturn OutputType = 3 + PKH OutputType = 4 + SH OutputType = 5 + Nonstandard OutputType = 6 +) + func prettifyOutput( numOutput int, outpoint []byte, value uint, - outputType btcspv.OutputType) string { + outputType OutputType) string { outpointStr := hex.EncodeToString(outpoint) // Get the output type in readable format - outputTypeString := btcspv.GetOutputType(outputType) + outputTypeString := GetOutputType(outputType) // Get the address associated with the output address := getAddress(outputType, outpoint) @@ -28,19 +43,19 @@ func prettifyOutput( } // getAddress return the address associated with the output -func getAddress(outputType btcspv.OutputType, outpoint []byte) string { +func getAddress(outputType OutputType, outpoint []byte) string { var address string var err error switch outputType { - case btcspv.WPKH: + case WPKH: address, err = btcspv.EncodeP2WPKH(outpoint) - case btcspv.WSH: + case WSH: digest, _ := btcspv.NewHash256Digest(outpoint) address, err = btcspv.EncodeP2WSH(digest) - case btcspv.PKH: + case PKH: address, err = btcspv.EncodeP2PKH(outpoint) - case btcspv.SH: + case SH: address, err = btcspv.EncodeP2SH(outpoint) default: address = "" @@ -70,7 +85,7 @@ func ParseVout(vout []byte) string { } // Use ParseOutput to get more information about the vout - value, outputType, payload := btcspv.ParseOutput(vout) + value, outputType, payload := parseOutput(vout) // Format information about the vout numOutput := i + 1 @@ -82,3 +97,58 @@ func ParseVout(vout []byte) string { return formattedOutputs } + +// ParseOutput extracts human-readable information from an output +func parseOutput(output []byte) (uint, OutputType, []byte) { + value := btcspv.ExtractValue(output) + var outputType OutputType + var payload []byte + + if output[9] == 0x6a { + outputType = OpReturn + payload, _ = btcspv.ExtractOpReturnData(output) + } else { + prefixHash := output[8:10] + if bytes.Equal(prefixHash, []byte{0x22, 0x00}) { + outputType = WSH + payload = output[11:43] + } else if bytes.Equal(prefixHash, []byte{0x16, 0x00}) { + outputType = WPKH + payload = output[11:31] + } else if bytes.Equal(prefixHash, []byte{0x19, 0x76}) { + outputType = PKH + payload = output[12:32] + } else if bytes.Equal(prefixHash, []byte{0x17, 0xa9}) { + outputType = SH + payload = output[11:31] + } else { + outputType = Nonstandard + payload = []byte{} + } + } + + return value, outputType, payload +} + + +// GetOutputType returns the name of the output type associated with the number +func GetOutputType(outputType OutputType) string { + var typeString string + switch outputType { + case OutputNone: + typeString = "Output None" + case WPKH: + typeString = "WPKH" + case WSH: + typeString = "WSH" + case OpReturn: + typeString = "Op Return" + case PKH: + typeString = "PKH" + case SH: + typeString = "SH" + case Nonstandard: + typeString = "Nonstandard" + } + return typeString +} diff --git a/js/src/BTCUtils.js b/js/src/BTCUtils.js index 20262c0a..d386c74a 100644 --- a/js/src/BTCUtils.js +++ b/js/src/BTCUtils.js @@ -250,19 +250,6 @@ export function extractInputTxIdLE(input) { return utils.safeSlice(input, 0, 32); } -/** - * - * Extracts the outpoint index from an input - * 32 byte tx id - * - * @param {Uint8Array} input The input - * @returns {Uint8Array} The tx id (big-endian bytes) - */ -export function extractInputTxId(input) { - const leId = extractInputTxIdLE(input); - return utils.reverseEndianness(leId); -} - /** * * Extracts the LE tx input index from the input in a tx @@ -527,20 +514,6 @@ export function extractMerkleRootLE(header) { return utils.safeSlice(header, 36, 68); } -/** - * - * Extracts the transaction merkle root from a block header - * Use verifyHash256Merkle to verify proofs with this root - * - * @param {Uint8Array} header An 80-byte Bitcoin header - * @returns {Uint8Array} The serialized merkle root (big-endian) - */ -export function extractMerkleRootBE(header) { - return utils.reverseEndianness( - extractMerkleRootLE(header) - ); -} - /** * * Extracts the target from a block header @@ -590,20 +563,6 @@ export function extractPrevBlockLE(header) { return utils.safeSlice(header, 4, 36); } -/** - * - * Extracts the previous block's hash from a block header - * Block headers do NOT include block number :( - * - * @param {Uint8Array} header The header - * @returns {Uint8Array} The previous block's hash (big-endian) - */ -export function extractPrevBlockBE(header) { - return utils.reverseEndianness( - extractPrevBlockLE(header) - ); -} - /** * * Extracts the timestamp from a block header diff --git a/js/src/ValidateSPV.js b/js/src/ValidateSPV.js index e403d50c..c93c6478 100644 --- a/js/src/ValidateSPV.js +++ b/js/src/ValidateSPV.js @@ -60,117 +60,6 @@ export function calculateTxId(version, vin, vout, locktime) { ); } -/** - * - * Parses a tx input from raw input bytes - * - * @dev Supports LEGACY and WITNESS inputs - * @param {Uint8Array} input bytes tx input - * @returns {object} Tx input, sequence number, tx hash, and index - */ -export function parseInput(input) { - // NB: If the scriptsig is exactly 00, we are WITNESS. - // Otherwise we are Compatibility or LEGACY - let sequence; - let witnessTag; - let inputType; - - if (input[36] !== 0) { - sequence = BTCUtils.extractSequenceLegacy(input); - witnessTag = utils.safeSlice(input, 36, 39); - - if (utils.typedArraysAreEqual(witnessTag, new Uint8Array([0x22, 0x00, 0x20])) - || utils.typedArraysAreEqual(witnessTag, new Uint8Array([0x16, 0x00, 0x14]))) { - inputType = utils.INPUT_TYPES.COMPATIBILITY; - } else { - inputType = utils.INPUT_TYPES.LEGACY; - } - } else { - sequence = BTCUtils.extractSequenceWitness(input); - inputType = utils.INPUT_TYPES.WITNESS; - } - - const inputId = BTCUtils.extractInputTxId(input); - const inputIndex = BTCUtils.extractTxIndex(input); - - return { - sequence, inputId, inputIndex, inputType - }; -} - -/** - * - * Parses a tx output from raw output bytes - * - * @dev Differentiates by output script prefix, handles LEGACY and WITNESS - * @param {Uint8Array} output bytes tx output - * @returns {object} Tx output value, output type, payload - */ -export function parseOutput(output) { - const value = BTCUtils.extractValue(output); - let outputType; - let payload; - - if (output[9] === 0x6a) { - // OP_RETURN - outputType = utils.OUTPUT_TYPES.OP_RETURN; - payload = BTCUtils.extractOpReturnData(output); - } else { - const prefixHash = utils.safeSlice(output, 8, 10); - if (utils.typedArraysAreEqual(prefixHash, new Uint8Array([0x22, 0x00]))) { - // P2WSH - outputType = utils.OUTPUT_TYPES.WSH; - payload = utils.safeSlice(output, 11, 43); - } else if (utils.typedArraysAreEqual(prefixHash, new Uint8Array([0x16, 0x00]))) { - // P2WPKH - outputType = utils.OUTPUT_TYPES.WPKH; - payload = utils.safeSlice(output, 11, 31); - } else if (utils.typedArraysAreEqual(prefixHash, new Uint8Array([0x19, 0x76]))) { - // PKH - outputType = utils.OUTPUT_TYPES.PKH; - payload = utils.safeSlice(output, 12, 32); - } else if (utils.typedArraysAreEqual(prefixHash, new Uint8Array([0x17, 0xa9]))) { - // SH - outputType = utils.OUTPUT_TYPES.SH; - payload = utils.safeSlice(output, 11, 31); - } else { - outputType = utils.OUTPUT_TYPES.NONSTANDARD; - payload = new Uint8Array([]); - } - } - - return { value, outputType, payload }; -} - -/** - * - * Parses a block header struct from a bytestring - * - * @dev Block headers are always 80 bytes, see Bitcoin docs - * @param {Uint8Array} header Header - * @returns {object} Header digest, version, previous block header hash, - * merkle root, timestamp, target, nonce - * @throws {TypeError} When passed a bad header - */ -export function parseHeader(header) { - // If header has an invalid length, bubble up error - if (header.length !== 80) { - throw new TypeError('Malformatted header. Must be exactly 80 bytes.'); - } - - const digest = utils.reverseEndianness(BTCUtils.hash256(header)); - const version = utils.bytesToUint(utils.reverseEndianness(utils.safeSlice(header, 0, 4))); - const prevhashLE = BTCUtils.extractPrevBlockLE(header); - const merkleRoot = BTCUtils.extractMerkleRootLE(header); - const timestamp = BTCUtils.extractTimestamp(header); - const target = BTCUtils.extractTarget(header); - const nonce = utils.bytesToUint(utils.reverseEndianness(utils.safeSlice(header, 76, 80))); - - return { - digest, version, prevhashLE, merkleRoot, timestamp, target, nonce - }; -} - /** * * Checks validity of header work diff --git a/js/test/BTCUtils.test.js b/js/test/BTCUtils.test.js index 9dc6f1c1..4a23c41b 100644 --- a/js/test/BTCUtils.test.js +++ b/js/test/BTCUtils.test.js @@ -26,7 +26,6 @@ const { extractValueLE, extractValue, extractInputTxIdLE, - extractInputTxId, extractTxIndexLE, extractTxIndex, determineInputLength, @@ -37,9 +36,7 @@ const { determineOutputLength, determineOutputLengthError, extractOutputAtIndex, - extractMerkleRootBE, extractTarget, - extractPrevBlockBE, extractTimestamp, verifyHash256Merkle, determineVarIntDataLength, @@ -250,27 +247,11 @@ describe('BTCUtils', () => { } }); - it('extracts a root from a header', () => { - for (let i = 0; i < extractMerkleRootBE.length; i += 1) { - const res = BTCUtils.extractMerkleRootBE(extractMerkleRootBE[i].input); - const arraysAreEqual = utils.typedArraysAreEqual(res, extractMerkleRootBE[i].output); - assert.isTrue(arraysAreEqual); - } - }); - it('extracts the target from a header', () => { const res = BTCUtils.extractTarget(extractTarget[0].input); assert.strictEqual(res, utils.bytesToUint(extractTarget[0].output)); }); - it('extracts the prev block hash', () => { - for (let i = 0; i < BTCUtils.extractPrevBlockBE.length; i += 1) { - const res = BTCUtils.extractPrevBlockBE(extractPrevBlockBE[i].input); - const arraysAreEqual = utils.typedArraysAreEqual(res, extractPrevBlockBE[i].output); - assert.isTrue(arraysAreEqual); - } - }); - it('extracts a timestamp from a header', () => { for (let i = 0; i < extractTimestamp.length; i += 1) { const res = BTCUtils.extractTimestamp(extractTimestamp[i].input); @@ -356,7 +337,7 @@ describe('BTCUtils', () => { }); describe('#extractInputTxIdLE', () => { - it('extracts the LE oupoint index from an input', () => { + it('extracts the oupoint index from an input', () => { let res; let equalArrays; for (let i = 0; i < extractInputTxIdLE.length; i += 1) { @@ -367,18 +348,6 @@ describe('BTCUtils', () => { }); }); - describe('#extractInputTxId', () => { - it('extracts the oupoint index from an input', () => { - let res; - let equalArrays; - for (let i = 0; i < extractInputTxId.length; i += 1) { - res = BTCUtils.extractInputTxId(extractInputTxId[i].input); - equalArrays = utils.typedArraysAreEqual(res, extractInputTxId[i].output); - assert.isTrue(equalArrays); - } - }); - }); - describe('#extractTxIndexLE', () => { it('extracts the LE tx input index from the input in a tx', () => { let res; diff --git a/js/test/ValidateSPV.test.js b/js/test/ValidateSPV.test.js index 02731064..8b7c06e5 100644 --- a/js/test/ValidateSPV.test.js +++ b/js/test/ValidateSPV.test.js @@ -14,10 +14,6 @@ utils.updateJSON(vectorObj); const { prove, calculateTxId, - parseInput, - parseOutput, - parseHeader, - parseHeaderError, validateHeaderChain, validateHeaderChainError, validateHeaderWork, @@ -62,68 +58,6 @@ describe('ValidateSPV', () => { }); }); - describe('#parseInput', () => { - it('returns the tx input sequence and outpoint', () => { - for (let i = 0; i < parseInput.length; i += 1) { - const txIn = ValidateSPV.parseInput(parseInput[i].input); - const { - sequence, txId, index, type - } = parseInput[i].output; - - assert.strictEqual(txIn.sequence, BigInt(sequence)); - assert.isTrue(utils.typedArraysAreEqual(txIn.inputId, txId)); - assert.strictEqual(txIn.inputIndex, BigInt(index)); - assert.strictEqual(txIn.inputType, BigInt(type)); - } - }); - }); - - describe('#parseOutput', () => { - it('returns the tx output value, output type, and payload for an output', () => { - for (let i = 0; i < parseOutput.length; i += 1) { - const output = parseOutput[i].input; - const { value, type, payload } = parseOutput[i].output; - - const TxOut = ValidateSPV.parseOutput(output); - - assert.strictEqual(TxOut.value, BigInt(value)); - assert.strictEqual(TxOut.outputType, BigInt(type)); - assert.isTrue(utils.typedArraysAreEqual(TxOut.payload, payload)); - } - }); - }); - - describe('#parseHeader', () => { - it('returns the header digest, version, prevhash, merkleRoot, timestamp, target, and nonce', - () => { - for (let i = 0; i < parseHeader.length; i += 1) { - const validHeader = ValidateSPV.parseHeader(parseHeader[0].input); - const { - digest, version, prevHash, merkleRoot, timestamp, target, nonce - } = parseHeader[i].output; - - assert.isTrue(utils.typedArraysAreEqual(validHeader.digest, digest)); - assert.strictEqual(validHeader.version, BigInt(version)); - assert.isTrue(utils.typedArraysAreEqual(validHeader.prevhashLE, prevHash)); - assert.isTrue(utils.typedArraysAreEqual(validHeader.merkleRoot, merkleRoot)); - assert.strictEqual(validHeader.timestamp, BigInt(timestamp)); - assert.strictEqual(validHeader.target, BigInt(utils.bytesToUint(target))); - assert.strictEqual(validHeader.nonce, BigInt(nonce)); - } - }); - - it('throws error if input header is not valid', () => { - for (let i = 0; i < parseHeaderError.length; i += 1) { - try { - ValidateSPV.parseHeader(parseHeaderError[i].input); - assert(false, 'expected an error'); - } catch (e) { - assert.include(e.message, parseHeaderError[i].errorMessage); - } - } - }); - }); - describe('#validateHeaderChain', () => { it('returns true if header chain is valid', () => { for (let i = 0; i < validateHeaderChain.length; i += 1) { diff --git a/python/btcspv/test/test_validate_spv.py b/python/btcspv/test/test_validate_spv.py index 0c09ff1f..5c43c44c 100644 --- a/python/btcspv/test/test_validate_spv.py +++ b/python/btcspv/test/test_validate_spv.py @@ -56,40 +56,26 @@ def test_validate_vout(self): ) def test_extract_merkle_root_le(self): - cases = self.vectors['extractMerkleRootBE'] + cases = self.vectors['extractMerkleRootLE'] for case in cases: input = bytes.fromhex(case['input'][2:]) - output_be = bytes.fromhex(case['output'][2:]) - output_le = output_be[::-1] + output = bytes.fromhex(case['output'][2:]) self.assertEqual( validate_spv.extract_merkle_root_le(input), - output_le + output ) def test_extract_prev_block_le(self): - cases = self.vectors['extractPrevBlockBE'] - - for case in cases: - input = bytes.fromhex(case['input'][2:]) - output_be = bytes.fromhex(case['output'][2:]) - output_le = output_be[::-1] - - self.assertEqual( - validate_spv.extract_prev_block_le(input), - output_le - ) - - def test_extract_prev_block_be(self): - cases = self.vectors['extractPrevBlockBE'] + cases = self.vectors['extractPrevBlockLE'] for case in cases: input = bytes.fromhex(case['input'][2:]) output = bytes.fromhex(case['output'][2:]) self.assertEqual( - validate_spv.extract_prev_block_be(input), + validate_spv.extract_prev_block_le(input), output ) diff --git a/solidity/contracts/BTCUtils.sol b/solidity/contracts/BTCUtils.sol index b5ded559..e14b0e23 100644 --- a/solidity/contracts/BTCUtils.sol +++ b/solidity/contracts/BTCUtils.sol @@ -259,16 +259,6 @@ library BTCUtils { return _input.slice(0, 32).toBytes32(); } - /// @notice Extracts the outpoint index from an input - /// @dev 32 byte tx id - /// @param _input The input - /// @return The tx id (big-endian bytes) - function extractInputTxId(bytes memory _input) internal pure returns (bytes32) { - bytes memory _leId = abi.encodePacked(extractInputTxIdLE(_input)); - bytes memory _beId = reverseEndianness(_leId); - return _beId.toBytes32(); - } - /// @notice Extracts the LE tx input index from the input in a tx /// @dev 4 byte tx index /// @param _input The input @@ -475,14 +465,6 @@ library BTCUtils { return _header.slice(36, 32); } - /// @notice Extracts the transaction merkle root from a block header - /// @dev Use verifyHash256Merkle to verify proofs with this root - /// @param _header The header - /// @return The merkle root (big-endian) - function extractMerkleRootBE(bytes memory _header) internal pure returns (bytes memory) { - return reverseEndianness(extractMerkleRootLE(_header)); - } - /// @notice Extracts the target from a block header /// @dev Target is a 256 bit number encoded as a 3-byte mantissa and 1 byte exponent /// @param _header The header @@ -514,14 +496,6 @@ library BTCUtils { return _header.slice(4, 32); } - /// @notice Extracts the previous block's hash from a block header - /// @dev Block headers do NOT include block number :( - /// @param _header The header - /// @return The previous block's hash (big-endian) - function extractPrevBlockBE(bytes memory _header) internal pure returns (bytes memory) { - return reverseEndianness(extractPrevBlockLE(_header)); - } - /// @notice Extracts the timestamp from a block header /// @dev Time is not 100% reliable /// @param _header The header diff --git a/solidity/contracts/BTCUtilsDelegate.sol b/solidity/contracts/BTCUtilsDelegate.sol index 9ba7e542..6af5c101 100644 --- a/solidity/contracts/BTCUtilsDelegate.sol +++ b/solidity/contracts/BTCUtilsDelegate.sol @@ -166,14 +166,6 @@ library BTCUtilsDelegate { return BTCUtils.extractInputTxIdLE(_input); } - /// @notice Extracts the outpoint index from an input - /// @dev 32 byte tx id - /// @param _input The input - /// @return The tx id (big-endian bytes) - function extractInputTxId(bytes memory _input) public pure returns (bytes32) { - return BTCUtils.extractInputTxId(_input); - } - /// @notice Extracts the LE tx input index from the input in a tx /// @dev 4 byte tx index /// @param _input The input @@ -287,14 +279,6 @@ library BTCUtilsDelegate { return BTCUtils.extractMerkleRootLE(_header); } - /// @notice Extracts the transaction merkle root from a block header - /// @dev Use verifyHash256Merkle to verify proofs with this root - /// @param _header The header - /// @return The merkle root (big-endian) - function extractMerkleRootBE(bytes memory _header) public pure returns (bytes memory) { - return BTCUtils.extractMerkleRootBE(_header); - } - /// @notice Extracts the target from a block header /// @dev Target is a 256 bit number encoded as a 3-byte mantissa and 1 byte exponent /// @param _header The header @@ -320,14 +304,6 @@ library BTCUtilsDelegate { return BTCUtils.extractPrevBlockLE(_header); } - /// @notice Extracts the previous block's hash from a block header - /// @dev Block headers do NOT include block number :( - /// @param _header The header - /// @return The previous block's hash (big-endian) - function extractPrevBlockBE(bytes memory _header) public pure returns (bytes memory) { - return BTCUtils.extractPrevBlockBE(_header); - } - /// @notice Extracts the timestamp from a block header /// @dev Time is not 100% reliable /// @param _header The header diff --git a/solidity/contracts/ValidateSPV.sol b/solidity/contracts/ValidateSPV.sol index ebb8b091..f4db5be7 100644 --- a/solidity/contracts/ValidateSPV.sol +++ b/solidity/contracts/ValidateSPV.sol @@ -74,97 +74,6 @@ library ValidateSPV { return abi.encodePacked(_version, _vin, _vout, _locktime).hash256(); } - /// @notice Parses a tx input from raw input bytes - /// @dev Supports Legacy and Witness inputs - /// @param _input Raw bytes tx input - /// @return Tx input sequence number, tx hash, and index - function parseInput(bytes memory _input) internal pure returns (uint32 _sequence, bytes32 _hash, uint32 _index, uint8 _inputType) { - // NB: If the scriptsig is exactly 00, we are witness. - // Otherwise we are compatibility - if (_input.keccak256Slice(36, 1) != keccak256(hex"00")) { - _sequence = _input.extractSequenceLegacy(); - bytes32 _witnessTag = _input.keccak256Slice(36, 3); - - if (_witnessTag == keccak256(hex"220020") || _witnessTag == keccak256(hex"160014")) { - _inputType = uint8(InputTypes.COMPATIBILITY); - } else { - _inputType = uint8(InputTypes.LEGACY); - } - - } else { - _sequence = _input.extractSequenceWitness(); - _inputType = uint8(InputTypes.WITNESS); - } - - return (_sequence, _input.extractInputTxId(), _input.extractTxIndex(), _inputType); - } - - /// @notice Parses a tx output from raw output bytes - /// @dev Differentiates by output script prefix, handles legacy and witness - /// @param _output Raw bytes tx output - /// @return Tx output value, output type, payload - function parseOutput(bytes memory _output) internal pure returns (uint64 _value, uint8 _outputType, bytes memory _payload) { - - _value = _output.extractValue(); - - if (_output.keccak256Slice(9, 1) == keccak256(hex"6a")) { - // OP_RETURN - _outputType = uint8(OutputTypes.OP_RETURN); - _payload = _output.extractOpReturnData(); - } else { - bytes32 _prefixHash = _output.keccak256Slice(8, 2); - if (_prefixHash == keccak256(hex"2200")) { - // P2WSH - _outputType = uint8(OutputTypes.WSH); - _payload = _output.slice(11, 32); - } else if (_prefixHash == keccak256(hex"1600")) { - // P2WPKH - _outputType = uint8(OutputTypes.WPKH); - _payload = _output.slice(11, 20); - } else if (_prefixHash == keccak256(hex"1976")) { - // PKH - _outputType = uint8(OutputTypes.PKH); - _payload = _output.slice(12, 20); - } else if (_prefixHash == keccak256(hex"17a9")) { - // SH - _outputType = uint8(OutputTypes.SH); - _payload = _output.slice(11, 20); - } else { - _outputType = uint8(OutputTypes.NONSTANDARD); - } - } - - return (_value, _outputType, _payload); - } - - /// @notice Parses a block header struct from a bytestring - /// @dev Block headers are always 80 bytes, see Bitcoin docs - /// @return Header digest, version, previous block header hash, merkle root, timestamp, target, nonce - function parseHeader(bytes memory _header) internal pure returns ( - bytes32 _digest, - uint32 _version, - bytes32 _prevHash, - bytes32 _merkleRoot, - uint32 _timestamp, - uint256 _target, - uint32 _nonce - ) { - // If header has an invalid length, bubble up error - if (_header.length != 80) { - return(_digest, _version, _prevHash, _merkleRoot, _timestamp, _target, _nonce); - } - - _digest = abi.encodePacked(_header.hash256()).reverseEndianness().toBytes32(); - _version = uint32(_header.slice(0, 4).reverseEndianness().bytesToUint()); - _prevHash = _header.extractPrevBlockLE().toBytes32(); - _merkleRoot = _header.extractMerkleRootLE().toBytes32(); - _timestamp = _header.extractTimestamp(); - _target = _header.extractTarget(); - _nonce = uint32(_header.slice(76, 4).reverseEndianness().bytesToUint()); - - return(_digest, _version, _prevHash, _merkleRoot, _timestamp, _target, _nonce); - } - /// @notice Checks validity of header chain /// @notice Compares the hash of each header to the prevHash in the next header /// @param _headers Raw byte array of header chain diff --git a/solidity/contracts/ValidateSPVDelegate.sol b/solidity/contracts/ValidateSPVDelegate.sol index 4e078be7..b9a34dac 100644 --- a/solidity/contracts/ValidateSPVDelegate.sol +++ b/solidity/contracts/ValidateSPVDelegate.sol @@ -51,36 +51,6 @@ library ValidateSPVDelegate { return ValidateSPV.calculateTxId(_version, _vin, _vout, _locktime); } - /// @notice Parses a tx input from raw input bytes - /// @dev Supports Legacy Inputs now too - /// @param _input Raw bytes tx input - /// @return Tx input sequence number, tx hash, and index - function parseInput(bytes memory _input) public pure returns (uint32 _sequence, bytes32 _hash, uint32 _index, uint8 _inputType) { - return ValidateSPV.parseInput(_input); - } - - /// @notice Parses a tx output from raw output bytes - /// @dev Differentiates by output script prefix - /// @param _output Raw bytes tx output - /// @return Tx output value, output type, payload - function parseOutput(bytes memory _output) public pure returns (uint64 _value, uint8 _outputType, bytes memory _payload) { - return ValidateSPV.parseOutput(_output); - } - - /// @notice Parses a block header struct from a bytestring - /// @dev Block headers are always 80 bytes, see Bitcoin docs - /// @return Header digest, version, previous block header hash, merkle root, timestamp, target, nonce - function parseHeader(bytes memory _header) public pure returns ( - bytes32 _digest, - uint32 _version, - bytes32 _prevHash, - bytes32 _merkleRoot, - uint32 _timestamp, - uint256 _target, - uint32 _nonce - ) { - return ValidateSPV.parseHeader(_header); - } /// @notice Checks validity of header chain /// @notice Compares the hash of each header to the prevHash in the next header /// @param _headers Raw byte array of header chain diff --git a/solidity/contracts/test/BTCUtilsTest.sol b/solidity/contracts/test/BTCUtilsTest.sol index 27f53da6..71695f03 100644 --- a/solidity/contracts/test/BTCUtilsTest.sol +++ b/solidity/contracts/test/BTCUtilsTest.sol @@ -158,14 +158,6 @@ contract BTCUtilsTest { return BTCUtils.extractInputTxIdLE(_input); } - /// @notice Extracts the outpoint index from an input - /// @dev 32 byte tx id - /// @param _input The input - /// @return The tx id (big-endian bytes) - function extractInputTxId(bytes memory _input) public pure returns (bytes32) { - return BTCUtils.extractInputTxId(_input); - } - /// @notice Extracts the LE tx input index from the input in a tx /// @dev 4 byte tx index /// @param _input The input @@ -279,14 +271,6 @@ contract BTCUtilsTest { return BTCUtils.extractMerkleRootLE(_header); } - /// @notice Extracts the transaction merkle root from a block header - /// @dev Use verifyHash256Merkle to verify proofs with this root - /// @param _header The header - /// @return The merkle root (big-endian) - function extractMerkleRootBE(bytes memory _header) public pure returns (bytes memory) { - return BTCUtils.extractMerkleRootBE(_header); - } - /// @notice Extracts the target from a block header /// @dev Target is a 256 bit number encoded as a 3-byte mantissa and 1 byte exponent /// @param _header The header @@ -312,14 +296,6 @@ contract BTCUtilsTest { return BTCUtils.extractPrevBlockLE(_header); } - /// @notice Extracts the previous block's hash from a block header - /// @dev Block headers do NOT include block number :( - /// @param _header The header - /// @return The previous block's hash (big-endian) - function extractPrevBlockBE(bytes memory _header) public pure returns (bytes memory) { - return BTCUtils.extractPrevBlockBE(_header); - } - /// @notice Extracts the timestamp from a block header /// @dev Time is not 100% reliable /// @param _header The header diff --git a/solidity/contracts/test/ValidateSPVTest.sol b/solidity/contracts/test/ValidateSPVTest.sol index 440b31af..ec351cc0 100644 --- a/solidity/contracts/test/ValidateSPVTest.sol +++ b/solidity/contracts/test/ValidateSPVTest.sol @@ -50,55 +50,6 @@ contract ValidateSPVTest { return ValidateSPV.calculateTxId(_version, _vin, _vout, _locktime); } - /// @notice Parses a tx input from raw input bytes - /// @dev Supports Legacy Inputs now too - /// @param _input Raw bytes tx input - /// @return Tx input sequence number, tx hash, and index - function parseInput(bytes memory _input) public pure returns (uint32 _sequence, bytes32 _hash, uint32 _index, uint8 _inputType) { - return ValidateSPV.parseInput(_input); - } - - /// @notice Parses a tx input from raw input bytes - /// @dev Supports Legacy Inputs now too - /// @param _input Raw bytes tx input - /// @return Tx input sequence number, tx hash, and index - // DESPITE WHAT THE COMPILER TELLS YOU, THIS CANNOT BE SET TO PURE - function parseInputTx(bytes memory _input) public view returns (uint32 _sequence, bytes32 _hash, uint32 _index, uint8 _inputType) { - return ValidateSPV.parseInput(_input); - } - - /// @notice Parses a tx output from raw output bytes - /// @dev Differentiates by output script prefix - /// @param _output Raw bytes tx output - /// @return Tx output value, output type, payload - function parseOutput(bytes memory _output) public pure returns (uint64 _value, uint8 _outputType, bytes memory _payload) { - return ValidateSPV.parseOutput(_output); - } - - /// @notice Parses a tx output from raw output bytes - /// @dev Differentiates by output script prefix - /// @param _output Raw bytes tx output - /// @return Tx output value, output type, payload - // DESPITE WHAT THE COMPILER TELLS YOU, THIS CANNOT BE SET TO PURE - function parseOutputTx(bytes memory _output) public view returns (uint64 _value, uint8 _outputType, bytes memory _payload) { - return ValidateSPV.parseOutput(_output); - } - - /// @notice Parses a block header struct from a bytestring - /// @dev Block headers are always 80 bytes, see Bitcoin docs - /// @return Header digest, version, previous block header hash, merkle root, timestamp, target, nonce - function parseHeader(bytes memory _header) public pure returns ( - bytes32 _digest, - uint32 _version, - bytes32 _prevHash, - bytes32 _merkleRoot, - uint32 _timestamp, - uint256 _target, - uint32 _nonce - ) { - return ValidateSPV.parseHeader(_header); - } - /// @notice Checks validity of header chain /// @notice Compares the hash of each header to the prevHash in the next header /// @param _headers Raw byte array of header chain diff --git a/solidity/test/BTCUtils.test.js b/solidity/test/BTCUtils.test.js index b84d787c..c97e0216 100644 --- a/solidity/test/BTCUtils.test.js +++ b/solidity/test/BTCUtils.test.js @@ -36,9 +36,9 @@ const { determineOutputLength, determineOutputLengthError, extractOutputAtIndex, - extractMerkleRootBE, + extractMerkleRootLE, extractTarget, - extractPrevBlockBE, + extractPrevBlockLE, extractTimestamp, verifyHash256Merkle, determineVarIntDataLength, @@ -294,9 +294,9 @@ contract('BTCUtils', () => { }); it('extracts a root from a header', async () => { - for (let i = 0; i < extractMerkleRootBE.length; i += 1) { - const res = await instance.extractMerkleRootBE(extractMerkleRootBE[i].input); - assert.strictEqual(res, extractMerkleRootBE[i].output); + for (let i = 0; i < extractMerkleRootLE.length; i += 1) { + const res = await instance.extractMerkleRootLE(extractMerkleRootLE[i].input); + assert.strictEqual(res, extractMerkleRootLE[i].output); } }); @@ -308,9 +308,9 @@ contract('BTCUtils', () => { }); it('extracts the prev block hash', async () => { - for (let i = 0; i < extractPrevBlockBE.length; i += 1) { - const res = await instance.extractPrevBlockBE(extractPrevBlockBE[i].input); - assert.strictEqual(res, extractPrevBlockBE[i].output); + for (let i = 0; i < extractPrevBlockLE.length; i += 1) { + const res = await instance.extractPrevBlockLE(extractPrevBlockLE[i].input); + assert.strictEqual(res, extractPrevBlockLE[i].output); } }); diff --git a/solidity/test/ValidateSPV.test.js b/solidity/test/ValidateSPV.test.js index 6576cafb..1e1f109f 100644 --- a/solidity/test/ValidateSPV.test.js +++ b/solidity/test/ValidateSPV.test.js @@ -14,10 +14,6 @@ const { getErrLowWork, prove, calculateTxId, - parseInput, - parseOutput, - parseHeader, - parseHeaderSolErr, validateHeaderChain, validateHeaderChainError, validateHeaderWork, @@ -69,85 +65,6 @@ contract('ValidateSPV', () => { }); }); - describe('#parseInput', async () => { - it('returns the tx input sequence and outpoint', async () => { - for (let i = 0; i < parseInput.length; i += 1) { - const txIn = await instance.parseInput(parseInput[i].input); - const { - sequence, txId, index, type - } = parseInput[i].output; - - // Execute within Tx to measure gas amount - await instance.parseInputTx(parseInput[i].input); - - assert(txIn._sequence.eq(new BN(sequence, 10))); - assert.strictEqual(txIn._hash, txId); - assert(txIn._index.eq(new BN(index, 10))); - assert(txIn._inputType.eq(new BN(type, 10))); - } - }); - }); - - describe('#parseOutput', async () => { - it('returns the tx output value, output type, and payload for an OP_RETURN output', async () => { - for (let i = 0; i < parseOutput.length; i += 1) { - const { value, type } = parseOutput[i].output; - let { payload } = parseOutput[i].output; - - // better way to do this? - if (payload === '0x') { - payload = null; - } - - const txOut = await instance.parseOutput(parseOutput[i].input); - - // Execute within Tx to measure gas amount - await instance.parseOutputTx(parseOutput[i].input); - - assert(txOut._value.eq(new BN(value, 10))); - assert(txOut._outputType.eq(new BN(type, 10))); - assert.strictEqual(txOut._payload, payload); - } - }); - }); - - describe('#parseHeader', async () => { - it('returns the header digest, version, prevHash, merkleRoot, timestamp, target, and nonce', - async () => { - for (let i = 0; i < parseHeader.length; i += 1) { - const validHeader = await instance.parseHeader(parseHeader[i].input); - const { - digest, version, prevHash, merkleRoot, timestamp, target, nonce - } = parseHeader[i].output; - - assert.strictEqual(validHeader._digest, digest); - assert(validHeader._version.eq(new BN(version, 10))); - assert.strictEqual(validHeader._prevHash, prevHash); - assert.strictEqual(validHeader._merkleRoot, merkleRoot); - assert(validHeader._timestamp.eq(new BN(timestamp, 10))); - assert(validHeader._target.eq(new BN(target.slice(2), 16))); - assert(validHeader._nonce.eq(new BN(nonce, 10))); - } - }); - - it('bubble up errors if input header is not 80 bytes', async () => { - for (let i = 0; i < parseHeaderSolErr.length; i += 1) { - const invalidHeader = await instance.parseHeader(parseHeaderSolErr[i].input); - const { - digest, version, prevHash, merkleRoot, timestamp, target, nonce - } = parseHeaderSolErr[i].output; - - assert.strictEqual(digest, invalidHeader._digest); - assert(new BN(version, 10).eq(invalidHeader._version)); - assert.strictEqual(prevHash, invalidHeader._prevHash); - assert.strictEqual(merkleRoot, invalidHeader._merkleRoot); - assert(new BN(timestamp, 10).eq(invalidHeader._timestamp)); - assert(new BN(target, 10).eq(invalidHeader._target)); - assert(new BN(nonce, 10).eq(invalidHeader._nonce)); - } - }); - }); - describe('#validateHeaderChain', async () => { it('returns true if header chain is valid', async () => { for (let i = 0; i < validateHeaderChain.length; i += 1) { diff --git a/testVectors.json b/testVectors.json index bbe07e64..b28f1d0f 100644 --- a/testVectors.json +++ b/testVectors.json @@ -198,12 +198,6 @@ "output": "0x1746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba30" } ], - "extractInputTxId": [ - { - "input": "0x1746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000001eeffffffff", - "output": "0x30baf9c04bfdefff1ca6b425fff0c458aae1834bc2448f4b49f3007486bd4617" - } - ], "extractTxIndexLE": [ { "input": "0x1746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000001eeffffffff", @@ -428,10 +422,10 @@ "solidityError": "Multi-byte VarInts not supported" } ], - "extractMerkleRootBE": [ + "extractMerkleRootLE": [ { "input": "0x0100000055bd840a78798ad0da853f68974f3d183e2bd1db6a842c1feecf222a00000000ff104ccb05421ab93e63f8c3ce5c2c2e9dbb37de2764b3a3175c8166562cac7d51b96a49ffff001d283e9e70", - "output": "0x7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff" + "output": "0xff104ccb05421ab93e63f8c3ce5c2c2e9dbb37de2764b3a3175c8166562cac7d" } ], "extractTarget": [ @@ -440,10 +434,10 @@ "output": "0x00000000ffff0000000000000000000000000000000000000000000000000000" } ], - "extractPrevBlockBE": [ + "extractPrevBlockLE": [ { "input": "0x0100000055bd840a78798ad0da853f68974f3d183e2bd1db6a842c1feecf222a00000000ff104ccb05421ab93e63f8c3ce5c2c2e9dbb37de2764b3a3175c8166562cac7d51b96a49ffff001d283e9e70", - "output": "0x000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55" + "output": "0x55bd840a78798ad0da853f68974f3d183e2bd1db6a842c1feecf222a00000000" } ], "extractTimestamp": [ @@ -579,128 +573,6 @@ "output": "0x48e5a1a0e616d8fd92b4ef228c424e0c816799a256c6a90892195ccfc53300d6" } ], - "parseInput": [ - { - "input": "0x7bb2b8f32b9ebf13af2b0a2f9dc03797c7b77ccddcac75d1216389abfa7ab3750000000000ffffffff", - "output": { - "sequence": 4294967295, - "txId": "0x75b37afaab896321d175acdccd7cb7c79737c09d2f0a2baf13bf9e2bf3b8b27b", - "index": 0, - "type": 3 - } - }, - { - "input": "0x7bb2b8f32b9ebf13af2b0a2f9dc03797c7b77ccddcac75d1216389abfa7ab375000000000101ffffffff", - "output": { - "sequence": 4294967295, - "txId": "0x75b37afaab896321d175acdccd7cb7c79737c09d2f0a2baf13bf9e2bf3b8b27b", - "index": 0, - "type": 1 - } - }, - { - "input": "0x7bb2b8f32b9ebf13af2b0a2f9dc03797c7b77ccddcac75d1216389abfa7ab37500000000160014eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffff", - "output": { - "sequence": 4294967295, - "txId": "0x75b37afaab896321d175acdccd7cb7c79737c09d2f0a2baf13bf9e2bf3b8b27b", - "index": 0, - "type": 2 - } - }, - { - "input": "0x7bb2b8f32b9ebf13af2b0a2f9dc03797c7b77ccddcac75d1216389abfa7ab37500000000220020eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeffffffff", - "output": { - "sequence": 4294967295, - "txId": "0x75b37afaab896321d175acdccd7cb7c79737c09d2f0a2baf13bf9e2bf3b8b27b", - "index": 0, - "type": 2 - } - } - ], - "parseOutput": [ - { - "input": "0x0000000000000000166a14edb1b5c2f39af0fec151732585b1049b07895211", - "output": { - "value": 0, - "type": 3, - "payload": "0xedb1b5c2f39af0fec151732585b1049b07895211" - } - }, - { - "input": "0xe8cd9a3b000000001600147849e6bf5e4b1ba7235572d1b0cbc094f0213e6c", - "output": { - "value": 1000001000, - "type": 1, - "payload": "0x7849e6bf5e4b1ba7235572d1b0cbc094f0213e6c" - } - }, - { - "input": "0x40420f0000000000220020aedad4518f56379ef6f1f52f2e0fed64608006b3ccaff2253d847ddc90c91922", - "output": { - "value": 1000000, - "type": 2, - "payload": "0xaedad4518f56379ef6f1f52f2e0fed64608006b3ccaff2253d847ddc90c91922" - } - }, - { - "input": "0x0000000000000000167a14edb1b5c2f39af0fec151732585b1049b07895211", - "output": { - "value": 0, - "type": 6, - "payload": "0x" - } - }, - { - "input": "0xe8df05000000000017a914a654ebafa7a37e04a7ec3f684e34897e48f0496287", - "output": { - "value": 385000, - "type": 5, - "payload": "0xa654ebafa7a37e04a7ec3f684e34897e48f04962" - } - }, - { - "input": "0x88080000000000001976a9141458514240d7287e5254af48cd292eb876cb07eb88ac", - "output": { - "value": 2184, - "type": 4, - "payload": "0x1458514240d7287e5254af48cd292eb876cb07eb" - } - } - ], - "parseHeader": [ - { - "input": "0x0000002073bd2184edd9c4fc76642ea6754ee40136970efc10c4190000000000000000000296ef123ea96da5cf695f22bf7d94be87d49db1ad7ac371ac43c4da4161c8c216349c5ba11928170d38782b", - "output": { - "digest": "0x00000000000000000024cc6777e93673f53853240d34f1bb7fb1d63983e470fe", - "version": 536870912, - "prevHash": "0x73bd2184edd9c4fc76642ea6754ee40136970efc10c419000000000000000000", - "merkleRoot": "0x0296ef123ea96da5cf695f22bf7d94be87d49db1ad7ac371ac43c4da4161c8c2", - "timestamp": 1536963606, - "target": "0x2819a10000000000000000000000000000000000000000", - "nonce": 729298957 - } - } - ], - "parseHeaderError": [ - { - "input": "0x00002073bd2184edd9c4fc76642ea6754ee40136970efc10c4190000000000000000000296ef123ea96da5cf695f22bf7d94be87d49db1ad7ac371ac43c4da4161c8c216349c5ba11928170d38782b", - "errorMessage": "Malformatted header. Must be exactly 80 bytes" - } - ], - "parseHeaderSolErr": [ - { - "input": "0x0000002073bd2184edd9c4fc76642ea6754ee40136970efc10c4190000000000000000000296ef123ea96da5cf695f22bf7d94be87d49db1ad7ac371ac43c4da4161c8c216349c5ba11928170d38782b000020fe70e48339d6b17fbbf1340d245338f57336e97767cc240000000000000000005af53b865c27c6e9b5e5db4c3ea8e024f8329178a79ddb39f7727ea2fe6e6825d1349c5ba1192817e2d9515900000020baaea6746f4c16ccb7cd961655b636d39b5fe1519b8f15000000000000000000c63a8848a448a43c9e4402bd893f701cd11856e14cbbe026699e8fdc445b35a8d93c9c5ba1192817b945dc6c00000020f402c0b551b944665332466753f1eebb846a64ef24c71700000000000000000033fc68e070964e908d961cd11033896fa6c9b8b76f64a2db7ea928afa7e304257d3f9c5ba11928176164145d0000ff3f63d40efa46403afd71a254b54f2b495b7b0164991c2d22000000000000000000f046dc1b71560b7d0786cfbdb25ae320bd9644c98d5c7c77bf9df05cbe96212758419c5ba1192817a2bb2caa00000020e2d4f0edd5edd80bdcb880535443747c6b22b48fb6200d0000000000000000001d3799aa3eb8d18916f46bf2cf807cb89a9b1b4c56c3f2693711bf1064d9a32435429c5ba1192817752e49ae0000002022dba41dff28b337ee3463bf1ab1acf0e57443e0f7ab1d000000000000000000c3aadcc8def003ecbd1ba514592a18baddddcd3a287ccf74f584b04c5c10044e97479c5ba1192817c341f595", - "output": { - "digest": "0x0000000000000000000000000000000000000000000000000000000000000000", - "version": 0, - "prevHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "merkleRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", - "timestamp": 0, - "target": 0, - "nonce": 0 - } - } - ], "validateHeaderWork": [ { "input": {