From 24e5eee796a3e0623ab42bc17dd75e7e05fe2ba4 Mon Sep 17 00:00:00 2001 From: ruben beck Date: Thu, 20 Jul 2023 18:16:18 +0200 Subject: [PATCH] check for bip21 on validating address --- lib/services/breezlib/breez_bridge.dart | 6 +++- lib/utils/bip21.dart | 27 ++++++++++++++++++ test/invoice_test.dart | 38 ++++++++++++++++++++----- 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/lib/services/breezlib/breez_bridge.dart b/lib/services/breezlib/breez_bridge.dart index a463e5b04..1d9ca06e6 100644 --- a/lib/services/breezlib/breez_bridge.dart +++ b/lib/services/breezlib/breez_bridge.dart @@ -6,6 +6,7 @@ import 'package:breez/bloc/lnurl/lnurl_model.dart'; import 'package:breez/logger.dart' as logger; import 'package:breez/services/breezlib/data/messages.pb.dart'; import 'package:breez/services/download_manager.dart'; +import 'package:breez/utils/bip21.dart'; import 'package:dio/dio.dart'; import 'package:fixnum/fixnum.dart'; import 'package:flutter/services.dart'; @@ -778,8 +779,11 @@ class BreezBridge { if (address == null) { return Future.error("empty address"); } - if (addr.toLowerCase().startsWith("bitcoin:")) { + addr = extractBitcoinAddressFromBip21(address) ?? addr; + + if (addr.startsWith("bitcoin:") || addr.startsWith("BITCOIN:")) { addr = addr.substring(8); + isLegacyOrNestedSegwit(addr) ? addr : addr.toLowerCase(); } return _invokeMethodWhenReady("validateAddress", {"argument": addr}) .then((response) => addr); diff --git a/lib/utils/bip21.dart b/lib/utils/bip21.dart index 7e3b851d9..50ebe94be 100644 --- a/lib/utils/bip21.dart +++ b/lib/utils/bip21.dart @@ -13,3 +13,30 @@ String extractBolt11FromBip21(String bip21) { } return null; } + +String extractBitcoinAddressFromBip21(String bip21) { + String urnScheme = "bitcoin"; + if (bip21.startsWith("bitcoin:") || bip21.startsWith("BITCOIN:")) { + try { + int split = bip21.indexOf("?"); + String address = + bip21.substring(urnScheme.length + 1, split == -1 ? null : split); + if (address.isNotEmpty) { + isLegacyOrNestedSegwit(address) + ? address + : address = address.toLowerCase(); + return address; + } + } on FormatException { + // do nothing. + } + } + return null; +} + +bool isLegacyOrNestedSegwit(String address) { + if (address.startsWith(RegExp("^[1 | 3]"))) { + return true; + } + return false; +} diff --git a/test/invoice_test.dart b/test/invoice_test.dart index a894b5d7c..540a4bb4d 100644 --- a/test/invoice_test.dart +++ b/test/invoice_test.dart @@ -2,30 +2,54 @@ import 'package:breez/utils/bip21.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - group('invoice_tests', () { + group('invoice_tests', () { test("should extract bolt11 from bip21 full info", () async { - String bolt11 = extractBolt11FromBip21("bitcoin:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?amount=0.000001&lightning=1234"); + String bolt11 = extractBolt11FromBip21( + "bitcoin:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?amount=0.000001&lightning=1234"); expect(bolt11, "1234"); }); test("should extract bolt11 from bip21 no amount", () async { - String bolt11 = extractBolt11FromBip21("bitcoin:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?lightning=1234"); + String bolt11 = extractBolt11FromBip21( + "bitcoin:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?lightning=1234"); expect(bolt11, "1234"); }); test("should extract bolt11 from bip21 amount last", () async { - String bolt11 = extractBolt11FromBip21("bitcoin:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?lightning=1234&amount=0.000001"); + String bolt11 = extractBolt11FromBip21( + "bitcoin:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?lightning=1234&amount=0.000001"); expect(bolt11, "1234"); }); test("should extract bolt11 from bip21 upper case", () async { - String bolt11 = extractBolt11FromBip21("BITCOIN:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?amount=0.000001&lightning=1234"); + String bolt11 = extractBolt11FromBip21( + "BITCOIN:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?amount=0.000001&lightning=1234"); expect(bolt11, "1234"); }); test("should extract bolt11 from bip21 negative, no lightning", () async { - String bolt11 = extractBolt11FromBip21("bitcoin:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?amount=0.000001"); + String bolt11 = extractBolt11FromBip21( + "bitcoin:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?amount=0.000001"); expect(bolt11, null); }); + + test("should extract bitcoin addresss from bip21 full info legacy address", + () async { + String bitcoinAddress = extractBitcoinAddressFromBip21( + "bitcoin:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?amount=0.000001&lightning=1234"); + expect(bitcoinAddress, "1DamianM2k8WfNEeJmyqSe2YW1upB7UATx"); + }); + + test("should extract bitcoin address from bip 21 nested segwit", () async { + String bitcoinAddress = extractBitcoinAddressFromBip21( + "bitcoin:3K2CfYmqYuD99CDyqrdzt481F9jkLKirEn?amount=0.00001&label=sbddesign%3A%20For%20lunch%20Tuesday&message=For%20lunch%20Tuesday&lightning=LNBC10U1P3PJ257PP5YZTKWJCZ5FTL5LAXKAV23ZMZEKAW37ZK6KMV80PK4XAEV5QHTZ7QDPDWD3XGER9WD5KWM36YPRX7U3QD36KUCMGYP282ETNV3SHJCQZPGXQYZ5VQSP5USYC4LK9CHSFP53KVCNVQ456GANH60D89REYKDNGSMTJ6YW3NHVQ9QYYSSQJCEWM5CJWZ4A6RFJX77C490YCED6PEMK0UPKXHY89CMM7SCT66K8GNEANWYKZGDRWRFJE69H9U5U0W57RRCSYSAS7GADWMZXC8C6T0SPJAZUP6"); + expect(bitcoinAddress, "3K2CfYmqYuD99CDyqrdzt481F9jkLKirEn"); + }); + + test("should extract bitcoin address from bip 21 bech32", () async { + String bitcoinAddress = extractBitcoinAddressFromBip21( + "BITCOIN:BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U?amount=0.00001&label=sbddesign%3A%20For%20lunch%20Tuesday&message=For%20lunch%20Tuesday&lightning=LNBC10U1P3PJ257PP5YZTKWJCZ5FTL5LAXKAV23ZMZEKAW37ZK6KMV80PK4XAEV5QHTZ7QDPDWD3XGER9WD5KWM36YPRX7U3QD36KUCMGYP282ETNV3SHJCQZPGXQYZ5VQSP5USYC4LK9CHSFP53KVCNVQ456GANH60D89REYKDNGSMTJ6YW3NHVQ9QYYSSQJCEWM5CJWZ4A6RFJX77C490YCED6PEMK0UPKXHY89CMM7SCT66K8GNEANWYKZGDRWRFJE69H9U5U0W57RRCSYSAS7GADWMZXC8C6T0SPJAZUP6"); + expect(bitcoinAddress, "bc1qylh3u67j673h6y6alv70m0pl2yz53tzhvxgg7u"); + }); }); -} \ No newline at end of file +}