commit 61a9deaa610fecbacd3b37501faba59a7e60d163
parent 7797af6ffa1f584377bbc16e1aee4736f610508a
Author: ghost43 <somber.night@protonmail.com>
Date: Wed, 20 Jun 2018 18:09:38 +0200
Check SPV proof inner nodes not to be valid transactions. (#4436)
Diffstat:
1 file changed, 26 insertions(+), 2 deletions(-)
diff --git a/lib/verifier.py b/lib/verifier.py
@@ -20,8 +20,12 @@
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
-from .util import ThreadJob
+from .util import ThreadJob, bh2u
from .bitcoin import Hash, hash_decode, hash_encode
+from .transaction import Transaction
+
+
+class InnerNodeOfSpvProofIsValidTx(Exception): pass
class SPV(ThreadJob):
@@ -79,7 +83,12 @@ class SPV(ThreadJob):
tx_hash = params[0]
tx_height = merkle.get('block_height')
pos = merkle.get('pos')
- merkle_root = self.hash_merkle_root(merkle['merkle'], tx_hash, pos)
+ try:
+ merkle_root = self.hash_merkle_root(merkle['merkle'], tx_hash, pos)
+ except InnerNodeOfSpvProofIsValidTx:
+ self.print_error("merkle verification failed for {} (inner node looks like tx)"
+ .format(tx_hash))
+ return
header = self.network.blockchain().read_header(tx_height)
# FIXME: if verification fails below,
# we should make a fresh connection to a server to
@@ -112,8 +121,23 @@ class SPV(ThreadJob):
for i in range(len(merkle_s)):
item = merkle_s[i]
h = Hash(hash_decode(item) + h) if ((pos >> i) & 1) else Hash(h + hash_decode(item))
+ cls._raise_if_valid_tx(bh2u(h))
return hash_encode(h)
+ @classmethod
+ def _raise_if_valid_tx(cls, raw_tx: str):
+ # If an inner node of the merkle proof is also a valid tx, chances are, this is an attack.
+ # https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-June/016105.html
+ # https://lists.linuxfoundation.org/pipermail/bitcoin-dev/attachments/20180609/9f4f5b1f/attachment-0001.pdf
+ # https://bitcoin.stackexchange.com/questions/76121/how-is-the-leaf-node-weakness-in-merkle-trees-exploitable/76122#76122
+ tx = Transaction(raw_tx)
+ try:
+ tx.deserialize()
+ except:
+ pass
+ else:
+ raise InnerNodeOfSpvProofIsValidTx()
+
def undo_verifications(self):
height = self.blockchain.get_checkpoint()
tx_hashes = self.wallet.undo_verifications(self.blockchain, height)