diff --git a/bip-0352/reference.py b/bip-0352/reference.py index 1671c25142..c22efafe74 100755 --- a/bip-0352/reference.py +++ b/bip-0352/reference.py @@ -14,7 +14,7 @@ from secp256k1lab.util import tagged_hash, hash_sha256 -from bech32m import convertbits, bech32_encode, decode, Encoding +from bech32m import bech32_decode, bech32_encode, convertbits, Encoding from bitcoin_utils import ( deser_txid, from_hex, @@ -116,11 +116,20 @@ def create_labeled_silent_payment_address(b_scan: Scalar, B_spend: GE, m: int, h def decode_silent_payment_address(address: str, hrp: str = "tsp") -> Tuple[GE, GE]: - _, data = decode(hrp, address) - if data is None: + hrpgot, data, spec = bech32_decode(address) + if hrpgot != hrp or spec != Encoding.BECH32M or data is None or not data: return GE(), GE() - B_scan = GE.from_bytes_compressed(data[:33]) - B_spend = GE.from_bytes_compressed(data[33:]) + + version = data[0] + if version == 31: + return GE(), GE() + + decoded = convertbits(data[1:], 5, 8, False) + if decoded is None or len(decoded) < 66 or (version == 0 and len(decoded) != 66): + return GE(), GE() + + B_scan = GE.from_bytes_compressed(decoded[:33]) + B_spend = GE.from_bytes_compressed(decoded[33:66]) return B_scan, B_spend diff --git a/bip-0352/test_address_decoding.py b/bip-0352/test_address_decoding.py new file mode 100644 index 0000000000..0094ae4534 --- /dev/null +++ b/bip-0352/test_address_decoding.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +import unittest + +from bech32m import Encoding, bech32_encode, convertbits +from reference import decode_silent_payment_address +from secp256k1lab.secp256k1 import GE + + +SCAN_KEY = bytes.fromhex("0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4") +SPEND_KEY = bytes.fromhex("025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36") +PAYLOAD = SCAN_KEY + SPEND_KEY + + +def make_address(version, payload=PAYLOAD, spec=Encoding.BECH32M): + data = convertbits(payload, 8, 5) + return bech32_encode("tsp", [version] + data, spec) + + +class SilentPaymentAddressDecodingTest(unittest.TestCase): + def test_accepts_higher_forward_compatible_version(self): + scan, spend = decode_silent_payment_address(make_address(17)) + self.assertEqual(scan, GE.from_bytes_compressed(SCAN_KEY)) + self.assertEqual(spend, GE.from_bytes_compressed(SPEND_KEY)) + + def test_discards_forward_compatible_extension_data(self): + scan, spend = decode_silent_payment_address(make_address(1, PAYLOAD + b"extension")) + self.assertEqual(scan, GE.from_bytes_compressed(SCAN_KEY)) + self.assertEqual(spend, GE.from_bytes_compressed(SPEND_KEY)) + + def test_rejects_bech32_for_version_zero(self): + scan, spend = decode_silent_payment_address(make_address(0, spec=Encoding.BECH32)) + self.assertTrue(scan.infinity) + self.assertTrue(spend.infinity) + + def test_rejects_extra_data_for_version_zero(self): + scan, spend = decode_silent_payment_address(make_address(0, PAYLOAD + b"extension")) + self.assertTrue(scan.infinity) + self.assertTrue(spend.infinity) + + def test_rejects_version_31(self): + scan, spend = decode_silent_payment_address(make_address(31)) + self.assertTrue(scan.infinity) + self.assertTrue(spend.infinity) + + +if __name__ == "__main__": + unittest.main()