diff --git a/lib/services/breezlib/breez_bridge.dart b/lib/services/breezlib/breez_bridge.dart index a463e5b04..2bd581ad4 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'; @@ -774,15 +775,9 @@ class BreezBridge { } Future validateAddress(String address) { - String addr = address; - if (address == null) { - return Future.error("empty address"); - } - if (addr.toLowerCase().startsWith("bitcoin:")) { - addr = addr.substring(8); - } - return _invokeMethodWhenReady("validateAddress", {"argument": addr}) - .then((response) => addr); + address = extractBitcoinAddress(address); + return _invokeMethodWhenReady("validateAddress", {"argument": address}) + .then((response) => address); } Future getDefaultOnChainFeeRate() { diff --git a/lib/utils/bip21.dart b/lib/utils/bip21.dart index 7e3b851d9..755719052 100644 --- a/lib/utils/bip21.dart +++ b/lib/utils/bip21.dart @@ -13,3 +13,29 @@ String extractBolt11FromBip21(String bip21) { } return null; } + +const String URN_SCHEME = "bitcoin:"; + +String extractBitcoinAddress(String address) { + if (address.toLowerCase().startsWith(URN_SCHEME)) { + try { + int split = address.indexOf("?"); + String addr = + address.substring(URN_SCHEME.length, split == -1 ? null : split); + if (addr.isNotEmpty) { + isLegacyOrNestedSegwit(addr) ? addr : addr = addr.toLowerCase(); + return addr; + } + } on FormatException { + // do nothing. + } + } + return address; +} + +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..67662b5aa 100644 --- a/test/invoice_test.dart +++ b/test/invoice_test.dart @@ -1,31 +1,75 @@ +import 'dart:math'; + 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 = extractBitcoinAddress( + "bitcoin:1DamianM2k8WfNEeJmyqSe2YW1upB7UATx?amount=0.000001&lightning=1234"); + expect(bitcoinAddress, "1DamianM2k8WfNEeJmyqSe2YW1upB7UATx"); + }); + + test("should extract bitcoin address from bip 21 nested segwit", () async { + String bitcoinAddress = extractBitcoinAddress( + "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 = extractBitcoinAddress( + "BITCOIN:BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U?amount=0.00001&label=sbddesign%3A%20For%20lunch%20Tuesday&message=For%20lunch%20Tuesday&lightning=LNBC10U1P3PJ257PP5YZTKWJCZ5FTL5LAXKAV23ZMZEKAW37ZK6KMV80PK4XAEV5QHTZ7QDPDWD3XGER9WD5KWM36YPRX7U3QD36KUCMGYP282ETNV3SHJCQZPGXQYZ5VQSP5USYC4LK9CHSFP53KVCNVQ456GANH60D89REYKDNGSMTJ6YW3NHVQ9QYYSSQJCEWM5CJWZ4A6RFJX77C490YCED6PEMK0UPKXHY89CMM7SCT66K8GNEANWYKZGDRWRFJE69H9U5U0W57RRCSYSAS7GADWMZXC8C6T0SPJAZUP6"); + expect(bitcoinAddress, "bc1qylh3u67j673h6y6alv70m0pl2yz53tzhvxgg7u"); + }); + + test("should extract the same address as it receives", () { + String bitcoinAddress = + extractBitcoinAddress("3K2CfYmqYuD99CDyqrdzt481F9jkLKirEn"); + expect(bitcoinAddress, "3K2CfYmqYuD99CDyqrdzt481F9jkLKirEn"); + }); + + test("should extract the address from bip21 scheme", () async { + String bitcoinAddress = extractBitcoinAddress( + "BITCOIN:BC1QYLH3U67J673H6Y6ALV70M0PL2YZ53TZHVXGG7U"); + expect(bitcoinAddress, "bc1qylh3u67j673h6y6alv70m0pl2yz53tzhvxgg7u"); + }); + + test("should extract the same address as it receives", () { + String bitcoinAddress = + extractBitcoinAddress("BItCOiN:3K2CfYmqYuD99CDyqrdzt481F9jkLKirEn"); + expect(bitcoinAddress, "3K2CfYmqYuD99CDyqrdzt481F9jkLKirEn"); + }); }); -} \ No newline at end of file +}