最終更新:
編集

Verify Credentials in Python

This tutorial describes how to verify that an account holds a valid credential on the XRP Ledger, which has different use cases depending on the type of credential and the meaning behind it. A few possible reasons to verify a credential include:

  • Confirming that a recipient has passed a background check before sending a payment.
  • Checking a person's professional certifications, after verifying their identity with a DID.
  • Displaying a player's achievements in a blockchain-connected game.

This tutorial uses sample code in Python using the xrpl-py library.

Prerequisites

  • You must have Python installed and know how to run Python code from the command line. Python 3.8 or later is required for xrpl-py.
  • You should have a basic understanding of the XRP Ledger.
  • The credential you want to verify should exist in the ledger already, and you should know the addresses of both the issuer and the holder, as well as the official credential type you want to check.

Setup

First, download the complete sample code for this tutorial from GitHub:

Then, in the appropriate directory, set up a virtual environment and install dependencies:

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

This installs the appropriate version of the xrpl-py library. There are no other dependencies for this tutorial outside of the Python standard library.

Overview

The Verify Credential sample code consists of one file, verify_credential.py, and contains two main parts:

  1. A function, verify_credential(...) which can be called with appropriate arguments to verify that a credential exists and is valid. This function can be imported into other code to be used as part of a larger application.
  2. A commandline utility that can be used to verify any credential.

Usage

To test that you have the code installed and working properly, you can run the commandline utility with no arguments to check the existence of a default credential on Devnet, such as:

python verify_credential.py

If all goes well, you should see output such as the following:

Encoded credential_type as hex: 6D795F63726564656E7469616C
Looking up credential...
{'ledger_index': 'validated', 'method': 'ledger_entry', 'api_version': 2, 'credential': {'subject': 'rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA', 'issuer': 'rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS', 'credential_type': '6D795F63726564656E7469616C'}, 'binary': False}
Found credential:
{'CredentialType': '6D795F63726564656E7469616C', 'Flags': 65536, 'Issuer': 'rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS', 'IssuerNode': '0', 'LedgerEntryType': 'Credential', 'PreviousTxnID': '7D1257779E2D298C07C7E0C73CD446534B143FBD1F13DB268A878E40FD153B9A', 'PreviousTxnLgrSeq': 803275, 'Subject': 'rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA', 'SubjectNode': '0', 'index': '9603F0E204A8B1C61823625682EB0ECE98A4ECF22FF46CD4845FA9BFA3606B24'}
Credential is valid.

If the code reports that the credential was not found when called with no arguments, it's possible that the example credential has been deleted or the Devnet has been reset. If you have another credential you can verify, you can provide the details as commandline arguments. For example:

python verify_credential.py rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA my_credential

A full usage statement is available with the -h flag.

Interactive Shell

You can open an interactive python shell and import the verify_credential function, as in the following example:

>>> from verify_credential import verify_credential
>>> from xrpl.clients import JsonRpcClient
>>> client = JsonRpcClient("https://s.devnet.rippletest.net:51234/")
>>> verify_credential(client, issuer="rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS", subject="rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA", credential_type="my_credential")
True

You can import the verify_credential(...) function into other scripts and use it the same way.

Other Examples

The following examples show other possible scenarios. The data for these examples may or may not still be present in Devnet. For example, anyone can delete an expired credential.

$ ./verify_credential.py rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS rs9DtpwyCSGMCyxiYEvVG29ZXo99iFjZ9S long_lasting_credential

Encoded credential_type as hex: 6C6F6E675F6C617374696E675F63726564656E7469616C
Looking up credential...
{'ledger_index': 'validated', 'method': 'ledger_entry', 'api_version': 2, 'credential': {'subject': 'rs9DtpwyCSGMCyxiYEvVG29ZXo99iFjZ9S', 'issuer': 'rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS', 'credential_type': '6C6F6E675F6C617374696E675F63726564656E7469616C'}, 'binary': False}
Found credential:
{'CredentialType': '6C6F6E675F6C617374696E675F63726564656E7469616C', 'Expiration': 1167724800, 'Flags': 65536, 'Issuer': 'rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS', 'IssuerNode': '0', 'LedgerEntryType': 'Credential', 'PreviousTxnID': 'C65794B7C322F028DB0D2DD72C9FF69D53A676B1608B77ADEF22311AFB22BFF7', 'PreviousTxnLgrSeq': 996934, 'Subject': 'rs9DtpwyCSGMCyxiYEvVG29ZXo99iFjZ9S', 'SubjectNode': '0', 'index': 'FC4BB495DAE7C9F4615174188B3C5F2E337680017BA90E1F126DE08CAD15FD66'}
Credential has expiration: 2037-01-01T08:00:00+00:00
Looking up validated ledger to check for expiration.
Most recent validated ledger is: 2025-03-11T20:01:51+00:00
Credential is valid.

Code Walkthrough

1. Initial setup

The verify_credential.py file implements the code for this tutorial. This file can be run as a commandline tool, so it starts with a shebang. Then it imports dependencies, with standard lib first and then specific parts of the xrpl-py library:

#!/usr/bin/env python

import argparse
import logging
import sys
from binascii import hexlify
from re import match

from xrpl.clients import JsonRpcClient
from xrpl.models.requests import LedgerEntry, Ledger
from xrpl.utils import ripple_time_to_datetime

The next section of the code sets the default log level for messages that might be written to the console through the utility:

# Set up logging --------------------------------------------------------------
# Use WARNING by default in case verify_credential is called from elsewhere.
logger = logging.getLogger("verify_credential")
logger.setLevel(logging.WARNING)
logger.addHandler(logging.StreamHandler(sys.stderr))

Then it defines a type of exception to throw if something goes wrong when connecting to the XRP Ledger:

# Define an error to throw when XRPL lookup fails unexpectedly ----------------
class XRPLLookupError(Exception):
    def __init__(self, xrpl_response):
        self.body = xrpl_response.result

2. Main function

The verify_credential(...) function performs the main work for this tutorial. The function definition and docstring define the parameters:

# Main function ---------------------------------------------------------------
def verify_credential(client:JsonRpcClient, 
                      issuer:str, 
                      subject:str, 
                      credential_type:str="", 
                      credential_type_hex:str=""):
    """
    Check whether an XRPL account holds a specified credential,
    as of the most recently validated ledger.

    Paramters:
        client - JsonRpcClient for the XRPL network to use.
        issuer - Address of the credential issuer, in base58
        subject - Address of the credential holder/subject, in base58
        credential_type - Credential type to check for as a string,
                          which will be encoded as UTF-8 (1-64 bytes long).
        credential_type_hex - Credential type (binary) as hexadecimal.
        verbose - If true, print details to stdout during lookup.
    You must provide either credential_type or credential_type_hex.

    Returns True if the account holds the specified, valid credential.
    Returns False if the credential is missing, expired, or not accepted.
    """
    

The first thing the function does is verify that the user provided a credential type in either the credential_type or credential_type_hex parameter. The XRP Ledger APIs require the credential type to be hexadecimal, so it converts the user input if necessary:

    # Handle function inputs --------------------------------------------------
    if not (credential_type or credential_type_hex):
        raise ValueError("Provide a non-empty credential_type or " +
                         "credential_type_hex")
    if credential_type and credential_type_hex:
        raise ValueError("Provide either credential_type or " +
                         "credential_type_hex, but not both")
    
    # Encode credential_type as uppercase hex, if needed
    if credential_type:
        credential_type_hex = hexlify(credential_type.encode("utf-8")
                                      ).decode("ascii")
        logger.info("Encoded credential_type as hex: "+credential_type_hex.upper())
    credential_type_hex = credential_type_hex.upper()

    if len(credential_type_hex) % 2 or \
            not match(r"[0-9A-F]{2,128}", credential_type_hex):
        # Hexadecimal is always 2 chars per byte, so an odd length is invalid.
        raise ValueError("credential_type_hex must be 1-64 bytes as hexadecimal.")

    

Next, it calls the ledger_entry method to look up the requested Credential ledger entry:

    # Perform XRPL lookup of Credential ledger entry --------------------------
    ledger_entry_request = LedgerEntry(
        credential={
            "subject": subject,
            "issuer": issuer,
            "credential_type": credential_type_hex
        },
        ledger_index="validated"
    )
    logger.info("Looking up credential...")
    logger.info(ledger_entry_request.to_dict())
    xrpl_response = client.request(ledger_entry_request)

    if xrpl_response.status != "success":
        if xrpl_response.result["error"] == "entryNotFound":
            logger.info("Credential was not found")
            return False
        # Other errors, for example invalidly-specified addresses.
        raise XRPLLookupError(xrpl_response)

    credential = xrpl_response.result["node"]
    logger.info("Found credential:")
    logger.info(credential)

    

If it succeeds in finding the credential, the function continues by checking that the credential has been accepted by its holder. Since anyone can issue a credential to anyone else, a credential is only considered valid if its subject has accepted it.

    # Confirm that the credential has been accepted ---------------------------
    lsfAccepted = 0x00010000
    if not credential["Flags"] & lsfAccepted:
        logger.info("Credential is not accepted.")
        return False
    
    

Then, if the credential has an expiration time, the function checks that the credential is not expired. If the credential has no expiration, this step can be skipped. A credential is officially considered expired if its expiration time is before the official close time of the most recently validated ledger. This is more universal than comparing the expiration to your own local clock. Thus, the code uses the ledger method to look up the most recently validated ledger:

    # Confirm that the credential is not expired ------------------------------
    if credential.get("Expiration"):
        expiration_time = ripple_time_to_datetime(credential["Expiration"])
        logger.info("Credential has expiration: "+expiration_time.isoformat())
        logger.info("Looking up validated ledger to check for expiration.")

        ledger_response = client.request(Ledger(ledger_index="validated"))
        if ledger_response.status != "success":
            raise XRPLLookupError(ledger_response)
        close_time = ripple_time_to_datetime(
                ledger_response.result["ledger"]["close_time"]
        )
        logger.info("Most recent validated ledger is: "+close_time.isoformat())

        if close_time > expiration_time:
            logger.info("Credential is expired.")
            return False

    

If none of the checks up to this point have returned a False value, then the credential must be valid. This concludes the verify_credential(...) main function:

    # Credential has passed all checks. ---------------------------------------
    logger.info("Credential is valid.")
    return True

3. Commandline interface

This file also implements a commandline utility which runs when the file is executed directly as a Python script. Some variables, such as the set of available networks, are only needed for this mode:

# Commandline usage -----------------------------------------------------------
if __name__=="__main__":
    NETWORKS = {
        # JSON-RPC URLs of public servers
        "devnet": "https://s.devnet.rippletest.net:51234/",
        "testnet": "https://s.altnet.rippletest.net:51234/",
        "mainnet": "https://xrplcluster.com/"
    }

    

Then it uses the argparse library to define and parse the arguments that the user can pass from the commandline:

    # Parse arguments ---------------------------------------------------------
    parser = argparse.ArgumentParser(description="Verify an XRPL credential")
    parser.add_argument("issuer", type=str, nargs="?",
                        help="Credential issuer address as base58.",
                        default="rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS")
    parser.add_argument("subject", type=str, nargs="?",
                        help="Credential subject (holder) address as base58.",
                        default="rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA")
    parser.add_argument("credential_type", type=str, nargs="?",
                        help="Credential type as string", 
                        default="my_credential")
    parser.add_argument("-b", "--binary", action="store_true",
                        help="Use binary (hexadecimal) for credential_type")
    parser.add_argument("-n", "--network", choices=NETWORKS.keys(),
                        help="Use the specified network for lookup",
                        default="devnet")
    parser.add_argument("-q", "--quiet", action="store_true",
                        help="Don't print log messages.")
    args = parser.parse_args()

    

After parsing the commandline args, it sets the appropriate values and passes them to verify_credential(...) to perform the credential verification:

    # Call verify_credential with appropriate args ----------------------------
    client = JsonRpcClient(NETWORKS[args.network])
    if not args.quiet:
        # Use INFO level by default when called from the commandline.
        logger.setLevel(logging.INFO)

    if args.binary:
        result = verify_credential(client,
                          issuer=args.issuer,
                          subject=args.subject,
                          credential_type_hex=args.credential_type)
    else:
        result = verify_credential(client,
                          issuer=args.issuer,
                          subject=args.subject,
                          credential_type=args.credential_type)
    
    

Finally, it returns a nonzero exit code if this credential was not verified. This can be useful for shell scripts:

    # Call verify_credential with appropriate args ----------------------------
    client = JsonRpcClient(NETWORKS[args.network])
    if not args.quiet:
        # Use INFO level by default when called from the commandline.
        logger.setLevel(logging.INFO)

    if args.binary:
        result = verify_credential(client,
                          issuer=args.issuer,
                          subject=args.subject,
                          credential_type_hex=args.credential_type)
    else:
        result = verify_credential(client,
                          issuer=args.issuer,
                          subject=args.subject,
                          credential_type=args.credential_type)
    
    

Otherwise, the code exits as normal, which returns a successful exit code of 0 to the shell.

Next Steps

Now that you know how to use xrpl-py to verify credentials, you can try building this or related steps together into a bigger project. For example: