docs: @Nullable
      public static PkiVerificationData verifyPaymentRequestPki(Protos.PaymentRequest paymentRequest, KeyStore trustStore)
              throws PaymentProtocolException {
          List<X509Certificate> certs = null;
          try {
              final String pkiType = paymentRequest.getPkiType();
              if ("none".equals(pkiType))
                  // Nothing to verify. Everything is fine. Move along.
                  return null;
  
              String algorithm;
              if ("x509+sha256".equals(pkiType))
                  algorithm = "SHA256withRSA";
              else if ("x509+sha1".equals(pkiType))
                  algorithm = "SHA1withRSA";
              else
                  throw new PaymentProtocolException.InvalidPkiType("Unsupported PKI type: " + pkiType);
  
              Protos.X509Certificates protoCerts = Protos.X509Certificates.parseFrom(paymentRequest.getPkiData());
              if (protoCerts.getCertificateCount() == 0)
                  throw new PaymentProtocolException.InvalidPkiData("No certificates provided in message: server config error");
  
              // Parse the certs and turn into a certificate chain object. Cert factories can parse both DER and base64.
              // The ordering of certificates is defined by the payment protocol spec to be the same as what the Java
              // crypto API requires - convenient!
              CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
              certs = Lists.newArrayList();
              for (ByteString bytes : protoCerts.getCertificateList())
                  certs.add((X509Certificate) certificateFactory.generateCertificate(bytes.newInput()));
              CertPath path = certificateFactory.generateCertPath(certs);
  
              // Retrieves the most-trusted CAs from keystore.
              PKIXParameters params = new PKIXParameters(trustStore);
              // Revocation not supported in the current version.
              params.setRevocationEnabled(false);
  
              // Now verify the certificate chain is correct and trusted. This let's us get an identity linked pubkey.
              CertPathValidator validator = CertPathValidator.getInstance("PKIX");
              PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) validator.validate(path, params);
              PublicKey publicKey = result.getPublicKey();
              // OK, we got an identity, now check it was used to sign this message.
              Signature signature = Signature.getInstance(algorithm);
              // Note that we don't use signature.initVerify(certs.get(0)) here despite it being the most obvious
              // way to set it up, because we don't care about the constraints specified on the certificates: any
              // cert that links a key to a domain name or other identity will do for us.
              signature.initVerify(publicKey);
              Protos.PaymentRequest.Builder reqToCheck = paymentRequest.toBuilder();
              reqToCheck.setSignature(ByteString.EMPTY);
              signature.update(reqToCheck.build().toByteArray());
              if (!signature.verify(paymentRequest.getSignature().toByteArray()))
                  throw new PaymentProtocolException.PkiVerificationException("Invalid signature, this payment request is not valid.");
  
              // Signature verifies, get the names from the identity we just verified for presentation to the user.
              final X509Certificate cert = certs.get(0);
              String displayName = X509Utils.getDisplayNameFromCertificate(cert, true);
              if (displayName == null)
                  throw new PaymentProtocolException.PkiVerificationException("Could not extract name from certificate");
              // Everything is peachy. Return some useful data to the caller.
              return new PkiVerificationData(displayName, publicKey, result.getTrustAnchor());
          } catch (InvalidProtocolBufferException e) {
              // Data structures are malformed.
              throw new PaymentProtocolException.InvalidPkiData(e);
          } catch (CertificateException e) {
              // The X.509 certificate data didn't parse correctly.
              throw new PaymentProtocolException.PkiVerificationException(e);
          } catch (NoSuchAlgorithmException e) {
              // Should never happen so don't make users have to think about it. PKIX is always present.
              throw new RuntimeException(e);
          } catch (InvalidAlgorithmParameterException e) {
              throw new RuntimeException(e);
          } catch (CertPathValidatorException e) {
              // The certificate chain isn't known or trusted, probably, the server is using an SSL root we don't
              // know about and the user needs to upgrade to a new version of the software (or import a root cert).
              throw new PaymentProtocolException.PkiVerificationException(e, certs);
          } catch (InvalidKeyException e) {
              // Shouldn't happen if the certs verified correctly.
              throw new PaymentProtocolException.PkiVerificationException(e);
          } catch (SignatureException e) {
              // Something went wrong during hashing (yes, despite the name, this does not mean the sig was invalid).
              throw new PaymentProtocolException.PkiVerificationException(e);
          } catch (KeyStoreException e) {
              throw new RuntimeException(e);
          }
      }