7 changed files with 128 additions and 11 deletions
@ -0,0 +1,41 @@ |
|||||||
|
# https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki |
||||||
|
# bitcoin:<address>[?amount=<amount>][?label=<label>][?message=<message>] |
||||||
|
# We don't check validity of Bitcoin address here, as all the tools using |
||||||
|
# this are expected to do address validation independently anyway. |
||||||
|
|
||||||
|
from jmbitcoin import amount_to_sat |
||||||
|
from urllib.parse import parse_qs, urlparse |
||||||
|
from url_decode import urldecode |
||||||
|
import re |
||||||
|
|
||||||
|
|
||||||
|
def is_bip21_uri(uri): |
||||||
|
parsed = urlparse(uri) |
||||||
|
return parsed.scheme == 'bitcoin' and parsed.path != '' |
||||||
|
|
||||||
|
|
||||||
|
def is_bip21_amount_str(amount): |
||||||
|
return re.compile("^[0-9]{1,8}(\.[0-9]{1,8})?$").match(amount) != None |
||||||
|
|
||||||
|
|
||||||
|
def decode_bip21_uri(uri): |
||||||
|
if not is_bip21_uri(uri): |
||||||
|
raise ValueError("Not a valid BIP21 URI: " + uri) |
||||||
|
result = {} |
||||||
|
parsed = urlparse(uri) |
||||||
|
result['address'] = parsed.path |
||||||
|
params = parse_qs(parsed.query) |
||||||
|
for key in params: |
||||||
|
if key.startswith('req-'): |
||||||
|
raise ValueError("Unknown required parameter " + key + |
||||||
|
" in BIP21 URI.") |
||||||
|
if key == 'amount': |
||||||
|
amount_str = params['amount'][0] |
||||||
|
if not is_bip21_amount_str(amount_str): |
||||||
|
raise ValueError("Invalid BTC amount " + amount_str + |
||||||
|
" vs. " + str(amount_to_sat(amount_str)) + " sat") |
||||||
|
# Convert amount to sats, as used internally by JM |
||||||
|
result['amount'] = amount_to_sat(amount_str + "btc") |
||||||
|
else: |
||||||
|
result[key] = urldecode(params[key][0]) |
||||||
|
return result |
||||||
@ -0,0 +1,58 @@ |
|||||||
|
import jmbitcoin as btc |
||||||
|
import pytest |
||||||
|
|
||||||
|
|
||||||
|
def test_bip21(): |
||||||
|
|
||||||
|
# These should raise exception because of not being valid BIP21 URI's |
||||||
|
with pytest.raises(ValueError): |
||||||
|
btc.decode_bip21_uri('') |
||||||
|
btc.decode_bip21_uri('nfdjksnfjkdsnfjkds') |
||||||
|
btc.decode_bip21_uri('175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W') |
||||||
|
btc.decode_bip21_uri( |
||||||
|
'175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=20.3') |
||||||
|
btc.decode_bip21_uri('bitcoin:') |
||||||
|
btc.decode_bip21_uri('bitcoin:?amount=20.3') |
||||||
|
btc.decode_bip21_uri( |
||||||
|
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=') |
||||||
|
btc.decode_bip21_uri( |
||||||
|
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=XYZ') |
||||||
|
btc.decode_bip21_uri( |
||||||
|
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=100\'000') |
||||||
|
btc.decode_bip21_uri( |
||||||
|
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=100,000') |
||||||
|
btc.decode_bip21_uri( |
||||||
|
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=100000000') |
||||||
|
|
||||||
|
assert(btc.decode_bip21_uri('bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W' |
||||||
|
)['address'] == '175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W') |
||||||
|
|
||||||
|
parsed = btc.decode_bip21_uri( |
||||||
|
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?label=Luke-Jr') |
||||||
|
assert(parsed['address'] == '175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W') |
||||||
|
assert(parsed['label'] == 'Luke-Jr') |
||||||
|
|
||||||
|
parsed = btc.decode_bip21_uri( |
||||||
|
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=20.3&label=Luke-Jr') |
||||||
|
assert(parsed['address'] == '175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W') |
||||||
|
assert(parsed['amount'] == 2030000000) |
||||||
|
assert(parsed['label'] == 'Luke-Jr') |
||||||
|
|
||||||
|
parsed = btc.decode_bip21_uri( |
||||||
|
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=50&label=Luke-Jr&message=Donation%20for%20project%20xyz') |
||||||
|
assert(parsed['address'] == '175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W') |
||||||
|
assert(parsed['amount'] == 5000000000) |
||||||
|
assert(parsed['label'] == 'Luke-Jr') |
||||||
|
assert(parsed['message'] == 'Donation for project xyz') |
||||||
|
|
||||||
|
# This should raise exception because of unknown req-* parameters |
||||||
|
with pytest.raises(ValueError): |
||||||
|
btc.decode_bip21_uri( |
||||||
|
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?req-somethingyoudontunderstand=50&req-somethingelseyoudontget=999') |
||||||
|
|
||||||
|
parsed = btc.decode_bip21_uri( |
||||||
|
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?somethingyoudontunderstand=50&somethingelseyoudontget=999') |
||||||
|
assert(parsed['address'] == '175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W') |
||||||
|
assert(parsed['somethingyoudontunderstand'] == '50') |
||||||
|
assert(parsed['somethingelseyoudontget'] == '999') |
||||||
|
|
||||||
Loading…
Reference in new issue