diff --git a/core-test/src/test/scala/org/bitcoins/core/gcs/BlockFilterTest.scala b/core-test/src/test/scala/org/bitcoins/core/gcs/BlockFilterTest.scala index 13f39b8a86..da59790701 100644 --- a/core-test/src/test/scala/org/bitcoins/core/gcs/BlockFilterTest.scala +++ b/core-test/src/test/scala/org/bitcoins/core/gcs/BlockFilterTest.scala @@ -1,6 +1,6 @@ package org.bitcoins.core.gcs -import org.bitcoins.core.crypto.DoubleSha256Digest +import org.bitcoins.core.crypto.DoubleSha256DigestBE import org.bitcoins.core.protocol.blockchain.Block import org.bitcoins.core.protocol.script.ScriptPubKey import org.bitcoins.testkit.util.BitcoinSUnitTest @@ -14,20 +14,28 @@ class BlockFilterTest extends BitcoinSUnitTest { // https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki#appendix-c-test-vectors case class Bip158TestCase( blockHeight: Int, - blockHash: DoubleSha256Digest, + blockHash: DoubleSha256DigestBE, block: Block, prevOutputScripts: Vector[ScriptPubKey], - // TODO prevHeader: BlockFilterHeader, + prevHeader: DoubleSha256DigestBE, filter: GolombFilter, - // TODO header: BlockFilterHeader, + header: DoubleSha256DigestBE, notes: String ) { + val clue: String = s"Test Notes: $notes" + def runTest(): org.scalatest.Assertion = { val constructedFilter = BlockFilter(block, prevOutputScripts) - assert(constructedFilter.decodedHashes == filter.decodedHashes, - s"Test Notes: $notes") + assert(constructedFilter.decodedHashes == filter.decodedHashes, clue) + + assert(constructedFilter.encodedData.bytes == filter.encodedData.bytes, + clue) + + val constructedHeader = constructedFilter.getHeader(prevHeader.flip) + + assert(constructedHeader.hash == header.flip, clue) } } @@ -37,23 +45,33 @@ class BlockFilterTest extends BitcoinSUnitTest { def fromJsArray(array: JsArray): Bip158TestCase = { val parseResult = for { height <- array(0).validate[Int] - blockHash <- array(1).validate[String].map(DoubleSha256Digest.fromHex) + blockHash <- array(1).validate[String].map(DoubleSha256DigestBE.fromHex) block <- array(2).validate[String].map(Block.fromHex) scriptArray <- array(3).validate[JsArray] scripts = parseScripts(scriptArray) - //prevHeader <- array(4).validate[String].map(BlockFilterHeader.fromHex) + prevHeader <- array(4) + .validate[String] + .map(DoubleSha256DigestBE.fromHex) filter <- array(5) .validate[String] - .map(BlockFilter.fromHex(_, blockHash)) + .map(BlockFilter.fromHex(_, blockHash.flip)) - //header <- array(6).validate[String].map(BlockFilterHeader.fromHex) + header <- array(6).validate[String].map(DoubleSha256DigestBE.fromHex) notes <- array(7).validate[String] - } yield Bip158TestCase(height, blockHash, block, scripts, filter, notes) + } yield + Bip158TestCase(height, + blockHash, + block, + scripts, + prevHeader, + filter, + header, + notes) parseResult.get } diff --git a/core/src/main/scala/org/bitcoins/core/gcs/FilterHeader.scala b/core/src/main/scala/org/bitcoins/core/gcs/FilterHeader.scala new file mode 100644 index 0000000000..03c7b37bd7 --- /dev/null +++ b/core/src/main/scala/org/bitcoins/core/gcs/FilterHeader.scala @@ -0,0 +1,28 @@ +package org.bitcoins.core.gcs + +import org.bitcoins.core.crypto.DoubleSha256Digest +import org.bitcoins.core.util.CryptoUtil + +/** + * Bip 157 Block Filter Headers which commit to a chain of block filters, + * much in the same way that block headers commit to a block chain + * @see [[https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#filter-headers]] + */ +case class FilterHeader( + filterHash: DoubleSha256Digest, + prevHeaderHash: DoubleSha256Digest) { + + val hash: DoubleSha256Digest = { + CryptoUtil.doubleSHA256(filterHash.bytes ++ prevHeaderHash.bytes) + } + + /** Given the next Block Filter, constructs the next Block Filter Header */ + def nextHeader(nextFilter: GolombFilter): FilterHeader = { + FilterHeader(filterHash = nextFilter.hash, prevHeaderHash = this.hash) + } + + /** Given the next Block Filter hash, constructs the next Block Filter Header */ + def nextHeader(nextFilterHash: DoubleSha256Digest): FilterHeader = { + FilterHeader(filterHash = nextFilterHash, prevHeaderHash = this.hash) + } +} diff --git a/core/src/main/scala/org/bitcoins/core/gcs/GolombFilter.scala b/core/src/main/scala/org/bitcoins/core/gcs/GolombFilter.scala index 55ece073a6..7689eaa2d9 100644 --- a/core/src/main/scala/org/bitcoins/core/gcs/GolombFilter.scala +++ b/core/src/main/scala/org/bitcoins/core/gcs/GolombFilter.scala @@ -12,7 +12,7 @@ import org.bitcoins.core.protocol.transaction.{ TransactionOutput } import org.bitcoins.core.script.control.OP_RETURN -import org.bitcoins.core.util.BitcoinSUtil +import org.bitcoins.core.util.{BitcoinSUtil, CryptoUtil} import scodec.bits.{BitVector, ByteVector} import scala.annotation.tailrec @@ -31,6 +31,27 @@ case class GolombFilter( encodedData: BitVector) { lazy val decodedHashes: Vector[UInt64] = GCS.golombDecodeSet(encodedData, p) + /** The hash of this serialized filter */ + lazy val hash: DoubleSha256Digest = { + CryptoUtil.doubleSHA256(this.bytes) + } + + /** Given the previous FilterHeader, constructs the header corresponding to this */ + def getHeader(prevHeader: FilterHeader): FilterHeader = { + FilterHeader(filterHash = this.hash, prevHeaderHash = prevHeader.hash) + } + + /** Given the previous FilterHeader hash, constructs the header corresponding to this */ + def getHeader(prevHeaderHash: DoubleSha256Digest): FilterHeader = { + FilterHeader(filterHash = this.hash, prevHeaderHash = prevHeaderHash) + } + + def bytes: ByteVector = { + n.bytes ++ encodedData.bytes + } + + def hex: String = bytes.toHex + // TODO: Offer alternative that stops decoding when it finds out if data is there def matchesHash(hash: UInt64): Boolean = { @tailrec