Browse Source

Add BIP21 support for Payjoin

master
Kristaps Kaupe 6 years ago
parent
commit
4eced504b2
No known key found for this signature in database
GPG Key ID: D47B1B4232B55437
  1. 22
      jmbitcoin/jmbitcoin/bip21.py
  2. 1
      jmbitcoin/setup.py
  3. 60
      jmbitcoin/test/test_bip21.py
  4. 8
      jmclient/jmclient/maker.py
  5. 2
      scripts/sendpayment.py

22
jmbitcoin/jmbitcoin/bip21.py

@ -4,7 +4,7 @@
# this are expected to do address validation independently anyway.
from jmbitcoin import amount_to_sat
from urllib.parse import parse_qs, urlparse
from urllib.parse import quote, parse_qs, urlencode, urlparse
from url_decode import urldecode
import re
@ -15,7 +15,12 @@ def is_bip21_uri(uri):
def is_bip21_amount_str(amount):
return re.compile("^[0-9]{1,8}(\.[0-9]{1,8})?$").match(amount) != None
return re.compile("^[0-9]{1,8}(\.[0-9]{1,8})?$").match(str(amount)) != None
def validate_bip21_amount(amount):
if not is_bip21_amount_str(amount):
raise ValueError("Invalid BTC amount " + str(amount))
def decode_bip21_uri(uri):
@ -31,11 +36,18 @@ def decode_bip21_uri(uri):
" 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")
validate_bip21_amount(amount_str)
# 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
def encode_bip21_uri(address, params):
uri = 'bitcoin:' + address
if len(params) > 0:
if 'amount' in params:
validate_bip21_amount(params['amount'])
uri += '?' + urlencode(params, quote_via=quote)
return uri

1
jmbitcoin/setup.py

@ -10,4 +10,5 @@ setup(name='joinmarketbitcoin',
license='GPL',
packages=['jmbitcoin'],
install_requires=['future', 'coincurve', 'urldecode'],
python_requires='>=3.5',
zip_safe=False)

60
jmbitcoin/test/test_bip21.py

@ -2,7 +2,7 @@ import jmbitcoin as btc
import pytest
def test_bip21():
def test_bip21_decode():
# These should raise exception because of not being valid BIP21 URI's
with pytest.raises(ValueError):
@ -56,3 +56,61 @@ def test_bip21():
assert(parsed['somethingyoudontunderstand'] == '50')
assert(parsed['somethingelseyoudontget'] == '999')
def test_bip21_encode():
assert(
btc.encode_bip21_uri('175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W', {}) ==
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W'
)
assert(
btc.encode_bip21_uri('175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W', {
'label': 'Luke-Jr'
}) ==
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?label=Luke-Jr'
)
assert(
btc.encode_bip21_uri('175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W', {
'amount': 20.3,
'label': 'Luke-Jr'
}) ==
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=20.3&label=Luke-Jr'
)
assert(
btc.encode_bip21_uri('175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W', {
'amount': 50,
'label': 'Luke-Jr',
'message': 'Donation for project xyz'
}) ==
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?amount=50&label=Luke-Jr&message=Donation%20for%20project%20xyz'
)
assert(
btc.encode_bip21_uri('175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W', {
'req-somethingyoudontunderstand': 50,
'req-somethingelseyoudontget': 999
}) ==
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?req-somethingyoudontunderstand=50&req-somethingelseyoudontget=999'
)
assert(
btc.encode_bip21_uri('175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W', {
'somethingyoudontunderstand': 50,
'somethingelseyoudontget': 999
}) ==
'bitcoin:175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W?somethingyoudontunderstand=50&somethingelseyoudontget=999'
)
# Invalid amounts must raise ValueError
with pytest.raises(ValueError):
btc.encode_bip21_uri('175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W', {
'amount': ''
})
btc.encode_bip21_uri('175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W', {
'amount': 'XYZ'
})
btc.encode_bip21_uri('175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W', {
'amount': '100\'000'
})
btc.encode_bip21_uri('175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W', {
'amount': '100,000'
})
btc.encode_bip21_uri('175tWpb8K1S7NmH4Zx6rewF9WQrcZv245W', {
'amount': '100000000'
})

8
jmclient/jmclient/maker.py

@ -328,10 +328,16 @@ class P2EPMaker(Maker):
self.receiving_amount) + " satoshis.")
self.user_info("The sender also needs to know your ephemeral "
"nickname: " + jm_single().nickname)
self.user_info("This information has been stored in a file payjoin.txt;"
receive_uri = btc.encode_bip21_uri(self.destination_addr, {
'amount': btc.sat_to_btc(self.receiving_amount),
'jmnick': jm_single().nickname
})
self.user_info("Receive URI: " + receive_uri)
self.user_info("This information has also been stored in a file payjoin.txt;"
" send it to your counterparty when you are ready.")
with open("payjoin.txt", "w") as f:
f.write("Payjoin transfer details:\n\n")
f.write("Receive URI: " + receive_uri + "\n")
f.write("Address: " + self.destination_addr + "\n")
f.write("Amount (in sats): " + str(self.receiving_amount) + "\n")
f.write("Receiver nick: " + jm_single().nickname + "\n")

2
scripts/sendpayment.py

@ -74,6 +74,8 @@ def main():
parser.error("Given BIP21 URI does not contain amount.")
sys.exit(EXIT_ARGERROR)
destaddr = parsed['address']
if 'jmnick' in parsed:
options.p2ep = parsed['jmnick']
else:
amount = btc.amount_to_sat(args[1])
if amount == 0:

Loading…
Cancel
Save