|
41 | 41 | parse_post_transaction_callback, |
42 | 42 | parse_pubkey_response, |
43 | 43 | verify_pay_request_signature, |
| 44 | + verify_pay_request_backing_signatures, |
44 | 45 | verify_pay_req_response_signature, |
| 46 | + verify_pay_req_response_backing_signatures, |
45 | 47 | verify_post_transaction_callback_signature, |
46 | 48 | verify_uma_lnurlp_query_signature, |
| 49 | + verify_uma_lnurlp_query_backing_signatures, |
47 | 50 | verify_uma_lnurlp_response_signature, |
| 51 | + verify_uma_lnurlp_response_backing_signatures, |
48 | 52 | verify_uma_invoice_signature, |
49 | 53 | ) |
50 | 54 | from uma.uma_invoice_creator import IUmaInvoiceCreator |
@@ -146,6 +150,65 @@ def test_pay_request_create_and_parse() -> None: |
146 | 150 | ) |
147 | 151 |
|
148 | 152 |
|
| 153 | +def test_sign_and_verify_payreq_backing_signatures() -> None: |
| 154 | + sender_private_key = generate_key() |
| 155 | + receiver_private_key = generate_key() |
| 156 | + backing_vasp_private_key = generate_key() |
| 157 | + receiver_pubkey_response = _create_pubkey_response( |
| 158 | + receiver_private_key, receiver_private_key |
| 159 | + ) |
| 160 | + payer_identifier = "[email protected]" |
| 161 | + payer_compliance_data = create_compliance_payer_data( |
| 162 | + signing_private_key=sender_private_key.secret, |
| 163 | + receiver_encryption_pubkey=receiver_pubkey_response.get_encryption_pubkey(), |
| 164 | + payer_identifier=payer_identifier, |
| 165 | + travel_rule_info=None, |
| 166 | + payer_kyc_status=KycStatus.VERIFIED, |
| 167 | + payer_utxos=["abcdef12345"], |
| 168 | + payer_node_pubkey="dummy_node_key", |
| 169 | + utxo_callback="/api/lnurl/utxocallback?txid=1234", |
| 170 | + ) |
| 171 | + |
| 172 | + pay_request = create_pay_request( |
| 173 | + receiving_currency_code="USD", |
| 174 | + is_amount_in_receiving_currency=True, |
| 175 | + amount=1000, |
| 176 | + payer_identifier=payer_identifier, |
| 177 | + uma_major_version=1, |
| 178 | + payer_name=None, |
| 179 | + payer_email=None, |
| 180 | + payer_compliance=payer_compliance_data, |
| 181 | + ) |
| 182 | + pay_request_without_backing_signature = parse_pay_request(pay_request.to_json()) |
| 183 | + |
| 184 | + # append backing signature |
| 185 | + backing_domain = "backingvasp.com" |
| 186 | + pay_request_without_backing_signature.append_backing_signature( |
| 187 | + backing_vasp_private_key.secret, backing_domain |
| 188 | + ) |
| 189 | + pay_request_with_backing_signature = parse_pay_request( |
| 190 | + pay_request_without_backing_signature.to_json() |
| 191 | + ) |
| 192 | + |
| 193 | + # verify backing signature |
| 194 | + compliance = compliance_from_payer_data( |
| 195 | + none_throws(pay_request_with_backing_signature.payer_data) |
| 196 | + ) |
| 197 | + assert compliance is not None |
| 198 | + assert compliance.backing_signatures is not None |
| 199 | + assert len(compliance.backing_signatures) == 1 |
| 200 | + public_key_cache = InMemoryPublicKeyCache() |
| 201 | + backing_vasp_pubkey_response = _create_pubkey_response( |
| 202 | + backing_vasp_private_key, backing_vasp_private_key |
| 203 | + ) |
| 204 | + public_key_cache.add_public_key_for_vasp( |
| 205 | + backing_domain, backing_vasp_pubkey_response |
| 206 | + ) |
| 207 | + verify_pay_request_backing_signatures( |
| 208 | + pay_request_with_backing_signature, public_key_cache |
| 209 | + ) |
| 210 | + |
| 211 | + |
149 | 212 | def test_lnurlp_query_missing_params() -> None: |
150 | 213 | url = "https://vasp2.com/.well-known/lnurlp/bob?nonce=12345&vaspDomain=vasp1.com&umaVersion=1.0&isSubjectToTravelRule=true×tamp=12345678" |
151 | 214 | assert not is_uma_lnurlp_query(url) |
@@ -539,6 +602,83 @@ def test_pay_req_with_locked_sending_amount() -> None: |
539 | 602 | ) |
540 | 603 |
|
541 | 604 |
|
| 605 | +def test_sign_and_verify_payreq_response_backing_signatures() -> None: |
| 606 | + sender_private_key = generate_key() |
| 607 | + receiver_private_key = generate_key() |
| 608 | + backing_vasp_private_key = generate_key() |
| 609 | + receiver_pubkey_response = _create_pubkey_response( |
| 610 | + receiver_private_key, receiver_private_key |
| 611 | + ) |
| 612 | + currency_code = "USD" |
| 613 | + payer_identifier = "[email protected]" |
| 614 | + payee_identifier = "[email protected]" |
| 615 | + pay_request = create_pay_request( |
| 616 | + receiving_currency_code=currency_code, |
| 617 | + is_amount_in_receiving_currency=True, |
| 618 | + amount=1000, |
| 619 | + payer_identifier=payer_identifier, |
| 620 | + uma_major_version=1, |
| 621 | + payer_name=None, |
| 622 | + payer_email=None, |
| 623 | + payer_compliance=create_compliance_payer_data( |
| 624 | + signing_private_key=sender_private_key.secret, |
| 625 | + receiver_encryption_pubkey=receiver_pubkey_response.get_encryption_pubkey(), |
| 626 | + payer_identifier=payer_identifier, |
| 627 | + travel_rule_info="some TR info for VASP2", |
| 628 | + payer_kyc_status=KycStatus.VERIFIED, |
| 629 | + payer_utxos=["abcdef12345"], |
| 630 | + payer_node_pubkey="dummy_node_key", |
| 631 | + utxo_callback="/api/lnurl/utxocallback?txid=1234", |
| 632 | + ), |
| 633 | + ) |
| 634 | + response = create_pay_req_response( |
| 635 | + request=pay_request, |
| 636 | + invoice_creator=DummyUmaInvoiceCreator(), |
| 637 | + metadata=_create_metadata(), |
| 638 | + receiving_currency_code=currency_code, |
| 639 | + receiving_currency_decimals=2, |
| 640 | + msats_per_currency_unit=24_150, |
| 641 | + receiver_fees_msats=2_000, |
| 642 | + receiver_utxos=["abcdef12345"], |
| 643 | + receiver_node_pubkey="dummy_pub_key", |
| 644 | + utxo_callback="/api/lnurl/utxocallback?txid=1234", |
| 645 | + payee_identifier=payee_identifier, |
| 646 | + signing_private_key=receiver_private_key.secret, |
| 647 | + ) |
| 648 | + response_without_backing_signature = parse_pay_req_response(response.to_json()) |
| 649 | + |
| 650 | + # append backing signature |
| 651 | + backing_domain = "backingvasp.com" |
| 652 | + response_without_backing_signature.append_backing_signature( |
| 653 | + backing_vasp_private_key.secret, |
| 654 | + backing_domain, |
| 655 | + payer_identifier, |
| 656 | + payee_identifier, |
| 657 | + ) |
| 658 | + response_with_backing_signature = parse_pay_req_response( |
| 659 | + response_without_backing_signature.to_json() |
| 660 | + ) |
| 661 | + |
| 662 | + # verify backing signatures |
| 663 | + compliance = response_with_backing_signature.get_compliance() |
| 664 | + assert compliance is not None |
| 665 | + assert compliance.backing_signatures is not None |
| 666 | + assert len(compliance.backing_signatures) == 1 |
| 667 | + public_key_cache = InMemoryPublicKeyCache() |
| 668 | + backing_vasp_pubkey_response = _create_pubkey_response( |
| 669 | + backing_vasp_private_key, backing_vasp_private_key |
| 670 | + ) |
| 671 | + public_key_cache.add_public_key_for_vasp( |
| 672 | + backing_domain, backing_vasp_pubkey_response |
| 673 | + ) |
| 674 | + verify_pay_req_response_backing_signatures( |
| 675 | + response_with_backing_signature, |
| 676 | + public_key_cache, |
| 677 | + payer_identifier, |
| 678 | + payee_identifier, |
| 679 | + ) |
| 680 | + |
| 681 | + |
542 | 682 | def _create_metadata() -> str: |
543 | 683 | metadata = [ |
544 | 684 | ["text/plain", "Pay to vasp2.com user $bob"], |
@@ -741,6 +881,70 @@ def test_parse_v0_lnurlp_response() -> None: |
741 | 881 | ) |
742 | 882 |
|
743 | 883 |
|
| 884 | +def test_sign_and_verify_lnurlp_response_with_backing_signature() -> None: |
| 885 | + sender_private_key = generate_key() |
| 886 | + receiver_private_key = generate_key() |
| 887 | + backing_vasp_private_key = generate_key() |
| 888 | + payer_data_options = create_counterparty_data_options( |
| 889 | + {"name": False, "email": False, "compliance": True, "identifier": True} |
| 890 | + ) |
| 891 | + currencies = [ |
| 892 | + Currency( |
| 893 | + code="USD", |
| 894 | + name="US Dollar", |
| 895 | + symbol="$", |
| 896 | + millisatoshi_per_unit=34_150, |
| 897 | + min_sendable=1, |
| 898 | + max_sendable=10_000_000, |
| 899 | + decimals=2, |
| 900 | + uma_major_version=0, |
| 901 | + ) |
| 902 | + ] |
| 903 | + lnurlp_request_url = create_uma_lnurlp_request_url( |
| 904 | + signing_private_key=sender_private_key.secret, |
| 905 | + receiver_address="[email protected]", |
| 906 | + sender_vasp_domain="vasp1.com", |
| 907 | + is_subject_to_travel_rule=True, |
| 908 | + ) |
| 909 | + lnurlp_request = parse_lnurlp_request(lnurlp_request_url) |
| 910 | + response = create_uma_lnurlp_response( |
| 911 | + request=lnurlp_request, |
| 912 | + signing_private_key=receiver_private_key.secret, |
| 913 | + requires_travel_rule_info=True, |
| 914 | + callback="https://vasp2.com/api/lnurl/payreq/$bob", |
| 915 | + encoded_metadata='["text/plain","Pay to Bob"]', |
| 916 | + min_sendable_sats=1, |
| 917 | + max_sendable_sats=10_000_000, |
| 918 | + payer_data_options=payer_data_options, |
| 919 | + currency_options=currencies, |
| 920 | + receiver_kyc_status=KycStatus.VERIFIED, |
| 921 | + ) |
| 922 | + |
| 923 | + # append backing signature |
| 924 | + response_without_backing_signature = parse_lnurlp_response(response.to_json()) |
| 925 | + backing_domain = "backingvasp.com" |
| 926 | + response_without_backing_signature.append_backing_signature( |
| 927 | + backing_vasp_private_key.secret, |
| 928 | + backing_domain, |
| 929 | + ) |
| 930 | + |
| 931 | + # verify backing signature |
| 932 | + result_response = parse_lnurlp_response( |
| 933 | + response_without_backing_signature.to_json() |
| 934 | + ) |
| 935 | + assert result_response.compliance is not None |
| 936 | + assert result_response.compliance.backing_signatures is not None |
| 937 | + assert len(result_response.compliance.backing_signatures) == 1 |
| 938 | + public_key_cache = InMemoryPublicKeyCache() |
| 939 | + backing_vasp_pubkey_response = _create_pubkey_response( |
| 940 | + backing_vasp_private_key, backing_vasp_private_key |
| 941 | + ) |
| 942 | + public_key_cache.add_public_key_for_vasp( |
| 943 | + backing_domain, backing_vasp_pubkey_response |
| 944 | + ) |
| 945 | + verify_uma_lnurlp_response_backing_signatures(result_response, public_key_cache) |
| 946 | + |
| 947 | + |
744 | 948 | def test_invalid_lnurlp_signature() -> None: |
745 | 949 | private_key = generate_key() |
746 | 950 | pubkey_response = _create_pubkey_response(private_key, private_key) |
@@ -801,6 +1005,39 @@ def test_lnurlp_signature_too_old() -> None: |
801 | 1005 | verify_uma_lnurlp_query_signature(lnurlp_request, pubkey_response, nonce_cache) |
802 | 1006 |
|
803 | 1007 |
|
| 1008 | +def test_sign_and_verify_lnurlp_request_with_backing_signature() -> None: |
| 1009 | + sender_private_key = generate_key() |
| 1010 | + backing_vasp_private_key = generate_key() |
| 1011 | + lnurlp_request_url = create_uma_lnurlp_request_url( |
| 1012 | + signing_private_key=sender_private_key.secret, |
| 1013 | + receiver_address="[email protected]", |
| 1014 | + sender_vasp_domain="vasp1.com", |
| 1015 | + is_subject_to_travel_rule=True, |
| 1016 | + ) |
| 1017 | + |
| 1018 | + # append backing signature |
| 1019 | + lnurlp_request = parse_lnurlp_request(lnurlp_request_url) |
| 1020 | + backing_domain = "backingvasp.com" |
| 1021 | + lnurlp_request.append_backing_signature( |
| 1022 | + backing_vasp_private_key.secret, |
| 1023 | + backing_domain, |
| 1024 | + ) |
| 1025 | + lnurlp_request_url = lnurlp_request.encode_to_url() |
| 1026 | + |
| 1027 | + # verify backing signature |
| 1028 | + lnurlp_request = parse_lnurlp_request(lnurlp_request_url) |
| 1029 | + public_key_cache = InMemoryPublicKeyCache() |
| 1030 | + backing_vasp_pubkey_response = _create_pubkey_response( |
| 1031 | + backing_vasp_private_key, backing_vasp_private_key |
| 1032 | + ) |
| 1033 | + public_key_cache.add_public_key_for_vasp( |
| 1034 | + backing_domain, backing_vasp_pubkey_response |
| 1035 | + ) |
| 1036 | + assert lnurlp_request.backing_signatures is not None |
| 1037 | + assert len(lnurlp_request.backing_signatures) == 1 |
| 1038 | + verify_uma_lnurlp_query_backing_signatures(lnurlp_request, public_key_cache) |
| 1039 | + |
| 1040 | + |
804 | 1041 | def test_high_signature_normalization() -> None: |
805 | 1042 | pub_key_bytes = bytes.fromhex( |
806 | 1043 | "047d37ce263a855ff49eb2a537a77a369a861507687bfde1df40062c8774488d644455a44baeb5062b79907d2e6f9692dd5b7bd7c37a3721ba21378d3594672063" |
|
0 commit comments