mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-03-13 11:36:15 +01:00
PaymentProtocol, PaymentSession: remove BIP70 from bitcoinj
It had its time, but nowadays nobody is using it any more. Let's leave it behind.
This commit is contained in:
parent
aa9eaf9af7
commit
d97d33de41
25 changed files with 4 additions and 2311 deletions
|
@ -1,109 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.bitcoinj.crypto;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
|
||||
/**
|
||||
* An implementation of TrustStoreLoader handles fetching a KeyStore from the operating system, a file, etc. It's
|
||||
* necessary because the Java {@link KeyStore} abstraction is not completely seamless and for example
|
||||
* we sometimes need slightly different techniques to load the key store on different versions of Android, MacOS,
|
||||
* Windows, etc.
|
||||
*/
|
||||
public interface TrustStoreLoader {
|
||||
KeyStore getKeyStore() throws FileNotFoundException, KeyStoreException;
|
||||
|
||||
String DEFAULT_KEYSTORE_TYPE = KeyStore.getDefaultType();
|
||||
String DEFAULT_KEYSTORE_PASSWORD = "changeit";
|
||||
|
||||
class DefaultTrustStoreLoader implements TrustStoreLoader {
|
||||
@Override
|
||||
public KeyStore getKeyStore() throws FileNotFoundException, KeyStoreException {
|
||||
|
||||
String keystorePath = null;
|
||||
String keystoreType = DEFAULT_KEYSTORE_TYPE;
|
||||
try {
|
||||
// Check if we are on Android.
|
||||
Class<?> version = Class.forName("android.os.Build$VERSION");
|
||||
// Build.VERSION_CODES.ICE_CREAM_SANDWICH is 14.
|
||||
if (version.getDeclaredField("SDK_INT").getInt(version) >= 14) {
|
||||
return loadIcsKeyStore();
|
||||
} else {
|
||||
keystoreType = "BKS";
|
||||
keystorePath = System.getProperty("java.home")
|
||||
+ "/etc/security/cacerts.bks".replace('/', File.separatorChar);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
// NOP. android.os.Build is not present, so we are not on Android. Fall through.
|
||||
} catch (NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e); // Should never happen.
|
||||
}
|
||||
if (keystorePath == null) {
|
||||
keystorePath = System.getProperty("javax.net.ssl.trustStore");
|
||||
}
|
||||
if (keystorePath == null) {
|
||||
return loadFallbackStore();
|
||||
}
|
||||
try {
|
||||
return X509Utils.loadKeyStore(keystoreType, DEFAULT_KEYSTORE_PASSWORD,
|
||||
new FileInputStream(keystorePath));
|
||||
} catch (FileNotFoundException e) {
|
||||
// If we failed to find a system trust store, load our own fallback trust store. This can fail on
|
||||
// Android but we should never reach it there.
|
||||
return loadFallbackStore();
|
||||
}
|
||||
}
|
||||
|
||||
private KeyStore loadIcsKeyStore() throws KeyStoreException {
|
||||
try {
|
||||
// After ICS, Android provided this nice method for loading the keystore,
|
||||
// so we don't have to specify the location explicitly.
|
||||
KeyStore keystore = KeyStore.getInstance("AndroidCAStore");
|
||||
keystore.load(null, null);
|
||||
return keystore;
|
||||
} catch (IOException | GeneralSecurityException x) {
|
||||
throw new KeyStoreException(x);
|
||||
}
|
||||
}
|
||||
|
||||
private KeyStore loadFallbackStore() throws FileNotFoundException, KeyStoreException {
|
||||
return X509Utils.loadKeyStore("JKS", DEFAULT_KEYSTORE_PASSWORD, getClass().getResourceAsStream("cacerts"));
|
||||
}
|
||||
}
|
||||
|
||||
class FileTrustStoreLoader implements TrustStoreLoader {
|
||||
private final File path;
|
||||
|
||||
public FileTrustStoreLoader(@Nonnull File path) throws FileNotFoundException {
|
||||
if (!path.exists())
|
||||
throw new FileNotFoundException(path.toString());
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyStore getKeyStore() throws FileNotFoundException, KeyStoreException {
|
||||
return X509Utils.loadKeyStore(DEFAULT_KEYSTORE_TYPE, DEFAULT_KEYSTORE_PASSWORD, new FileInputStream(path));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014 The bitcoinj authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.bitcoinj.crypto;
|
||||
|
||||
import org.bitcoinj.protocols.payments.PaymentSession;
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
|
||||
import org.bouncycastle.asn1.ASN1String;
|
||||
import org.bouncycastle.asn1.x500.AttributeTypeAndValue;
|
||||
import org.bouncycastle.asn1.x500.RDN;
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x500.style.RFC4519Style;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* X509Utils provides tools for working with X.509 certificates and keystores, as used in the BIP 70 payment protocol.
|
||||
* For more details on this, see {@link PaymentSession}, the article "Working with
|
||||
* the payment protocol" on the bitcoinj website, or the Bitcoin developer guide.
|
||||
*/
|
||||
public class X509Utils {
|
||||
/**
|
||||
* Returns either a string that "sums up" the certificate for humans, in a similar manner to what you might see
|
||||
* in a web browser, or null if one cannot be extracted. This will typically be the common name (CN) field, but
|
||||
* can also be the org (O) field, org+location+country if withLocation is set, or the email
|
||||
* address for S/MIME certificates.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getDisplayNameFromCertificate(@Nonnull X509Certificate certificate, boolean withLocation) throws CertificateParsingException {
|
||||
X500Name name = new X500Name(certificate.getSubjectX500Principal().getName());
|
||||
String commonName = null, org = null, location = null, country = null;
|
||||
for (RDN rdn : name.getRDNs()) {
|
||||
AttributeTypeAndValue pair = rdn.getFirst();
|
||||
String val = ((ASN1String) pair.getValue()).getString();
|
||||
ASN1ObjectIdentifier type = pair.getType();
|
||||
if (type.equals(RFC4519Style.cn))
|
||||
commonName = val;
|
||||
else if (type.equals(RFC4519Style.o))
|
||||
org = val;
|
||||
else if (type.equals(RFC4519Style.l))
|
||||
location = val;
|
||||
else if (type.equals(RFC4519Style.c))
|
||||
country = val;
|
||||
}
|
||||
String altName = null;
|
||||
try {
|
||||
final Collection<List<?>> subjectAlternativeNames = certificate.getSubjectAlternativeNames();
|
||||
if (subjectAlternativeNames != null)
|
||||
for (final List<?> subjectAlternativeName : subjectAlternativeNames)
|
||||
if ((Integer) subjectAlternativeName.get(0) == 1) // rfc822name
|
||||
altName = (String) subjectAlternativeName.get(1);
|
||||
} catch (CertificateParsingException e) {
|
||||
// swallow
|
||||
}
|
||||
|
||||
if (org != null) {
|
||||
return withLocation ? Stream.of(org, location, country).filter(Objects::nonNull).collect(Collectors.joining()) : org;
|
||||
} else if (commonName != null) {
|
||||
return commonName;
|
||||
} else {
|
||||
return altName;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns a key store loaded from the given stream. Just a convenience around the Java APIs. */
|
||||
public static KeyStore loadKeyStore(String keystoreType, @Nullable String keystorePassword, InputStream is)
|
||||
throws KeyStoreException {
|
||||
try {
|
||||
KeyStore keystore = KeyStore.getInstance(keystoreType);
|
||||
keystore.load(is, keystorePassword != null ? keystorePassword.toCharArray() : null);
|
||||
return keystore;
|
||||
} catch (IOException | GeneralSecurityException x) {
|
||||
throw new KeyStoreException(x);
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (IOException x) {
|
||||
// Ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright by the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* High level protocols that build on top of Bitcoin go here: we have the payment protocol for sending transactions
|
||||
* from sender to receiver with metadata, and a micropayment channels implementation.
|
||||
*/
|
||||
package org.bitcoinj.protocols;
|
|
@ -1,491 +0,0 @@
|
|||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
* Copyright 2014 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.bitcoinj.protocols.payments;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import org.bitcoinj.protobuf.payments.Protos;
|
||||
import org.bitcoinj.base.Address;
|
||||
import org.bitcoinj.base.Coin;
|
||||
import org.bitcoinj.base.internal.TimeUtils;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.crypto.X509Utils;
|
||||
import org.bitcoinj.params.BitcoinNetworkParams;
|
||||
import org.bitcoinj.params.MainNetParams;
|
||||
import org.bitcoinj.params.RegTestParams;
|
||||
import org.bitcoinj.params.SigNetParams;
|
||||
import org.bitcoinj.params.TestNet3Params;
|
||||
import org.bitcoinj.params.UnitTestParams;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertPath;
|
||||
import java.security.cert.CertPathValidator;
|
||||
import java.security.cert.CertPathValidatorException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.PKIXCertPathValidatorResult;
|
||||
import java.security.cert.PKIXParameters;
|
||||
import java.security.cert.TrustAnchor;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>Utility methods and constants for working with <a href="https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki">
|
||||
* BIP 70 aka the payment protocol</a>. These are low level wrappers around the protocol buffers. If you're implementing
|
||||
* a wallet app, look at {@link PaymentSession} for a higher level API that should simplify working with the protocol.</p>
|
||||
*
|
||||
* <p>BIP 70 defines a binary, protobuf based protocol that runs directly between sender and receiver of funds. Payment
|
||||
* protocol data does not flow over the Bitcoin P2P network or enter the block chain. It's instead for data that is only
|
||||
* of interest to the parties involved but isn't otherwise needed for consensus.</p>
|
||||
*/
|
||||
public class PaymentProtocol {
|
||||
|
||||
// MIME types as defined in BIP71.
|
||||
public static final String MIMETYPE_PAYMENTREQUEST = "application/bitcoin-paymentrequest";
|
||||
public static final String MIMETYPE_PAYMENT = "application/bitcoin-payment";
|
||||
public static final String MIMETYPE_PAYMENTACK = "application/bitcoin-paymentack";
|
||||
|
||||
/** The string used by the payment protocol to represent the main net. */
|
||||
public static final String PAYMENT_PROTOCOL_ID_MAINNET = "main";
|
||||
/** The string used by the payment protocol to represent the test net. */
|
||||
public static final String PAYMENT_PROTOCOL_ID_TESTNET = "test";
|
||||
/** The string used by the payment protocol to represent signet (note that this is non-standard). */
|
||||
public static final String PAYMENT_PROTOCOL_ID_SIGNET = "signet";
|
||||
/** The string used by the payment protocol to represent unit testing (note that this is non-standard). */
|
||||
public static final String PAYMENT_PROTOCOL_ID_UNIT_TESTS = "unittest";
|
||||
public static final String PAYMENT_PROTOCOL_ID_REGTEST = "regtest";
|
||||
|
||||
/**
|
||||
* Create a payment request with one standard pay to address output. You may want to sign the request using
|
||||
* {@link #signPaymentRequest}. Use {@link Protos.PaymentRequest.Builder#build} to get the actual payment
|
||||
* request.
|
||||
*
|
||||
* @param params network parameters
|
||||
* @param amount amount of coins to request, or null
|
||||
* @param toAddress address to request coins to
|
||||
* @param memo arbitrary, user readable memo, or null if none
|
||||
* @param paymentUrl URL to send payment message to, or null if none
|
||||
* @param merchantData arbitrary merchant data, or null if none
|
||||
* @return created payment request, in its builder form
|
||||
*/
|
||||
public static Protos.PaymentRequest.Builder createPaymentRequest(NetworkParameters params,
|
||||
@Nullable Coin amount, Address toAddress, @Nullable String memo, @Nullable String paymentUrl,
|
||||
@Nullable byte[] merchantData) {
|
||||
return createPaymentRequest(params, Collections.singletonList(createPayToAddressOutput(amount, toAddress)), memo,
|
||||
paymentUrl, merchantData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a payment request. You may want to sign the request using {@link #signPaymentRequest}. Use
|
||||
* {@link Protos.PaymentRequest.Builder#build} to get the actual payment request.
|
||||
*
|
||||
* @param params network parameters
|
||||
* @param outputs list of outputs to request coins to
|
||||
* @param memo arbitrary, user readable memo, or null if none
|
||||
* @param paymentUrl URL to send payment message to, or null if none
|
||||
* @param merchantData arbitrary merchant data, or null if none
|
||||
* @return created payment request, in its builder form
|
||||
*/
|
||||
public static Protos.PaymentRequest.Builder createPaymentRequest(NetworkParameters params,
|
||||
List<Protos.Output> outputs, @Nullable String memo, @Nullable String paymentUrl,
|
||||
@Nullable byte[] merchantData) {
|
||||
final Protos.PaymentDetails.Builder paymentDetails = Protos.PaymentDetails.newBuilder();
|
||||
paymentDetails.setNetwork(protocolIdFromParams(params));
|
||||
for (Protos.Output output : outputs)
|
||||
paymentDetails.addOutputs(output);
|
||||
if (memo != null)
|
||||
paymentDetails.setMemo(memo);
|
||||
if (paymentUrl != null)
|
||||
paymentDetails.setPaymentUrl(paymentUrl);
|
||||
if (merchantData != null)
|
||||
paymentDetails.setMerchantData(ByteString.copyFrom(merchantData));
|
||||
paymentDetails.setTime(TimeUtils.currentTime().getEpochSecond());
|
||||
|
||||
final Protos.PaymentRequest.Builder paymentRequest = Protos.PaymentRequest.newBuilder();
|
||||
paymentRequest.setSerializedPaymentDetails(paymentDetails.build().toByteString());
|
||||
return paymentRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a payment request.
|
||||
*
|
||||
* @param paymentRequest payment request to parse
|
||||
* @return instance of {@link PaymentSession}, used as a value object
|
||||
* @throws PaymentProtocolException
|
||||
*/
|
||||
public static PaymentSession parsePaymentRequest(Protos.PaymentRequest paymentRequest)
|
||||
throws PaymentProtocolException {
|
||||
return new PaymentSession(paymentRequest, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign the provided payment request.
|
||||
*
|
||||
* @param paymentRequest Payment request to sign, in its builder form.
|
||||
* @param certificateChain Certificate chain to send with the payment request, ordered from client certificate to root
|
||||
* certificate. The root certificate itself may be omitted.
|
||||
* @param privateKey The key to sign with. Must match the public key from the first certificate of the certificate chain.
|
||||
*/
|
||||
public static void signPaymentRequest(Protos.PaymentRequest.Builder paymentRequest,
|
||||
X509Certificate[] certificateChain, PrivateKey privateKey) {
|
||||
try {
|
||||
final Protos.X509Certificates.Builder certificates = Protos.X509Certificates.newBuilder();
|
||||
for (final Certificate certificate : certificateChain)
|
||||
certificates.addCertificate(ByteString.copyFrom(certificate.getEncoded()));
|
||||
|
||||
paymentRequest.setPkiType("x509+sha256");
|
||||
paymentRequest.setPkiData(certificates.build().toByteString());
|
||||
paymentRequest.setSignature(ByteString.EMPTY);
|
||||
final Protos.PaymentRequest paymentRequestToSign = paymentRequest.build();
|
||||
|
||||
final String algorithm;
|
||||
if ("RSA".equalsIgnoreCase(privateKey.getAlgorithm()))
|
||||
algorithm = "SHA256withRSA";
|
||||
else
|
||||
throw new IllegalStateException(privateKey.getAlgorithm());
|
||||
|
||||
final Signature signature = Signature.getInstance(algorithm);
|
||||
signature.initSign(privateKey);
|
||||
signature.update(paymentRequestToSign.toByteArray());
|
||||
|
||||
paymentRequest.setSignature(ByteString.copyFrom(signature.sign()));
|
||||
} catch (final GeneralSecurityException x) {
|
||||
// Should never happen so don't make users have to think about it.
|
||||
throw new RuntimeException(x);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the provided PKI method to find the corresponding public key and verify the provided signature.
|
||||
*
|
||||
* @param paymentRequest Payment request to verify.
|
||||
* @param trustStore KeyStore of trusted root certificate authorities.
|
||||
* @return verification data, or null if no PKI method was specified in the {@link Protos.PaymentRequest}.
|
||||
* @throws PaymentProtocolException if payment request could not be verified.
|
||||
*/
|
||||
@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 = new ArrayList<>();
|
||||
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 | SignatureException | InvalidKeyException e) {
|
||||
// CertificateException: The X.509 certificate data didn't parse correctly.
|
||||
// SignatureException: Something went wrong during hashing (yes, despite the name, this does not mean the sig was invalid).
|
||||
// InvalidKeyException: Shouldn't happen if the certs verified correctly.
|
||||
throw new PaymentProtocolException.PkiVerificationException(e);
|
||||
} catch (NoSuchAlgorithmException | KeyStoreException | InvalidAlgorithmParameterException e) {
|
||||
// NoSuchAlgorithmException: Should never happen so don't make users have to think about it. PKIX is always present.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Information about the X.509 signature's issuer and subject.
|
||||
*/
|
||||
public static class PkiVerificationData {
|
||||
/** Display name of the payment requestor, could be a domain name, email address, legal name, etc */
|
||||
public final String displayName;
|
||||
/** SSL public key that was used to sign. */
|
||||
public final PublicKey merchantSigningKey;
|
||||
/** Object representing the CA that verified the merchant's ID */
|
||||
public final TrustAnchor rootAuthority;
|
||||
/** String representing the display name of the CA that verified the merchant's ID */
|
||||
public final String rootAuthorityName;
|
||||
|
||||
private PkiVerificationData(@Nullable String displayName, PublicKey merchantSigningKey,
|
||||
TrustAnchor rootAuthority) throws PaymentProtocolException.PkiVerificationException {
|
||||
try {
|
||||
this.displayName = displayName;
|
||||
this.merchantSigningKey = merchantSigningKey;
|
||||
this.rootAuthority = rootAuthority;
|
||||
this.rootAuthorityName = X509Utils.getDisplayNameFromCertificate(rootAuthority.getTrustedCert(), true);
|
||||
} catch (CertificateParsingException x) {
|
||||
throw new PaymentProtocolException.PkiVerificationException(x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("displayName", displayName)
|
||||
.add("rootAuthorityName", rootAuthorityName)
|
||||
.add("merchantSigningKey", merchantSigningKey)
|
||||
.add("rootAuthority", rootAuthority)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a payment message with one standard pay to address output.
|
||||
*
|
||||
* @param transactions one or more transactions that satisfy the requested outputs.
|
||||
* @param refundAmount amount of coins to request as a refund, or null if no refund.
|
||||
* @param refundAddress address to refund coins to
|
||||
* @param memo arbitrary, user readable memo, or null if none
|
||||
* @param merchantData arbitrary merchant data, or null if none
|
||||
* @return created payment message
|
||||
*/
|
||||
public static Protos.Payment createPaymentMessage(List<Transaction> transactions,
|
||||
@Nullable Coin refundAmount, @Nullable Address refundAddress, @Nullable String memo,
|
||||
@Nullable byte[] merchantData) {
|
||||
if (refundAddress != null) {
|
||||
if (refundAmount == null)
|
||||
throw new IllegalArgumentException("Specify refund amount if refund address is specified.");
|
||||
return createPaymentMessage(transactions,
|
||||
Collections.singletonList(createPayToAddressOutput(refundAmount, refundAddress)), memo, merchantData);
|
||||
} else {
|
||||
return createPaymentMessage(transactions, null, memo, merchantData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a payment message. This wraps up transaction data along with anything else useful for making a payment.
|
||||
*
|
||||
* @param transactions transactions to include with the payment message
|
||||
* @param refundOutputs list of outputs to refund coins to, or null
|
||||
* @param memo arbitrary, user readable memo, or null if none
|
||||
* @param merchantData arbitrary merchant data, or null if none
|
||||
* @return created payment message
|
||||
*/
|
||||
public static Protos.Payment createPaymentMessage(List<Transaction> transactions,
|
||||
@Nullable List<Protos.Output> refundOutputs, @Nullable String memo, @Nullable byte[] merchantData) {
|
||||
Protos.Payment.Builder builder = Protos.Payment.newBuilder();
|
||||
for (Transaction transaction : transactions) {
|
||||
builder.addTransactions(ByteString.copyFrom(transaction.serialize()));
|
||||
}
|
||||
if (refundOutputs != null) {
|
||||
for (Protos.Output output : refundOutputs)
|
||||
builder.addRefundTo(output);
|
||||
}
|
||||
if (memo != null)
|
||||
builder.setMemo(memo);
|
||||
if (merchantData != null)
|
||||
builder.setMerchantData(ByteString.copyFrom(merchantData));
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse transactions from payment message.
|
||||
*
|
||||
* @param params network parameters (needed for transaction deserialization)
|
||||
* @param paymentMessage payment message to parse
|
||||
* @return list of transactions
|
||||
*/
|
||||
public static List<Transaction> parseTransactionsFromPaymentMessage(NetworkParameters params,
|
||||
Protos.Payment paymentMessage) {
|
||||
final List<Transaction> transactions = new ArrayList<>(paymentMessage.getTransactionsCount());
|
||||
for (final ByteString transaction : paymentMessage.getTransactionsList())
|
||||
transactions.add(params.getDefaultSerializer().makeTransaction(ByteBuffer.wrap(transaction.toByteArray())));
|
||||
return transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message returned by the merchant in response to a Payment message.
|
||||
*/
|
||||
public static class Ack {
|
||||
@Nullable private final String memo;
|
||||
|
||||
Ack(@Nullable String memo) {
|
||||
this.memo = memo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the memo included by the merchant in the payment ack. This message is typically displayed to the user
|
||||
* as a notification (e.g. "Your payment was received and is being processed"). If none was provided, returns
|
||||
* null.
|
||||
*/
|
||||
@Nullable public String getMemo() {
|
||||
return memo;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a payment ack.
|
||||
*
|
||||
* @param paymentMessage payment message to send with the ack
|
||||
* @param memo arbitrary, user readable memo, or null if none
|
||||
* @return created payment ack
|
||||
*/
|
||||
public static Protos.PaymentACK createPaymentAck(Protos.Payment paymentMessage, @Nullable String memo) {
|
||||
final Protos.PaymentACK.Builder builder = Protos.PaymentACK.newBuilder();
|
||||
builder.setPayment(paymentMessage);
|
||||
if (memo != null)
|
||||
builder.setMemo(memo);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse payment ack into an object.
|
||||
*/
|
||||
public static Ack parsePaymentAck(Protos.PaymentACK paymentAck) {
|
||||
final String memo = paymentAck.hasMemo() ? paymentAck.getMemo() : null;
|
||||
return new Ack(memo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a standard pay to address output for usage in {@link #createPaymentRequest} and
|
||||
* {@link #createPaymentMessage}.
|
||||
*
|
||||
* @param amount amount to pay, or null
|
||||
* @param address address to pay to
|
||||
* @return output
|
||||
*/
|
||||
public static Protos.Output createPayToAddressOutput(@Nullable Coin amount, Address address) {
|
||||
Protos.Output.Builder output = Protos.Output.newBuilder();
|
||||
if (amount != null) {
|
||||
final NetworkParameters params = NetworkParameters.of(address.network());
|
||||
if (params.network().exceedsMaxMoney(amount))
|
||||
throw new IllegalArgumentException("Amount too big: " + amount);
|
||||
output.setAmount(amount.value);
|
||||
} else {
|
||||
output.setAmount(0);
|
||||
}
|
||||
output.setScript(ByteString.copyFrom(ScriptBuilder.createOutputScript(address).program()));
|
||||
return output.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return network parameters for a paymentProtocol ID string
|
||||
* @param pmtProtocolId paymentProtocol ID string
|
||||
* @return network parameters for the given string paymentProtocolID or NULL if not recognized
|
||||
*/
|
||||
@Nullable
|
||||
public static BitcoinNetworkParams paramsFromPmtProtocolID(String pmtProtocolId) {
|
||||
if (pmtProtocolId.equals(PAYMENT_PROTOCOL_ID_MAINNET)) {
|
||||
return MainNetParams.get();
|
||||
} else if (pmtProtocolId.equals(PAYMENT_PROTOCOL_ID_TESTNET)) {
|
||||
return TestNet3Params.get();
|
||||
} else if (pmtProtocolId.equals(PAYMENT_PROTOCOL_ID_SIGNET)) {
|
||||
return SigNetParams.get();
|
||||
} else if (pmtProtocolId.equals(PAYMENT_PROTOCOL_ID_REGTEST)) {
|
||||
return RegTestParams.get();
|
||||
} else if (pmtProtocolId.equals(PAYMENT_PROTOCOL_ID_UNIT_TESTS)) {
|
||||
return UnitTestParams.get();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String protocolIdFromParams(NetworkParameters params) {
|
||||
if (params instanceof MainNetParams) {
|
||||
return PAYMENT_PROTOCOL_ID_MAINNET;
|
||||
} else if (params instanceof TestNet3Params) {
|
||||
return PAYMENT_PROTOCOL_ID_TESTNET;
|
||||
} else if (params instanceof SigNetParams) {
|
||||
return PAYMENT_PROTOCOL_ID_SIGNET;
|
||||
} else if (params instanceof RegTestParams) {
|
||||
return PAYMENT_PROTOCOL_ID_REGTEST;
|
||||
} else if (params instanceof UnitTestParams) {
|
||||
return PAYMENT_PROTOCOL_ID_UNIT_TESTS;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown network");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to hold amount/script pairs.
|
||||
*/
|
||||
public static class Output {
|
||||
@Nullable public final Coin amount;
|
||||
public final byte[] scriptData;
|
||||
|
||||
public Output(@Nullable Coin amount, byte[] scriptData) {
|
||||
this.amount = amount;
|
||||
this.scriptData = scriptData;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,107 +0,0 @@
|
|||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.bitcoinj.protocols.payments;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
public class PaymentProtocolException extends Exception {
|
||||
public PaymentProtocolException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public PaymentProtocolException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public static class Expired extends PaymentProtocolException {
|
||||
public Expired(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidPaymentRequestURL extends PaymentProtocolException {
|
||||
public InvalidPaymentRequestURL(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public InvalidPaymentRequestURL(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidPaymentURL extends PaymentProtocolException {
|
||||
public InvalidPaymentURL(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public InvalidPaymentURL(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidOutputs extends PaymentProtocolException {
|
||||
public InvalidOutputs(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidVersion extends PaymentProtocolException {
|
||||
public InvalidVersion(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidNetwork extends PaymentProtocolException {
|
||||
public InvalidNetwork(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidPkiType extends PaymentProtocolException {
|
||||
public InvalidPkiType(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InvalidPkiData extends PaymentProtocolException {
|
||||
public InvalidPkiData(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public InvalidPkiData(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PkiVerificationException extends PaymentProtocolException {
|
||||
public List<X509Certificate> certificates;
|
||||
|
||||
public PkiVerificationException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
public PkiVerificationException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public PkiVerificationException(Exception e, List<X509Certificate> certificates) {
|
||||
super(e);
|
||||
this.certificates = certificates;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,438 +0,0 @@
|
|||
/*
|
||||
* Copyright by the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.bitcoinj.protocols.payments;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import org.bitcoinj.protobuf.payments.Protos;
|
||||
import org.bitcoinj.base.Address;
|
||||
import org.bitcoinj.base.Coin;
|
||||
import org.bitcoinj.base.internal.TimeUtils;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.crypto.TrustStoreLoader;
|
||||
import org.bitcoinj.params.MainNetParams;
|
||||
import org.bitcoinj.protocols.payments.PaymentProtocol.PkiVerificationData;
|
||||
import org.bitcoinj.uri.BitcoinURI;
|
||||
import org.bitcoinj.base.internal.FutureUtils;
|
||||
import org.bitcoinj.utils.Threading;
|
||||
import org.bitcoinj.wallet.SendRequest;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.security.KeyStoreException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
/**
|
||||
* <p>Provides a standard implementation of the Payment Protocol (BIP 0070)</p>
|
||||
*
|
||||
* <p>A PaymentSession can be initialized from one of the following:</p>
|
||||
*
|
||||
* <ul>
|
||||
* <li>A {@link BitcoinURI} object that conforms to BIP 0072</li>
|
||||
* <li>A url where the {@link Protos.PaymentRequest} can be fetched</li>
|
||||
* <li>Directly with a {@link Protos.PaymentRequest} object</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>If initialized with a BitcoinURI or a url, a network request is made for the payment request object and a
|
||||
* {@link CompletableFuture} is returned that will be notified with the PaymentSession object after it is downloaded.</p>
|
||||
*
|
||||
* <p>Once the PaymentSession is initialized, typically a wallet application will prompt the user to confirm that the
|
||||
* amount and recipient are correct, perform any additional steps, and then construct a list of transactions to pass to
|
||||
* the sendPayment method.</p>
|
||||
*
|
||||
* <p>Call sendPayment with a list of transactions that will be broadcast. A {@link Protos.Payment} message will be sent
|
||||
* to the merchant if a payment url is provided in the PaymentRequest. NOTE: sendPayment does NOT broadcast the
|
||||
* transactions to the bitcoin network. Instead it returns a CompletableFuture that will be notified when a
|
||||
* {@link Protos.PaymentACK} is received from the merchant. Typically a wallet will show the message to the user
|
||||
* as a confirmation message that the payment is now "processing" or that an error occurred, and then broadcast the
|
||||
* tx itself later if needed.</p>
|
||||
*
|
||||
* @see <a href="https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki">BIP 0070</a>
|
||||
*/
|
||||
public class PaymentSession {
|
||||
private final static ExecutorService executor = Threading.THREAD_POOL;
|
||||
private NetworkParameters params;
|
||||
private Protos.PaymentRequest paymentRequest;
|
||||
private Protos.PaymentDetails paymentDetails;
|
||||
private Coin totalValue = Coin.ZERO;
|
||||
|
||||
/**
|
||||
* Stores the calculated PKI verification data, or null if none is available.
|
||||
* Only valid after the session is created with the verifyPki parameter set to true.
|
||||
*/
|
||||
@Nullable public final PkiVerificationData pkiVerificationData;
|
||||
|
||||
/**
|
||||
* <p>Returns a future that will be notified with a PaymentSession object after it is fetched using the provided uri.
|
||||
* uri is a BIP-72-style BitcoinURI object that specifies where the {@link Protos.PaymentRequest} object may
|
||||
* be fetched in the r= parameter.</p>
|
||||
*
|
||||
* <p>If the payment request object specifies a PKI method, then the system trust store will be used to verify
|
||||
* the signature provided by the payment request. An exception is thrown by the future if the signature cannot
|
||||
* be verified.</p>
|
||||
*/
|
||||
public static CompletableFuture<PaymentSession> createFromBitcoinUri(final BitcoinURI uri) throws PaymentProtocolException {
|
||||
return createFromBitcoinUri(uri, true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a future that will be notified with a PaymentSession object after it is fetched using the provided uri.
|
||||
* uri is a BIP-72-style BitcoinURI object that specifies where the {@link Protos.PaymentRequest} object may
|
||||
* be fetched in the r= parameter.
|
||||
* If verifyPki is specified and the payment request object specifies a PKI method, then the system trust store will
|
||||
* be used to verify the signature provided by the payment request. An exception is thrown by the future if the
|
||||
* signature cannot be verified.
|
||||
*/
|
||||
public static CompletableFuture<PaymentSession> createFromBitcoinUri(final BitcoinURI uri, final boolean verifyPki)
|
||||
throws PaymentProtocolException {
|
||||
return createFromBitcoinUri(uri, verifyPki, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a future that will be notified with a PaymentSession object after it is fetched using the provided uri.
|
||||
* uri is a BIP-72-style BitcoinURI object that specifies where the {@link Protos.PaymentRequest} object may
|
||||
* be fetched in the r= parameter.
|
||||
* If verifyPki is specified and the payment request object specifies a PKI method, then the system trust store will
|
||||
* be used to verify the signature provided by the payment request. An exception is thrown by the future if the
|
||||
* signature cannot be verified.
|
||||
* If trustStoreLoader is null, the system default trust store is used.
|
||||
*/
|
||||
public static CompletableFuture<PaymentSession> createFromBitcoinUri(final BitcoinURI uri, final boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader)
|
||||
throws PaymentProtocolException {
|
||||
String url = uri.getPaymentRequestUrl();
|
||||
if (url == null)
|
||||
throw new PaymentProtocolException.InvalidPaymentRequestURL("No payment request URL (r= parameter) in BitcoinURI " + uri);
|
||||
try {
|
||||
return fetchPaymentRequest(new URI(url), verifyPki, trustStoreLoader);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new PaymentProtocolException.InvalidPaymentRequestURL(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a future that will be notified with a PaymentSession object after it is fetched using the provided url.
|
||||
* url is an address where the {@link Protos.PaymentRequest} object may be fetched.
|
||||
* If verifyPki is specified and the payment request object specifies a PKI method, then the system trust store will
|
||||
* be used to verify the signature provided by the payment request. An exception is thrown by the future if the
|
||||
* signature cannot be verified.
|
||||
*/
|
||||
public static CompletableFuture<PaymentSession> createFromUrl(final String url) throws PaymentProtocolException {
|
||||
return createFromUrl(url, true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a future that will be notified with a PaymentSession object after it is fetched using the provided url.
|
||||
* url is an address where the {@link Protos.PaymentRequest} object may be fetched.
|
||||
* If the payment request object specifies a PKI method, then the system trust store will
|
||||
* be used to verify the signature provided by the payment request. An exception is thrown by the future if the
|
||||
* signature cannot be verified.
|
||||
*/
|
||||
public static CompletableFuture<PaymentSession> createFromUrl(final String url, final boolean verifyPki)
|
||||
throws PaymentProtocolException {
|
||||
return createFromUrl(url, verifyPki, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a future that will be notified with a PaymentSession object after it is fetched using the provided url.
|
||||
* url is an address where the {@link Protos.PaymentRequest} object may be fetched.
|
||||
* If the payment request object specifies a PKI method, then the system trust store will
|
||||
* be used to verify the signature provided by the payment request. An exception is thrown by the future if the
|
||||
* signature cannot be verified.
|
||||
* If trustStoreLoader is null, the system default trust store is used.
|
||||
*/
|
||||
public static CompletableFuture<PaymentSession> createFromUrl(final String url, final boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader)
|
||||
throws PaymentProtocolException {
|
||||
if (url == null)
|
||||
throw new PaymentProtocolException.InvalidPaymentRequestURL("null paymentRequestUrl");
|
||||
try {
|
||||
return fetchPaymentRequest(new URI(url), verifyPki, trustStoreLoader);
|
||||
} catch(URISyntaxException e) {
|
||||
throw new PaymentProtocolException.InvalidPaymentRequestURL(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static CompletableFuture<PaymentSession> fetchPaymentRequest(final URI uri, final boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader) {
|
||||
return CompletableFuture.supplyAsync((FutureUtils.ThrowingSupplier<PaymentSession>) () -> {
|
||||
HttpURLConnection connection = (HttpURLConnection)uri.toURL().openConnection();
|
||||
connection.setRequestProperty("Accept", PaymentProtocol.MIMETYPE_PAYMENTREQUEST);
|
||||
connection.setUseCaches(false);
|
||||
Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.parseFrom(connection.getInputStream());
|
||||
return new PaymentSession(paymentRequest, verifyPki, trustStoreLoader);
|
||||
}, executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PaymentSession from the provided {@link Protos.PaymentRequest}.
|
||||
* Verifies PKI by default.
|
||||
*/
|
||||
public PaymentSession(Protos.PaymentRequest request) throws PaymentProtocolException {
|
||||
this(request, true, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PaymentSession from the provided {@link Protos.PaymentRequest}.
|
||||
* If verifyPki is true, also validates the signature and throws an exception if it fails.
|
||||
*/
|
||||
public PaymentSession(Protos.PaymentRequest request, boolean verifyPki) throws PaymentProtocolException {
|
||||
this(request, verifyPki, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PaymentSession from the provided {@link Protos.PaymentRequest}.
|
||||
* If verifyPki is true, also validates the signature and throws an exception if it fails.
|
||||
* If trustStoreLoader is null, the system default trust store is used.
|
||||
*/
|
||||
public PaymentSession(Protos.PaymentRequest request, boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader) throws PaymentProtocolException {
|
||||
TrustStoreLoader nonNullTrustStoreLoader = trustStoreLoader != null ? trustStoreLoader : new TrustStoreLoader.DefaultTrustStoreLoader();
|
||||
parsePaymentRequest(request);
|
||||
if (verifyPki) {
|
||||
try {
|
||||
pkiVerificationData = PaymentProtocol.verifyPaymentRequestPki(request, nonNullTrustStoreLoader.getKeyStore());
|
||||
} catch (IOException | KeyStoreException x) {
|
||||
throw new PaymentProtocolException(x);
|
||||
}
|
||||
} else {
|
||||
pkiVerificationData = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the outputs of the payment request.
|
||||
*/
|
||||
public List<PaymentProtocol.Output> getOutputs() {
|
||||
List<PaymentProtocol.Output> outputs = new ArrayList<>(paymentDetails.getOutputsCount());
|
||||
for (Protos.Output output : paymentDetails.getOutputsList()) {
|
||||
Coin amount = output.hasAmount() ? Coin.valueOf(output.getAmount()) : null;
|
||||
outputs.add(new PaymentProtocol.Output(amount, output.getScript().toByteArray()));
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the memo included by the merchant in the payment request, or null if not found.
|
||||
*/
|
||||
@Nullable public String getMemo() {
|
||||
if (paymentDetails.hasMemo())
|
||||
return paymentDetails.getMemo();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total amount of bitcoins requested.
|
||||
*/
|
||||
public Coin getValue() {
|
||||
return totalValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time that the payment request was generated.
|
||||
*/
|
||||
public Instant time() {
|
||||
return Instant.ofEpochSecond(paymentDetails.getTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expires time of the payment request, or empty if none.
|
||||
*/
|
||||
public Optional<Instant> expires() {
|
||||
if (paymentDetails.hasExpires())
|
||||
return Optional.of(Instant.ofEpochSecond(paymentDetails.getExpires()));
|
||||
else
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* This should always be called before attempting to call sendPayment.
|
||||
*/
|
||||
public boolean isExpired() {
|
||||
return expires()
|
||||
.map(time -> TimeUtils.currentTime().isAfter(time))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the payment url where the Payment message should be sent.
|
||||
* Returns null if no payment url was provided in the PaymentRequest.
|
||||
*/
|
||||
@Nullable
|
||||
public String getPaymentUrl() {
|
||||
if (paymentDetails.hasPaymentUrl())
|
||||
return paymentDetails.getPaymentUrl();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the merchant data included by the merchant in the payment request, or null if none.
|
||||
*/
|
||||
@Nullable public byte[] getMerchantData() {
|
||||
if (paymentDetails.hasMerchantData())
|
||||
return paymentDetails.getMerchantData().toByteArray();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link SendRequest} suitable for broadcasting to the network.
|
||||
*/
|
||||
public SendRequest getSendRequest() {
|
||||
Transaction tx = new Transaction();
|
||||
for (Protos.Output output : paymentDetails.getOutputsList())
|
||||
tx.addOutput(new TransactionOutput(tx, Coin.valueOf(output.getAmount()), output.getScript().toByteArray()));
|
||||
return SendRequest.forTx(tx).fromPaymentDetails(paymentDetails);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a Payment message and sends the payment to the merchant who sent the PaymentRequest.
|
||||
* Provide transactions built by the wallet.
|
||||
* NOTE: This does not broadcast the transactions to the bitcoin network, it merely sends a Payment message to the
|
||||
* merchant confirming the payment.
|
||||
* Returns an object wrapping PaymentACK once received.
|
||||
* If the PaymentRequest did not specify a payment_url, completes exceptionally.
|
||||
* @param txns list of transactions to be included with the Payment message.
|
||||
* @param refundAddr will be used by the merchant to send money back if there was a problem.
|
||||
* @param memo is a message to include in the payment message sent to the merchant.
|
||||
* @return a future for the PaymentACK
|
||||
*/
|
||||
public CompletableFuture<PaymentProtocol.Ack> sendPayment(List<Transaction> txns, @Nullable Address refundAddr, @Nullable String memo) {
|
||||
Protos.Payment payment = null;
|
||||
try {
|
||||
payment = getPayment(txns, refundAddr, memo);
|
||||
} catch (IOException e) {
|
||||
return FutureUtils.failedFuture(e);
|
||||
}
|
||||
if (payment == null)
|
||||
return FutureUtils.failedFuture(new PaymentProtocolException.InvalidPaymentRequestURL("Missing Payment URL"));
|
||||
if (isExpired())
|
||||
return FutureUtils.failedFuture(new PaymentProtocolException.Expired("PaymentRequest is expired"));
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(paymentDetails.getPaymentUrl());
|
||||
} catch (MalformedURLException e) {
|
||||
return FutureUtils.failedFuture(new PaymentProtocolException.InvalidPaymentURL(e));
|
||||
}
|
||||
return sendPayment(url, payment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a Payment message based on the information in the PaymentRequest.
|
||||
* Provide transactions built by the wallet.
|
||||
* @param txns list of transactions to be included with the Payment message.
|
||||
* @param refundAddr will be used by the merchant to send money back if there was a problem.
|
||||
* @param memo is a message to include in the payment message sent to the merchant.
|
||||
* @return Payment message or null (if the PaymentRequest did not specify a payment_url)
|
||||
*/
|
||||
@Nullable
|
||||
public Protos.Payment getPayment(List<Transaction> txns, @Nullable Address refundAddr, @Nullable String memo)
|
||||
throws IOException {
|
||||
if (paymentDetails.hasPaymentUrl()) {
|
||||
return PaymentProtocol.createPaymentMessage(txns, totalValue, refundAddr, memo, getMerchantData());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected CompletableFuture<PaymentProtocol.Ack> sendPayment(final URL url, final Protos.Payment payment) {
|
||||
return CompletableFuture.supplyAsync((FutureUtils.ThrowingSupplier<PaymentProtocol.Ack>) () -> {
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", PaymentProtocol.MIMETYPE_PAYMENT);
|
||||
connection.setRequestProperty("Accept", PaymentProtocol.MIMETYPE_PAYMENTACK);
|
||||
connection.setRequestProperty("Content-Length", Integer.toString(payment.getSerializedSize()));
|
||||
connection.setUseCaches(false);
|
||||
connection.setDoInput(true);
|
||||
connection.setDoOutput(true);
|
||||
|
||||
// Send request.
|
||||
DataOutputStream outStream = new DataOutputStream(connection.getOutputStream());
|
||||
payment.writeTo(outStream);
|
||||
outStream.flush();
|
||||
outStream.close();
|
||||
|
||||
// Get response.
|
||||
Protos.PaymentACK paymentAck = Protos.PaymentACK.parseFrom(connection.getInputStream());
|
||||
return PaymentProtocol.parsePaymentAck(paymentAck);
|
||||
}, executor);
|
||||
}
|
||||
|
||||
private void parsePaymentRequest(Protos.PaymentRequest request) throws PaymentProtocolException {
|
||||
try {
|
||||
if (request == null)
|
||||
throw new PaymentProtocolException("request cannot be null");
|
||||
if (request.getPaymentDetailsVersion() != 1)
|
||||
throw new PaymentProtocolException.InvalidVersion("Version 1 required. Received version " + request.getPaymentDetailsVersion());
|
||||
paymentRequest = request;
|
||||
if (!request.hasSerializedPaymentDetails())
|
||||
throw new PaymentProtocolException("No PaymentDetails");
|
||||
paymentDetails = Protos.PaymentDetails.newBuilder().mergeFrom(request.getSerializedPaymentDetails()).build();
|
||||
if (paymentDetails == null)
|
||||
throw new PaymentProtocolException("Invalid PaymentDetails");
|
||||
if (!paymentDetails.hasNetwork())
|
||||
params = MainNetParams.get();
|
||||
else
|
||||
params = PaymentProtocol.paramsFromPmtProtocolID(paymentDetails.getNetwork());
|
||||
if (params == null)
|
||||
throw new PaymentProtocolException.InvalidNetwork("Invalid network " + paymentDetails.getNetwork());
|
||||
if (paymentDetails.getOutputsCount() < 1)
|
||||
throw new PaymentProtocolException.InvalidOutputs("No outputs");
|
||||
for (Protos.Output output : paymentDetails.getOutputsList()) {
|
||||
if (output.hasAmount())
|
||||
totalValue = totalValue.add(Coin.valueOf(output.getAmount()));
|
||||
}
|
||||
// This won't ever happen in practice. It would only happen if the user provided outputs
|
||||
// that are obviously invalid. Still, we don't want to silently overflow.
|
||||
if (params.network().exceedsMaxMoney(totalValue))
|
||||
throw new PaymentProtocolException.InvalidOutputs("The outputs are way too big.");
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new PaymentProtocolException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the value of pkiVerificationData or null if it wasn't verified at construction time. */
|
||||
@Nullable public PkiVerificationData verifyPki() {
|
||||
return pkiVerificationData;
|
||||
}
|
||||
|
||||
/** Gets the params as read from the PaymentRequest.network field: main is the default if missing. */
|
||||
public NetworkParameters getNetworkParameters() {
|
||||
return params;
|
||||
}
|
||||
|
||||
/** Returns the protobuf that this object was instantiated with. */
|
||||
public Protos.PaymentRequest getPaymentRequest() {
|
||||
return paymentRequest;
|
||||
}
|
||||
|
||||
/** Returns the protobuf that describes the payment to be made. */
|
||||
public Protos.PaymentDetails getPaymentDetails() {
|
||||
return paymentDetails;
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright by the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The BIP70 payment protocol wraps Bitcoin transactions and adds various useful features like memos, refund addresses
|
||||
* and authentication.
|
||||
*/
|
||||
package org.bitcoinj.protocols.payments;
|
|
@ -82,7 +82,6 @@ public class BitcoinURI {
|
|||
public static final String FIELD_LABEL = "label";
|
||||
public static final String FIELD_AMOUNT = "amount";
|
||||
public static final String FIELD_ADDRESS = "address";
|
||||
public static final String FIELD_PAYMENT_REQUEST_URL = "r";
|
||||
|
||||
/**
|
||||
* URI Scheme for Bitcoin network.
|
||||
|
@ -188,8 +187,8 @@ public class BitcoinURI {
|
|||
}
|
||||
}
|
||||
|
||||
if (addressToken.isEmpty() && getPaymentRequestUrl() == null) {
|
||||
throw new BitcoinURIParseException("No address and no r= parameter found");
|
||||
if (addressToken.isEmpty()) {
|
||||
throw new BitcoinURIParseException("No address found");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -287,32 +286,6 @@ public class BitcoinURI {
|
|||
return (String) parameterMap.get(FIELD_MESSAGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The URL where a payment request (as specified in BIP 70) may
|
||||
* be fetched.
|
||||
*/
|
||||
public final String getPaymentRequestUrl() {
|
||||
return (String) parameterMap.get(FIELD_PAYMENT_REQUEST_URL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URLs where a payment request (as specified in BIP 70) may be fetched. The first URL is the main URL,
|
||||
* all subsequent URLs are fallbacks.
|
||||
*/
|
||||
public List<String> getPaymentRequestUrls() {
|
||||
ArrayList<String> urls = new ArrayList<>();
|
||||
while (true) {
|
||||
int i = urls.size();
|
||||
String paramName = FIELD_PAYMENT_REQUEST_URL + (i > 0 ? Integer.toString(i) : "");
|
||||
String url = (String) parameterMap.get(paramName);
|
||||
if (url == null)
|
||||
break;
|
||||
urls.add(url);
|
||||
}
|
||||
Collections.reverse(urls);
|
||||
return urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name The name of the parameter
|
||||
* @return The parameter value, or null if not present
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package org.bitcoinj.wallet;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import org.bitcoinj.protobuf.payments.Protos.PaymentDetails;
|
||||
import org.bitcoinj.base.Address;
|
||||
import org.bitcoinj.base.Coin;
|
||||
import org.bitcoinj.crypto.AesKey;
|
||||
|
@ -228,13 +227,6 @@ public class SendRequest {
|
|||
return req;
|
||||
}
|
||||
|
||||
/** Copy data from payment request. */
|
||||
public SendRequest fromPaymentDetails(PaymentDetails paymentDetails) {
|
||||
if (paymentDetails.hasMemo())
|
||||
this.memo = paymentDetails.getMemo();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// print only the user-settable fields
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
/** Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Authors: Mike Hearn, Gavin Andresen
|
||||
*/
|
||||
|
||||
/* Notes:
|
||||
* - Endianness: All byte arrays that represent numbers (such as hashes and private keys) are Big Endian
|
||||
* - To regenerate after editing, run gradle generateProto
|
||||
*/
|
||||
|
||||
//
|
||||
// Simple Bitcoin Payment Protocol messages
|
||||
//
|
||||
// Use fields 100+ for extensions;
|
||||
// to avoid conflicts, register extensions at:
|
||||
// https://en.bitcoin.it/wiki/Payment_Request
|
||||
//
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package payments;
|
||||
|
||||
option java_package = "org.bitcoinj.protobuf.payments";
|
||||
option java_outer_classname = "Protos";
|
||||
|
||||
// Generalized form of "send payment to this/these bitcoin addresses"
|
||||
message Output {
|
||||
optional uint64 amount = 1 [default = 0]; // amount is integer-number-of-satoshis
|
||||
required bytes script = 2; // usually one of the standard Script forms
|
||||
}
|
||||
message PaymentDetails {
|
||||
optional string network = 1 [default = "main"]; // "main" or "test"
|
||||
repeated Output outputs = 2; // Where payment should be sent
|
||||
required uint64 time = 3; // Timestamp; when payment request created
|
||||
optional uint64 expires = 4; // Timestamp; when this request should be considered invalid
|
||||
optional string memo = 5; // Human-readable description of request for the customer
|
||||
optional string payment_url = 6; // URL to send Payment and get PaymentACK
|
||||
optional bytes merchant_data = 7; // Arbitrary data to include in the Payment message
|
||||
}
|
||||
message PaymentRequest {
|
||||
optional uint32 payment_details_version = 1 [default = 1];
|
||||
optional string pki_type = 2 [default = "none"]; // none / x509+sha256 / x509+sha1
|
||||
optional bytes pki_data = 3; // depends on pki_type
|
||||
required bytes serialized_payment_details = 4; // PaymentDetails
|
||||
optional bytes signature = 5; // pki-dependent signature
|
||||
}
|
||||
message X509Certificates {
|
||||
repeated bytes certificate = 1; // DER-encoded X.509 certificate chain
|
||||
}
|
||||
message Payment {
|
||||
optional bytes merchant_data = 1; // From PaymentDetails.merchant_data
|
||||
repeated bytes transactions = 2; // Signed transactions that satisfy PaymentDetails.outputs
|
||||
repeated Output refund_to = 3; // Where to send refunds, if a refund is necessary
|
||||
optional string memo = 4; // Human-readable message for the merchant
|
||||
}
|
||||
message PaymentACK {
|
||||
required Payment payment = 1; // Payment message that triggered this ACK
|
||||
optional string memo = 2; // human-readable message for customer
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.bitcoinj.crypto;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class X509UtilsTest {
|
||||
|
||||
@Test
|
||||
public void testDisplayName() throws Exception {
|
||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||
|
||||
X509Certificate clientCert = (X509Certificate) cf.generateCertificate(getClass().getResourceAsStream(
|
||||
"startssl-client.crt"));
|
||||
assertEquals("Andreas Schildbach", X509Utils.getDisplayNameFromCertificate(clientCert, false));
|
||||
|
||||
X509Certificate comodoCert = (X509Certificate) cf.generateCertificate(getClass().getResourceAsStream(
|
||||
"comodo-smime.crt"));
|
||||
assertEquals("comodo.com@schildbach.de", X509Utils.getDisplayNameFromCertificate(comodoCert, true));
|
||||
}
|
||||
}
|
|
@ -1,162 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.bitcoinj.protocols.payments;
|
||||
|
||||
import org.bitcoinj.protobuf.payments.Protos;
|
||||
import org.bitcoinj.protobuf.payments.Protos.Payment;
|
||||
import org.bitcoinj.protobuf.payments.Protos.PaymentACK;
|
||||
import org.bitcoinj.protobuf.payments.Protos.PaymentRequest;
|
||||
import org.bitcoinj.base.BitcoinNetwork;
|
||||
import org.bitcoinj.base.ScriptType;
|
||||
import org.bitcoinj.base.Address;
|
||||
import org.bitcoinj.base.Coin;
|
||||
import org.bitcoinj.base.internal.TimeUtils;
|
||||
import org.bitcoinj.crypto.ECKey;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.crypto.X509Utils;
|
||||
import org.bitcoinj.params.TestNet3Params;
|
||||
import org.bitcoinj.protocols.payments.PaymentProtocol.Output;
|
||||
import org.bitcoinj.protocols.payments.PaymentProtocol.PkiVerificationData;
|
||||
import org.bitcoinj.protocols.payments.PaymentProtocolException.PkiVerificationException;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
import org.bitcoinj.testing.FakeTxBuilder;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class PaymentProtocolTest {
|
||||
private static final NetworkParameters TESTNET = TestNet3Params.get();
|
||||
|
||||
// static test data
|
||||
private static final Coin AMOUNT = Coin.SATOSHI;
|
||||
private static final Address TO_ADDRESS = new ECKey().toAddress(ScriptType.P2PKH, BitcoinNetwork.TESTNET);
|
||||
private static final String MEMO = "memo";
|
||||
private static final String PAYMENT_URL = "https://example.com";
|
||||
private static final byte[] MERCHANT_DATA = { 0, 1, 2 };
|
||||
|
||||
private KeyStore caStore;
|
||||
private X509Certificate caCert;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
caStore = X509Utils.loadKeyStore("JKS", "password", getClass().getResourceAsStream("test-cacerts"));
|
||||
caCert = (X509Certificate) caStore.getCertificate("test-cacert");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignAndVerifyValid() throws Exception {
|
||||
Protos.PaymentRequest.Builder paymentRequest = minimalPaymentRequest().toBuilder();
|
||||
|
||||
// Sign
|
||||
KeyStore keyStore = X509Utils
|
||||
.loadKeyStore("JKS", "password", getClass().getResourceAsStream("test-valid-cert"));
|
||||
PrivateKey privateKey = (PrivateKey) keyStore.getKey("test-valid", "password".toCharArray());
|
||||
X509Certificate clientCert = (X509Certificate) keyStore.getCertificate("test-valid");
|
||||
PaymentProtocol.signPaymentRequest(paymentRequest, new X509Certificate[]{clientCert}, privateKey);
|
||||
|
||||
// Verify
|
||||
PkiVerificationData verificationData = PaymentProtocol.verifyPaymentRequestPki(paymentRequest.build(), caStore);
|
||||
assertNotNull(verificationData);
|
||||
assertEquals(caCert, verificationData.rootAuthority.getTrustedCert());
|
||||
}
|
||||
|
||||
@Test(expected = PkiVerificationException.class)
|
||||
public void testSignAndVerifyExpired() throws Exception {
|
||||
Protos.PaymentRequest.Builder paymentRequest = minimalPaymentRequest().toBuilder();
|
||||
|
||||
// Sign
|
||||
KeyStore keyStore = X509Utils.loadKeyStore("JKS", "password",
|
||||
getClass().getResourceAsStream("test-expired-cert"));
|
||||
PrivateKey privateKey = (PrivateKey) keyStore.getKey("test-expired", "password".toCharArray());
|
||||
X509Certificate clientCert = (X509Certificate) keyStore.getCertificate("test-expired");
|
||||
PaymentProtocol.signPaymentRequest(paymentRequest, new X509Certificate[]{clientCert}, privateKey);
|
||||
|
||||
// Verify
|
||||
PaymentProtocol.verifyPaymentRequestPki(paymentRequest.build(), caStore);
|
||||
}
|
||||
|
||||
private Protos.PaymentRequest minimalPaymentRequest() {
|
||||
Protos.PaymentDetails.Builder paymentDetails = Protos.PaymentDetails.newBuilder();
|
||||
paymentDetails.setTime(TimeUtils.currentTime().getEpochSecond());
|
||||
Protos.PaymentRequest.Builder paymentRequest = Protos.PaymentRequest.newBuilder();
|
||||
paymentRequest.setSerializedPaymentDetails(paymentDetails.build().toByteString());
|
||||
return paymentRequest.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPaymentRequest() throws Exception {
|
||||
// Create
|
||||
PaymentRequest paymentRequest = PaymentProtocol.createPaymentRequest(TESTNET, AMOUNT, TO_ADDRESS, MEMO,
|
||||
PAYMENT_URL, MERCHANT_DATA).build();
|
||||
byte[] paymentRequestBytes = paymentRequest.toByteArray();
|
||||
|
||||
// Parse
|
||||
PaymentSession parsedPaymentRequest = PaymentProtocol.parsePaymentRequest(PaymentRequest
|
||||
.parseFrom(paymentRequestBytes));
|
||||
final List<Output> parsedOutputs = parsedPaymentRequest.getOutputs();
|
||||
assertEquals(1, parsedOutputs.size());
|
||||
assertEquals(AMOUNT, parsedOutputs.get(0).amount);
|
||||
assertArrayEquals(ScriptBuilder.createOutputScript(TO_ADDRESS).program(), parsedOutputs.get(0).scriptData);
|
||||
assertEquals(MEMO, parsedPaymentRequest.getMemo());
|
||||
assertEquals(PAYMENT_URL, parsedPaymentRequest.getPaymentUrl());
|
||||
assertArrayEquals(MERCHANT_DATA, parsedPaymentRequest.getMerchantData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPaymentMessage() throws Exception {
|
||||
// Create
|
||||
List<Transaction> transactions = new LinkedList<>();
|
||||
transactions.add(FakeTxBuilder.createFakeTx(TESTNET.network(), AMOUNT, TO_ADDRESS));
|
||||
Coin refundAmount = Coin.SATOSHI;
|
||||
Address refundAddress = new ECKey().toAddress(ScriptType.P2PKH, BitcoinNetwork.TESTNET);
|
||||
Payment payment = PaymentProtocol.createPaymentMessage(transactions, refundAmount, refundAddress, MEMO,
|
||||
MERCHANT_DATA);
|
||||
byte[] paymentBytes = payment.toByteArray();
|
||||
|
||||
// Parse
|
||||
Payment parsedPayment = Payment.parseFrom(paymentBytes);
|
||||
List<Transaction> parsedTransactions = PaymentProtocol.parseTransactionsFromPaymentMessage(TESTNET,
|
||||
parsedPayment);
|
||||
assertEquals(transactions, parsedTransactions);
|
||||
assertEquals(1, parsedPayment.getRefundToCount());
|
||||
assertEquals(MEMO, parsedPayment.getMemo());
|
||||
assertArrayEquals(MERCHANT_DATA, parsedPayment.getMerchantData().toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPaymentAck() throws Exception {
|
||||
// Create
|
||||
Payment paymentMessage = Protos.Payment.newBuilder().build();
|
||||
PaymentACK paymentAck = PaymentProtocol.createPaymentAck(paymentMessage, MEMO);
|
||||
byte[] paymentAckBytes = paymentAck.toByteArray();
|
||||
|
||||
// Parse
|
||||
PaymentACK parsedPaymentAck = PaymentACK.parseFrom(paymentAckBytes);
|
||||
assertEquals(paymentMessage, parsedPaymentAck.getPayment());
|
||||
assertEquals(MEMO, parsedPaymentAck.getMemo());
|
||||
}
|
||||
}
|
|
@ -1,238 +0,0 @@
|
|||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
* Copyright 2014 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.bitcoinj.protocols.payments;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import org.bitcoinj.protobuf.payments.Protos;
|
||||
import org.bitcoinj.base.BitcoinNetwork;
|
||||
import org.bitcoinj.base.ScriptType;
|
||||
import org.bitcoinj.base.Address;
|
||||
import org.bitcoinj.base.Coin;
|
||||
import org.bitcoinj.base.internal.TimeUtils;
|
||||
import org.bitcoinj.core.Context;
|
||||
import org.bitcoinj.core.TransactionOutPoint;
|
||||
import org.bitcoinj.crypto.ECKey;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.core.TransactionInput;
|
||||
import org.bitcoinj.core.TransactionOutput;
|
||||
import org.bitcoinj.crypto.TrustStoreLoader;
|
||||
import org.bitcoinj.params.MainNetParams;
|
||||
import org.bitcoinj.params.TestNet3Params;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static org.bitcoinj.base.Coin.COIN;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class PaymentSessionTest {
|
||||
private static final NetworkParameters TESTNET = TestNet3Params.get();
|
||||
private static final NetworkParameters MAINNET = MainNetParams.get();
|
||||
|
||||
private static final String simplePaymentUrl = "http://a.simple.url.com/";
|
||||
private static final String paymentRequestMemo = "send coinz noa plz kthx";
|
||||
private static final String paymentMemo = "take ze coinz";
|
||||
private static final ByteString merchantData = ByteString.copyFromUtf8("merchant data");
|
||||
private static final Instant time = TimeUtils.currentTime().truncatedTo(ChronoUnit.SECONDS);
|
||||
private ECKey serverKey;
|
||||
private Transaction tx;
|
||||
private TransactionOutput outputToMe;
|
||||
private final Coin amount = COIN;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Context.propagate(new Context());
|
||||
serverKey = new ECKey();
|
||||
tx = new Transaction();
|
||||
outputToMe = new TransactionOutput(tx, amount, serverKey);
|
||||
tx.addOutput(outputToMe);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimplePayment() throws Exception {
|
||||
// Create a PaymentRequest and make sure the correct values are parsed by the PaymentSession.
|
||||
MockPaymentSession paymentSession = new MockPaymentSession(newSimplePaymentRequest("test"));
|
||||
assertEquals(paymentRequestMemo, paymentSession.getMemo());
|
||||
assertEquals(amount, paymentSession.getValue());
|
||||
assertEquals(simplePaymentUrl, paymentSession.getPaymentUrl());
|
||||
assertEquals(time, paymentSession.time());
|
||||
assertTrue(paymentSession.getSendRequest().tx.equals(tx));
|
||||
assertFalse(paymentSession.isExpired());
|
||||
|
||||
// Send the payment and verify that the correct information is sent.
|
||||
// Add a dummy input to tx so it is considered valid.
|
||||
tx.addInput(new TransactionInput(tx, outputToMe.getScriptBytes(), TransactionOutPoint.UNCONNECTED));
|
||||
ArrayList<Transaction> txns = new ArrayList<>();
|
||||
txns.add(tx);
|
||||
Address refundAddr = serverKey.toAddress(ScriptType.P2PKH, BitcoinNetwork.TESTNET);
|
||||
paymentSession.sendPayment(txns, refundAddr, paymentMemo);
|
||||
assertEquals(1, paymentSession.getPaymentLog().size());
|
||||
assertEquals(simplePaymentUrl, paymentSession.getPaymentLog().get(0).getUrl().toString());
|
||||
Protos.Payment payment = paymentSession.getPaymentLog().get(0).getPayment();
|
||||
assertEquals(paymentMemo, payment.getMemo());
|
||||
assertEquals(merchantData, payment.getMerchantData());
|
||||
assertEquals(1, payment.getRefundToCount());
|
||||
assertEquals(amount.value, payment.getRefundTo(0).getAmount());
|
||||
TransactionOutput refundOutput = new TransactionOutput(null, amount, refundAddr);
|
||||
ByteString refundScript = ByteString.copyFrom(refundOutput.getScriptBytes());
|
||||
assertTrue(refundScript.equals(payment.getRefundTo(0).getScript()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaults() throws Exception {
|
||||
Protos.Output.Builder outputBuilder = Protos.Output.newBuilder()
|
||||
.setScript(ByteString.copyFrom(outputToMe.getScriptBytes()));
|
||||
Protos.PaymentDetails paymentDetails = Protos.PaymentDetails.newBuilder()
|
||||
.setTime(time.getEpochSecond())
|
||||
.addOutputs(outputBuilder)
|
||||
.build();
|
||||
Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.newBuilder()
|
||||
.setSerializedPaymentDetails(paymentDetails.toByteString())
|
||||
.build();
|
||||
MockPaymentSession paymentSession = new MockPaymentSession(paymentRequest);
|
||||
assertEquals(Coin.ZERO, paymentSession.getValue());
|
||||
assertNull(paymentSession.getPaymentUrl());
|
||||
assertNull(paymentSession.getMemo());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpiredPaymentRequest() throws PaymentProtocolException {
|
||||
MockPaymentSession paymentSession = new MockPaymentSession(newExpiredPaymentRequest());
|
||||
assertTrue(paymentSession.isExpired());
|
||||
// Send the payment and verify that an exception is thrown.
|
||||
// Add a dummy input to tx so it is considered valid.
|
||||
tx.addInput(new TransactionInput(tx, outputToMe.getScriptBytes(), TransactionOutPoint.UNCONNECTED));
|
||||
ArrayList<Transaction> txns = new ArrayList<>();
|
||||
txns.add(tx);
|
||||
|
||||
CompletableFuture<PaymentProtocol.Ack> ack = paymentSession.sendPayment(txns, null, null);
|
||||
try {
|
||||
ack.get();
|
||||
} catch (ExecutionException e) {
|
||||
if (e.getCause() instanceof PaymentProtocolException.Expired) {
|
||||
PaymentProtocolException.Expired cause = (PaymentProtocolException.Expired) e.getCause();
|
||||
assertEquals(0, paymentSession.getPaymentLog().size());
|
||||
assertEquals(cause.getMessage(), "PaymentRequest is expired");
|
||||
return;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
fail("Expected exception due to expired PaymentRequest");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("certificate expired")
|
||||
public void testPkiVerification() throws Exception {
|
||||
InputStream in = getClass().getResourceAsStream("pki_test.bitcoinpaymentrequest");
|
||||
Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.newBuilder().mergeFrom(in).build();
|
||||
PaymentProtocol.PkiVerificationData pkiData = PaymentProtocol.verifyPaymentRequestPki(paymentRequest,
|
||||
new TrustStoreLoader.DefaultTrustStoreLoader().getKeyStore());
|
||||
assertEquals("www.bitcoincore.org", pkiData.displayName);
|
||||
assertEquals("The USERTRUST Network, Salt Lake City, US", pkiData.rootAuthorityName);
|
||||
}
|
||||
|
||||
private Protos.PaymentRequest newSimplePaymentRequest(String netID) {
|
||||
Protos.Output.Builder outputBuilder = Protos.Output.newBuilder()
|
||||
.setAmount(amount.value)
|
||||
.setScript(ByteString.copyFrom(outputToMe.getScriptBytes()));
|
||||
Protos.PaymentDetails paymentDetails = Protos.PaymentDetails.newBuilder()
|
||||
.setNetwork(netID)
|
||||
.setTime(time.getEpochSecond())
|
||||
.setPaymentUrl(simplePaymentUrl)
|
||||
.addOutputs(outputBuilder)
|
||||
.setMemo(paymentRequestMemo)
|
||||
.setMerchantData(merchantData)
|
||||
.build();
|
||||
return Protos.PaymentRequest.newBuilder()
|
||||
.setPaymentDetailsVersion(1)
|
||||
.setPkiType("none")
|
||||
.setSerializedPaymentDetails(paymentDetails.toByteString())
|
||||
.build();
|
||||
}
|
||||
|
||||
private Protos.PaymentRequest newExpiredPaymentRequest() {
|
||||
Protos.Output.Builder outputBuilder = Protos.Output.newBuilder()
|
||||
.setAmount(amount.value)
|
||||
.setScript(ByteString.copyFrom(outputToMe.getScriptBytes()));
|
||||
Protos.PaymentDetails paymentDetails = Protos.PaymentDetails.newBuilder()
|
||||
.setNetwork("test")
|
||||
.setTime(time.minusSeconds(10).getEpochSecond())
|
||||
.setExpires(time.minusSeconds(1).getEpochSecond())
|
||||
.setPaymentUrl(simplePaymentUrl)
|
||||
.addOutputs(outputBuilder)
|
||||
.setMemo(paymentRequestMemo)
|
||||
.setMerchantData(merchantData)
|
||||
.build();
|
||||
return Protos.PaymentRequest.newBuilder()
|
||||
.setPaymentDetailsVersion(1)
|
||||
.setPkiType("none")
|
||||
.setSerializedPaymentDetails(paymentDetails.toByteString())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static class MockPaymentSession extends PaymentSession {
|
||||
private final ArrayList<PaymentLogItem> paymentLog = new ArrayList<>();
|
||||
|
||||
public MockPaymentSession(Protos.PaymentRequest request) throws PaymentProtocolException {
|
||||
super(request);
|
||||
}
|
||||
|
||||
public ArrayList<PaymentLogItem> getPaymentLog() {
|
||||
return paymentLog;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<PaymentProtocol.Ack> sendPayment(final URL url, final Protos.Payment payment) {
|
||||
paymentLog.add(new PaymentLogItem(url, payment));
|
||||
// Return a completed future that has a `null` value. This will satisfy the current tests.
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
public static class PaymentLogItem {
|
||||
private final URL url;
|
||||
private final Protos.Payment payment;
|
||||
|
||||
PaymentLogItem(final URL url, final Protos.Payment payment) {
|
||||
this.url = url;
|
||||
this.payment = payment;
|
||||
}
|
||||
|
||||
public URL getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public Protos.Payment getPayment() {
|
||||
return payment;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -427,37 +427,4 @@ public class BitcoinURITest {
|
|||
BitcoinURI.of(BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
|
||||
+ "?amount=" + tooLargeByOne.movePointLeft(8), MAINNET);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPaymentProtocolReq() throws Exception {
|
||||
// Non-backwards compatible form ...
|
||||
BitcoinURI uri = BitcoinURI.of("bitcoin:?r=https%3A%2F%2Fbitcoincore.org%2F%7Egavin%2Ff.php%3Fh%3Db0f02e7cea67f168e25ec9b9f9d584f9", TESTNET);
|
||||
assertEquals("https://bitcoincore.org/~gavin/f.php?h=b0f02e7cea67f168e25ec9b9f9d584f9", uri.getPaymentRequestUrl());
|
||||
assertEquals(Collections.singletonList("https://bitcoincore.org/~gavin/f.php?h=b0f02e7cea67f168e25ec9b9f9d584f9"),
|
||||
uri.getPaymentRequestUrls());
|
||||
assertNull(uri.getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultiplePaymentProtocolReq() throws Exception {
|
||||
BitcoinURI uri = BitcoinURI.of("bitcoin:?r=https%3A%2F%2Fbitcoincore.org%2F%7Egavin&r1=bt:112233445566", MAINNET);
|
||||
assertEquals(Arrays.asList("bt:112233445566", "https://bitcoincore.org/~gavin"), uri.getPaymentRequestUrls());
|
||||
assertEquals("https://bitcoincore.org/~gavin", uri.getPaymentRequestUrl());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoPaymentProtocolReq() throws Exception {
|
||||
BitcoinURI uri = BitcoinURI.of("bitcoin:" + MAINNET_GOOD_ADDRESS, MAINNET);
|
||||
assertNull(uri.getPaymentRequestUrl());
|
||||
assertEquals(Collections.emptyList(), uri.getPaymentRequestUrls());
|
||||
assertNotNull(uri.getAddress());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnescapedPaymentProtocolReq() throws Exception {
|
||||
BitcoinURI uri = BitcoinURI.of("bitcoin:?r=https://merchant.example.com/pay?h%3D2a8628fc2fbe", TESTNET);
|
||||
assertEquals("https://merchant.example.com/pay?h=2a8628fc2fbe", uri.getPaymentRequestUrl());
|
||||
assertEquals(Collections.singletonList("https://merchant.example.com/pay?h=2a8628fc2fbe"), uri.getPaymentRequestUrls());
|
||||
assertNull(uri.getAddress());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFMTCCBBmgAwIBAgIQV2O4gJQ5NHgn/PPvDsU1djANBgkqhkiG9w0BAQUFADCB
|
||||
kzELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G
|
||||
A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxOTA3BgNV
|
||||
BAMTMENPTU9ETyBDbGllbnQgQXV0aGVudGljYXRpb24gYW5kIFNlY3VyZSBFbWFp
|
||||
bCBDQTAeFw0xNDAzMjkwMDAwMDBaFw0xNTAzMjkyMzU5NTlaMCkxJzAlBgkqhkiG
|
||||
9w0BCQEWGGNvbW9kby5jb21Ac2NoaWxkYmFjaC5kZTCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBAM61nbZo3ZN0Ojzn7UzoHBf07ZyTDm3KnwK4BdLKLgNS
|
||||
NbGAJtgaYN91qKRbXf97VAFIN6FGhoXT+7MXSzlHgQHn7RkForMyREsD6F32TtyV
|
||||
ZY9RuMGWjmPtABPRgeCVfNJNh9Hu87Uhhkj3Ma+H//ykfkJdDiOyBWIOJdjBFSZZ
|
||||
M6bsZnyH8JCHqmxvK2qHpk+qNqpsNOZV83GYPA2gTFWd1AHjo5+A7W1Bo/qyJMrz
|
||||
tpab0i+ieJPJdi6eJkMt3+nfr57Q2o4A3ZxH0Axq2D1a2dElhMK/JQilh2D+IDUp
|
||||
VjoKkHgV9yji9UGOc3VHq+Sx8bNTumL7OFLCFYky9J8CAwEAAaOCAegwggHkMB8G
|
||||
A1UdIwQYMBaAFHoTTgB0W8Z4Y2QnwS/ioFu8ecV7MB0GA1UdDgQWBBRWUNGbH8V3
|
||||
av0ESrIUwnvmq4aEEDAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAgBgNV
|
||||
HSUEGTAXBggrBgEFBQcDBAYLKwYBBAGyMQEDBQIwEQYJYIZIAYb4QgEBBAQDAgUg
|
||||
MEYGA1UdIAQ/MD0wOwYMKwYBBAGyMQECAQEBMCswKQYIKwYBBQUHAgEWHWh0dHBz
|
||||
Oi8vc2VjdXJlLmNvbW9kby5uZXQvQ1BTMFcGA1UdHwRQME4wTKBKoEiGRmh0dHA6
|
||||
Ly9jcmwuY29tb2RvY2EuY29tL0NPTU9ET0NsaWVudEF1dGhlbnRpY2F0aW9uYW5k
|
||||
U2VjdXJlRW1haWxDQS5jcmwwgYgGCCsGAQUFBwEBBHwwejBSBggrBgEFBQcwAoZG
|
||||
aHR0cDovL2NydC5jb21vZG9jYS5jb20vQ09NT0RPQ2xpZW50QXV0aGVudGljYXRp
|
||||
b25hbmRTZWN1cmVFbWFpbENBLmNydDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3Au
|
||||
Y29tb2RvY2EuY29tMCMGA1UdEQQcMBqBGGNvbW9kby5jb21Ac2NoaWxkYmFjaC5k
|
||||
ZTANBgkqhkiG9w0BAQUFAAOCAQEAMFhmP1Zy16m5L9gaGCDy847tJI3btBFFZMu/
|
||||
MMqamC5515QayLfwf9K2nmu1W63nehEAKqNw+PR1xTYnhPT4fopw5zFndiNg0L5u
|
||||
blEbRgSdQYBh1I2dkzzPRDRJig4LfxVzRzL66FbllLEiJ6oR/XgdsH+JFgyjhk3Y
|
||||
uJt+29sXoZ+ZR29d7l07OikQGI0HWCmp/UiwBcQ4dcTrDB72JYLHyli+OTAkcu9I
|
||||
rBpsIbWJq+7NjaQ/8CJjvQ2neTgDS1Dq5DzMqqRlhxQwRl4dhfCSCcF81Vf0as4S
|
||||
vVDNR8vJ9puGlYyGVJHhQ6mEoFEIvpetS7E9ELHnybSC9ev8CA==
|
||||
-----END CERTIFICATE-----
|
|
@ -1,41 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIHPjCCBiagAwIBAgICH8wwDQYJKoZIhvcNAQEFBQAwgYwxCzAJBgNVBAYTAklM
|
||||
MRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSswKQYDVQQLEyJTZWN1cmUgRGlnaXRh
|
||||
bCBDZXJ0aWZpY2F0ZSBTaWduaW5nMTgwNgYDVQQDEy9TdGFydENvbSBDbGFzcyAx
|
||||
IFByaW1hcnkgSW50ZXJtZWRpYXRlIENsaWVudCBDQTAeFw0wODA2MjIyMzA4MTJa
|
||||
Fw0wOTA2MjIyMzA4MTJaMIHEMQswCQYDVQQGEwJERTEPMA0GA1UECBMGQmF5ZXJu
|
||||
MREwDwYDVQQHEwhNdWVuY2hlbjEbMBkGA1UEChMSQW5kcmVhcyBTY2hpbGRiYWNo
|
||||
MR4wHAYDVQQLExVQZXJzb25hIG5vdCB2YWxpZGF0ZWQxKTAnBgNVBAMTIFN0YXJ0
|
||||
Q29tIEZyZWUgQ2VydGlmaWNhdGUgTWVtYmVyMSkwJwYJKoZIhvcNAQkBFhpzdGFy
|
||||
dHNzbC5jb21Ac2NoaWxkYmFjaC5kZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
|
||||
AQoCggEBAOhtSryPHR3gB5E48JZS9y8R3A9I2gRGGon+kRLNh+LCAJJ1hm28Lr41
|
||||
leH0uApWs1WP//qzkXaFUoLlilu/XzkDU48J6HCeUq+7zkxFhq7UxK2lq7J1P8fH
|
||||
tbKYSBWMfzZuVmwhrbaurggfCmq/o5/angbhN7Pn+aV1aPegjAKd8n94HVvVgzkp
|
||||
DDYTA5vFbX/3241MIeKRU5InYw9KzXAC1aE0BYVM21f5Z/UQ+V4PEfXrSH9OHPVW
|
||||
3GWasmjzR9h+/u1omJzejkXY6Ygd15tnmatqMoxRRVMWhWS9Hg7D+AgeiZNlEQZV
|
||||
NTZxIiPA8618x51Wlq0XAiV5UvkMImECAwEAAaOCA24wggNqMAwGA1UdEwQFMAMC
|
||||
AQAwCwYDVR0PBAQDAgSwMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDBDAd
|
||||
BgNVHQ4EFgQUeMIc15HHtjl2TDpGnc9X2UkUV2EwgagGA1UdIwSBoDCBnYAUU3Lt
|
||||
kpzg2ssBXHx+ljVO8tS4UYKhgYGkfzB9MQswCQYDVQQGEwJJTDEWMBQGA1UEChMN
|
||||
U3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNh
|
||||
dGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRo
|
||||
b3JpdHmCAQ0wggFHBgNVHSAEggE+MIIBOjCCATYGCysGAQQBgbU3AQEFMIIBJTAu
|
||||
BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0
|
||||
BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl
|
||||
LnBkZjCBvAYIKwYBBQUHAgIwga8wFBYNU3RhcnRDb20gTHRkLjADAgEBGoGWTGlt
|
||||
aXRlZCBMaWFiaWxpdHksIHJlYWQgdGhlIHNlY3Rpb24gKkxlZ2FsIExpbWl0YXRp
|
||||
b25zKiBvZiB0aGUgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUG9s
|
||||
aWN5IGF2YWlsYWJsZSBhdCBodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS9wb2xpY3ku
|
||||
cGRmMGMGA1UdHwRcMFowK6ApoCeGJWh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2Ny
|
||||
dHUxLWNybC5jcmwwK6ApoCeGJWh0dHA6Ly9jcmwuc3RhcnRzc2wuY29tL2NydHUx
|
||||
LWNybC5jcmwwgY4GCCsGAQUFBwEBBIGBMH8wOQYIKwYBBQUHMAGGLWh0dHA6Ly9v
|
||||
Y3NwLnN0YXJ0c3NsLmNvbS9zdWIvY2xhc3MxL2NsaWVudC9jYTBCBggrBgEFBQcw
|
||||
AoY2aHR0cDovL3d3dy5zdGFydHNzbC5jb20vY2VydHMvc3ViLmNsYXNzMS5jbGll
|
||||
bnQuY2EuY3J0MCMGA1UdEgQcMBqGGGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tLzAN
|
||||
BgkqhkiG9w0BAQUFAAOCAQEAIsKaduVCShmIKRsUCqTSeJSDIjgSdRiRmvDig+vT
|
||||
NkRrfhZhtpkp03lfe71agFlaV6UWtFF2nrgFaoUmq2KxMnCD1gkQPQu01TqrDDwi
|
||||
+dKFkh4tSGyj++BRCX4jpYgY7pDzh0Dtb261ovpzYB3e36mMO4AiLHby10VHir+k
|
||||
AUI87JVffsgsKCEEEkywA//KcXqyVfgW3FgicNczCiwXdWCLJcnBAq8aundebdIH
|
||||
hTFoWB/5BuRRCY2Je9XFR8vb1EUC5SuTL+wT0mGdx2T+qNskNtbZKyHLQSp9fCoD
|
||||
yupR1THhr7iqF4zRI6r5r8tRuu8jr55NgN5ZA+LCisEJ7A==
|
||||
-----END CERTIFICATE-----
|
|
@ -1,40 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIHFDCCBfygAwIBAgIDAILwMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ
|
||||
TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0
|
||||
YWwgQ2VydGlmaWNhdGUgU2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3Mg
|
||||
MSBQcmltYXJ5IEludGVybWVkaWF0ZSBDbGllbnQgQ0EwHhcNMDkwNjA5MDAwMDAx
|
||||
WhcNMTAwNjA5MjM1OTU5WjB2MR4wHAYDVQQKExVQZXJzb25hIE5vdCBWYWxpZGF0
|
||||
ZWQxKTAnBgNVBAMTIFN0YXJ0Q29tIEZyZWUgQ2VydGlmaWNhdGUgTWVtYmVyMSkw
|
||||
JwYJKoZIhvcNAQkBFhpzdGFydHNzbC5jb21Ac2NoaWxkYmFjaC5kZTCCASIwDQYJ
|
||||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALA4dtu1FlTbhZQ+M9pyTFte40zotk3J
|
||||
fqvEkDpWLBz3orN4SkMAUDOgTWdvNm+PakX2tEsZGD+nnWzkO3NI8x5ZhrOF3HwW
|
||||
zeCaYzaDjhhRw4G1K2FKVBHK6TUkZ/LoLimVMsV8AbsAWWlmxTCXB1vyoiOMISiK
|
||||
rMFsRFAQdtB5wHVuZdtVnO1++yLfQo+ckuTT35RBztpcP63GkVyo0ucFC8DxNQOA
|
||||
+k8cEIVrfsr9PrLUlhTx+P5jQAaURqcVf0IAR6bNV7WdJmli7yjlWeQm7ymh8YFE
|
||||
6xsy16TO24GQWR5waBFRqGaJPqRnpAdhiUc+1nbNGuaOYYCD6kATVycCAwEAAaOC
|
||||
A5IwggOOMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdJQQWMBQGCCsGAQUF
|
||||
BwMCBggrBgEFBQcDBDAdBgNVHQ4EFgQUY819Gv9zSrHpLGP3e9WbCJfzEgAwJQYD
|
||||
VR0RBB4wHIEac3RhcnRzc2wuY29tQHNjaGlsZGJhY2guZGUwgagGA1UdIwSBoDCB
|
||||
nYAUU3Ltkpzg2ssBXHx+ljVO8tS4UYKhgYGkfzB9MQswCQYDVQQGEwJJTDEWMBQG
|
||||
A1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwgQ2Vy
|
||||
dGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNhdGlv
|
||||
biBBdXRob3JpdHmCAQ0wggFHBgNVHSAEggE+MIIBOjCCATYGCysGAQQBgbU3AQIA
|
||||
MIIBJTAuBggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5
|
||||
LnBkZjA0BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJt
|
||||
ZWRpYXRlLnBkZjCBvAYIKwYBBQUHAgIwga8wFBYNU3RhcnRDb20gTHRkLjADAgEB
|
||||
GoGWTGltaXRlZCBMaWFiaWxpdHksIHJlYWQgdGhlIHNlY3Rpb24gKkxlZ2FsIExp
|
||||
bWl0YXRpb25zKiBvZiB0aGUgU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3Jp
|
||||
dHkgUG9saWN5IGF2YWlsYWJsZSBhdCBodHRwOi8vd3d3LnN0YXJ0c3NsLmNvbS9w
|
||||
b2xpY3kucGRmMGMGA1UdHwRcMFowK6ApoCeGJWh0dHA6Ly93d3cuc3RhcnRzc2wu
|
||||
Y29tL2NydHUxLWNybC5jcmwwK6ApoCeGJWh0dHA6Ly9jcmwuc3RhcnRzc2wuY29t
|
||||
L2NydHUxLWNybC5jcmwwgY4GCCsGAQUFBwEBBIGBMH8wOQYIKwYBBQUHMAGGLWh0
|
||||
dHA6Ly9vY3NwLnN0YXJ0c3NsLmNvbS9zdWIvY2xhc3MxL2NsaWVudC9jYTBCBggr
|
||||
BgEFBQcwAoY2aHR0cDovL3d3dy5zdGFydHNzbC5jb20vY2VydHMvc3ViLmNsYXNz
|
||||
MS5jbGllbnQuY2EuY3J0MCMGA1UdEgQcMBqGGGh0dHA6Ly93d3cuc3RhcnRzc2wu
|
||||
Y29tLzANBgkqhkiG9w0BAQUFAAOCAQEAaJgOEPjkRcKMVbbofA+GVlc1iMR+kJHk
|
||||
bQNmojAmgDL1pXabFuNZqx7FVUBk7MQQOUaC1vd3RbyOE+AzdXaq7/pFk5/Zxalv
|
||||
xn4gSA/wGHDB0oAi+efWQy7ZsskIOWkjg7tKqy0KCRlA6ZlQhL0aTFZe2X6fu/fI
|
||||
eebFb6gQ3vhOwIgAGz7CZMjRBqPjiqpPrD/Uac2LORUdLw/wowTV1YBnNwsZGnzJ
|
||||
/WquZB7n/yJjaSqhSL0s37AOg3TvXEXYS2GpoA02lQKfq3Lo86piAxSh7aJf7dpT
|
||||
JMVnE6/+5FyjpP8Hpl8FARv1m51c9n788cDzS4/qFibKf9s6yt1/0A==
|
||||
-----END CERTIFICATE-----
|
|
@ -1,6 +0,0 @@
|
|||
# Create key store for CA certificate
|
||||
keytool -keystore test-cacerts -importcert -file test-cacert.pem -alias test-cacert -deststorepass password
|
||||
|
||||
# Create key store for certificate and private key
|
||||
openssl pkcs12 -export -in test-valid-cert.pem -inkey test-valid-key.pem -passin pass:password -out test-valid.p12 -passout pass:password -name test-valid
|
||||
keytool -importkeystore -deststorepass password -destkeypass password -destkeystore test-valid-cert -srckeystore test-valid.p12 -srcstoretype PKCS12 -srcstorepass password -alias test-valid
|
Binary file not shown.
|
@ -1,54 +0,0 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
Proc-Type: 4,ENCRYPTED
|
||||
DEK-Info: DES-EDE3-CBC,621C9B6A0E9C192A
|
||||
|
||||
zj5A0/qeYJ5rmvJqcNoQxaUyEonVZQgLj9Jxw76Wvc1kIMNXsT7MGq2jHaDaUMVd
|
||||
LZDhRgNtirGVdfMFvsNGMB+F1SDq3Dftn/w1jKsNRwCXR958r7ciYp37ohldTEuX
|
||||
0rPQfltHQHkwpCDfi7xOQa06wdSA+Ke8Sek9u77RRhT+nEWOLwJHaIyIbSQr835q
|
||||
bcdW7oTFpAj7OQnEgpKMi2fU9t89N4X+F3foarfSwWGP+e8o9J7/UKKmrWGkB30Q
|
||||
H3U4CbZTXP07ti+ALh86/UaBdaZdDp/pXcJK9OyRSUw693NutSRFw5bXcbobessG
|
||||
lRcgJ5lrKy3F4WhyZMCQNTgr3LzymVn4DkBI3aNCI3a3hbIUi6BRyLF2zUZan5Pl
|
||||
IhsTQM4HLGpuq1AfbWwPvSWoTh+uI9qM9Fenc9kwZvkiMYDf+7kiY6Pt0e8gg16j
|
||||
QQoemZeI+TivyBVerUPV1j+0eBru6ogdxiWoBmZFTFjwpqObV05Zm1BxVAjp3x3+
|
||||
q2gzaYdV/Gca0IaHnmqlZQHwWv5Z0+79bGFta2ThiUR+7Ofgteh0ojlAmAvpHp0n
|
||||
cy6NNr4T81z6QpsHM1zeKbiuUgLlCchwEH0fCsHl0u/EJ7i+4bbugyTdAUiULqxH
|
||||
OpHU2FpuNvSS6BZc2fCmdPflG9yP1jOoAEKb7bcdPRfiJoyMf/ngNaSXsrjzs7NA
|
||||
Z2fZyKC45wtmfpfcQuHP+svWafDx3DCNhQ7Wo7YFBFacV242cjLaYnkgy8XHFHjM
|
||||
KW1Wct5Xg5FEnEcyhczLTZbylSwap9vTAk65cUjLmp/7fE8pLKGfvdRA6VLfbJQ/
|
||||
lnJvBI8valjtCfEMC8s5dNcsn7ISFyTzKMaxjs8DxBRg5eKwr/BPVeXkP21Cf+sO
|
||||
OUAZcgOBJxhb/MQ68Rx2pcjTbmOG81LINkCrwfNGez1gVg3CELneIZGi2ZSn2s5r
|
||||
tFXVJvCrUpSGvx3IC4xKXJ6R6euBE7JdxG0J8aizytlorEhSk294ksupEQUjdeEW
|
||||
RPHqp3LqVlRO1tFYZQrwZs26vgXa15LYXq63EewUCIM4TjNTm3FuovW1d11mCiFl
|
||||
dljkCMGYN+14YnHzd3G96JGxkZUlOCFN+y5Xa2I1x4jkUCvdD3gfMXBxGptkyT+K
|
||||
DlJJQ8JMrRMhRu2K/t8LVorPyHGoYRUoaXLgx/x8xgazLiA8vY8sCJ9A+YM78Bjd
|
||||
ytGQeuUzt5syAXuW3s6AStuMH0Scos674i0qcsd2j5TY4Td6+svuCluvKvsmWW4/
|
||||
pDPNW5c/TXMVU3J9H1AnuHoNQ7rX9++qbptOQHXsf2MY9HtgzYDuY4i/vmJFtTVe
|
||||
zbUqh3trTJ/ihvzq6to6b/AZqsljit7PzLBkXlmQozvlAqy3ycWvyFnVjPgjyEm5
|
||||
9C3uwnD4hiXfmf46nMv+YkaAhYESavLKkbcdQ+cONYnQ2zTJJaKLMHbl+TI5Tjph
|
||||
azuTmQKG72/V8aQBbIVgppoN7FtlIAvZl+k48uZAj2uN7/15ceIBP9Vh0O07EmNa
|
||||
birsXDWm2naQNKYBGrbMCRol8NSnqT3MvG075uZ0mXcyKO39YNxJmdfYgQohTosC
|
||||
dYD20mqdYKbygtvjfucXJoEUJKcB2I795XeZoZl32JgoE0h7q7gLtnUrSnyw8YVo
|
||||
C4s4DL9wXaxuReXQVGczHwPtW9X1Hjm07iuBbkb10Adbl03bd+5J4XbVtdjlshOw
|
||||
YVXHXLY5NfS+hu6jErxNNDjHY0lT+rwif4S9eBjepnpXyZWQRwoJ3SooyK9D6YR4
|
||||
4wpxc4JEglXOPtzqjErGBYxZZdZSD04eVjDV22+Hf6GVcFyEjEFqaEMl0NzOyi+n
|
||||
Svf7ixeo6x3xfUnx9T0mv4YqP62t/RkM9nkFcwKQkZ59cNw+/7XD6DgxO6ev5/25
|
||||
DemqP83feR2MEgwwfh2txi9N5WM0/zJhfxbyv3hImfKa6Br2UoC3O57uKyRVEQ+7
|
||||
dP6wOjJ5swe6kCvk4yj60V/Vr0urVW8xC6IN+2FIfiocJS8mIY5NZRd0CbNkiuZP
|
||||
ntVJwgQ2xwbIOp/+4Od3vBI6ujWmXyaR8k9EpnhM5wP7fb38PprxKdLjRLuGEPNP
|
||||
Z+Cov09oh6cR1GrBqM/gH3mggLZgO4h4WD8gKFkmepXit9Xcigd0ytXfDWE2jgx8
|
||||
du41SrDqYOwVwbkarRA9NDf5Db9dm/vNnw31/cVwUq10Mb7QD/wXPwSIpQbvyJBt
|
||||
1tYcH983hNFKQddNRm+hBP9Vz+nhHPneGjTKU6SxDMcQ3Baep3DwRPl9r9x09z0Y
|
||||
xkBUcCUZ5q+wgFepXYCB9lOBtATnFViFnTdvQuXqvLeOWDjAHBT3TDxzaB+vJMal
|
||||
qD+S4x3BSO64ZIxepl4CHa1Zajp8qzVWYh1rBlTL/Uqjg3O5MuvfO0DGpRCn/6t0
|
||||
CpJF/bcmRBogOvNM7zjMcDSRtIMOVgsfCqqIyXpvA5yZqocwd2o8tdiG0hKQxASV
|
||||
qxvTNTXQZut/xD0enHtDaoYYTHj2c6Ayu7DZfPHvcx40mdYjGI9ZKvXcSrYkfTqc
|
||||
mugXa29jah+1abRWfokV/0S9xuFTkBGPt8/Byw4oT7e8nkJGFQorObqj4gSJhCOf
|
||||
/+wTqhkappSHM0mkF72mNqJLWqMPGFnUq/5x5hws3qNgDbk48TDMWKyM3UEn8IUb
|
||||
zQViQNNVRxtZGTXO8Yhpqv/LgD3I9nk99xFALcBncrkrBIK121pVVM1Yf4TjsdCx
|
||||
zqykQY0cghi6zVE8wkDj6f7Sh8uYe5fP1gorp0rzxyKQh1RwsI6po3GmAA12aIiX
|
||||
3Z5XHE3qL7zeaiN0Mc8EkA7zVVI5Id8Wssdmf3TWRG36J99qzUWxiMsL3lKvgi9d
|
||||
X5Bbxo5l+3hdC31jqrt1w2vemr4bSs5SyBQMnBL2HEsbk7PWzbxD/NdKWYecyYAz
|
||||
OtFy7vAtURxYvS0IbX4uRoeAehyJ7+Ak7tulM4MG9O+w/WseBV7VS1beI9NLZg3S
|
||||
tLLTc/EyLsR9us5AMjDeuIoOSN4mEtFzlq72nyVbUGELN7fXl8Hv75QYh7/SUZt4
|
||||
nH+DbSxMon0SybKTywuwuSoFo/K2lwfyZvNb4R0ImlPpJobANG0knKgzKCTA+m5P
|
||||
-----END RSA PRIVATE KEY-----
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,111 +0,0 @@
|
|||
/*
|
||||
* Copyright 2014 The bitcoinj team
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.bitcoinj.tools;
|
||||
|
||||
import org.bitcoinj.base.internal.TimeUtils;
|
||||
import org.bitcoinj.crypto.TrustStoreLoader;
|
||||
import org.bitcoinj.protobuf.payments.Protos;
|
||||
import org.bitcoinj.protocols.payments.PaymentProtocol;
|
||||
import org.bitcoinj.protocols.payments.PaymentProtocolException;
|
||||
import org.bitcoinj.protocols.payments.PaymentSession;
|
||||
import org.bitcoinj.uri.BitcoinURI;
|
||||
import org.bitcoinj.uri.BitcoinURIParseException;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
/** Takes a URL or bitcoin URI and prints information about the payment request. */
|
||||
public class PaymentProtocolTool {
|
||||
public static void main(String[] args) {
|
||||
if (args.length < 1) {
|
||||
System.err.println("Provide a bitcoin URI or URL as the argument.");
|
||||
return;
|
||||
}
|
||||
dump(args[0]);
|
||||
}
|
||||
|
||||
private static void dump(String arg) {
|
||||
try {
|
||||
URI uri = new URI(arg);
|
||||
PaymentSession session;
|
||||
if (arg.startsWith("/")) {
|
||||
FileInputStream stream = new FileInputStream(arg);
|
||||
Protos.PaymentRequest request = Protos.PaymentRequest.parseFrom(stream);
|
||||
stream.close();
|
||||
session = new PaymentSession(request);
|
||||
} else if ("http".equals(uri.getScheme())) {
|
||||
session = PaymentSession.createFromUrl(arg).get();
|
||||
} else if ("bitcoin".equals(uri.getScheme())) {
|
||||
BitcoinURI bcuri = BitcoinURI.of(arg);
|
||||
final String paymentRequestUrl = bcuri.getPaymentRequestUrl();
|
||||
if (paymentRequestUrl == null) {
|
||||
System.err.println("No r= param in bitcoin URI");
|
||||
return;
|
||||
}
|
||||
session = PaymentSession.createFromBitcoinUri(bcuri).get();
|
||||
} else {
|
||||
System.err.println("Unknown URI scheme: " + uri.getScheme());
|
||||
return;
|
||||
}
|
||||
final int version = session.getPaymentRequest().getPaymentDetailsVersion();
|
||||
StringBuilder output = new StringBuilder(
|
||||
format("Bitcoin payment request, version %d%nDate: %s%n", version, session.time()));
|
||||
PaymentProtocol.PkiVerificationData pki = PaymentProtocol.verifyPaymentRequestPki(
|
||||
session.getPaymentRequest(), new TrustStoreLoader.DefaultTrustStoreLoader().getKeyStore());
|
||||
if (pki != null) {
|
||||
output.append(format("Signed by: %s%nIdentity verified by: %s%n", pki.displayName, pki.rootAuthorityName));
|
||||
}
|
||||
if (session.getPaymentDetails().hasExpires()) {
|
||||
output.append(format("Expires: %s%n", TimeUtils.dateTimeFormat(Instant.ofEpochSecond(session.getPaymentDetails().getExpires()))));
|
||||
}
|
||||
if (session.getMemo() != null) {
|
||||
output.append(format("Memo: %s%n", session.getMemo()));
|
||||
}
|
||||
output.append(format("%n%n%s%n%s", session.getPaymentRequest(), session.getPaymentDetails()));
|
||||
System.out.println(output);
|
||||
} catch (URISyntaxException | BitcoinURIParseException e) {
|
||||
System.err.println("Could not parse URI: " + e.getMessage());
|
||||
} catch (PaymentProtocolException.PkiVerificationException e) {
|
||||
System.err.println(e.getMessage());
|
||||
if (e.certificates != null) {
|
||||
for (X509Certificate certificate : e.certificates) {
|
||||
System.err.println(" " + certificate);
|
||||
}
|
||||
}
|
||||
} catch (PaymentProtocolException e) {
|
||||
System.err.println("Could not handle payment request: " + e.getMessage());
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("Interrupted whilst processing/downloading.");
|
||||
} catch (ExecutionException e) {
|
||||
System.err.println("Failed whilst retrieving payment URL: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
} catch (FileNotFoundException e) {
|
||||
System.err.println(e.getMessage());
|
||||
} catch (IOException | KeyStoreException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -50,9 +50,6 @@ import org.bitcoinj.crypto.KeyCrypterException;
|
|||
import org.bitcoinj.crypto.MnemonicCode;
|
||||
import org.bitcoinj.crypto.MnemonicException;
|
||||
import org.bitcoinj.protobuf.wallet.Protos;
|
||||
import org.bitcoinj.protocols.payments.PaymentProtocol;
|
||||
import org.bitcoinj.protocols.payments.PaymentProtocolException;
|
||||
import org.bitcoinj.protocols.payments.PaymentSession;
|
||||
import org.bitcoinj.script.ScriptException;
|
||||
import org.bitcoinj.store.BlockStore;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
|
@ -138,10 +135,6 @@ public class WalletTool implements Callable<Integer> {
|
|||
" The output destination can also be a native segwit address.%n" +
|
||||
" If the output destination starts with 04 and is 65 or 33 bytes long it will be treated as a public key instead of an address and the send will use%n" +
|
||||
" <key> CHECKSIG as the script.%n" +
|
||||
" If --payment-request is specified, a transaction will be created using the provided payment request. A payment request can be a local file, a bitcoin uri, or url to download the payment request, e.g.:%n" +
|
||||
" --payment-request=/path/to/my.bitcoinpaymentrequest%n" +
|
||||
" --payment-request=bitcoin:?r=https://merchant.example.com/pay?123%n" +
|
||||
" --payment-request=https://merchant.example.com/pay?123%n" +
|
||||
" Other options include:%n" +
|
||||
" --fee-per-vkb or --fee-sat-per-vbyte sets the network fee, see below%n" +
|
||||
" --select-addr or --select-output to select specific outputs%n" +
|
||||
|
@ -221,8 +214,6 @@ public class WalletTool implements Callable<Integer> {
|
|||
private boolean ignoreMandatoryExtensions = false;
|
||||
@CommandLine.Option(names = "--password", description = "For an encrypted wallet, the password is provided here.")
|
||||
private String password = null;
|
||||
@CommandLine.Option(names = "--payment-request", description = "Specifies a payment request either by name of a local file, a bitcoin uri, or url to download the payment request.")
|
||||
private String paymentRequestLocationStr;
|
||||
@CommandLine.Option(names = "--no-pki", description = "Disables pki verification for payment requests.")
|
||||
private boolean noPki = false;
|
||||
@CommandLine.Option(names = "--dump-privkeys", description = "Private keys and seed are printed.")
|
||||
|
@ -239,7 +230,6 @@ public class WalletTool implements Callable<Integer> {
|
|||
private static AbstractBlockChain chain;
|
||||
private static PeerGroup peerGroup;
|
||||
private static Wallet wallet;
|
||||
private static org.bitcoinj.protobuf.payments.Protos.PaymentRequest paymentRequest;
|
||||
|
||||
public static class Condition {
|
||||
public enum Type {
|
||||
|
@ -408,10 +398,7 @@ public class WalletTool implements Callable<Integer> {
|
|||
case RESET: reset(); break;
|
||||
case SYNC: syncChain(); break;
|
||||
case SEND:
|
||||
if (paymentRequestLocationStr != null && outputsStr != null) {
|
||||
System.err.println("--payment-request and --output cannot be used together.");
|
||||
return 1;
|
||||
} else if (feePerVkbStr != null && feeSatPerVbyteStr != null) {
|
||||
if (feePerVkbStr != null && feeSatPerVbyteStr != null) {
|
||||
System.err.println("--fee-per-kb and --fee-sat-per-byte cannot be used together.");
|
||||
return 1;
|
||||
} else if (outputsStr != null) {
|
||||
|
@ -454,10 +441,8 @@ public class WalletTool implements Callable<Integer> {
|
|||
coinSelector = null;
|
||||
}
|
||||
send(coinSelector, outputsStr, feePerVkb, lockTimeStr, allowUnconfirmed);
|
||||
} else if (paymentRequestLocationStr != null) {
|
||||
sendPaymentRequest(paymentRequestLocationStr, !noPki);
|
||||
} else {
|
||||
System.err.println("You must specify a --payment-request or at least one --output=addr:value.");
|
||||
System.err.println("You must specify at least one --output=addr:value.");
|
||||
return 1;
|
||||
}
|
||||
break;
|
||||
|
@ -764,140 +749,6 @@ public class WalletTool implements Callable<Integer> {
|
|||
return Long.parseLong(lockTimeStr);
|
||||
}
|
||||
|
||||
private void sendPaymentRequest(String location, boolean verifyPki) {
|
||||
if (location.startsWith("http") || location.startsWith("bitcoin")) {
|
||||
try {
|
||||
CompletableFuture<PaymentSession> future;
|
||||
if (location.startsWith("http")) {
|
||||
future = PaymentSession.createFromUrl(location, verifyPki);
|
||||
} else {
|
||||
BitcoinURI paymentRequestURI = BitcoinURI.of(location);
|
||||
future = PaymentSession.createFromBitcoinUri(paymentRequestURI, verifyPki);
|
||||
}
|
||||
PaymentSession session = future.get();
|
||||
if (session != null) {
|
||||
send(session);
|
||||
} else {
|
||||
System.err.println("Server returned null session");
|
||||
System.exit(1);
|
||||
}
|
||||
} catch (PaymentProtocolException e) {
|
||||
System.err.println("Error creating payment session " + e.getMessage());
|
||||
System.exit(1);
|
||||
} catch (BitcoinURIParseException e) {
|
||||
System.err.println("Invalid bitcoin uri: " + e.getMessage());
|
||||
System.exit(1);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore.
|
||||
} catch (ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
} else {
|
||||
// Try to open the payment request as a file.
|
||||
FileInputStream stream = null;
|
||||
try {
|
||||
File paymentRequestFile = new File(location);
|
||||
stream = new FileInputStream(paymentRequestFile);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to open file: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
try {
|
||||
paymentRequest = org.bitcoinj.protobuf.payments.Protos.PaymentRequest.newBuilder().mergeFrom(stream).build();
|
||||
} catch(IOException e) {
|
||||
System.err.println("Failed to parse payment request from file " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
PaymentSession session = null;
|
||||
try {
|
||||
session = new PaymentSession(paymentRequest, verifyPki);
|
||||
} catch (PaymentProtocolException e) {
|
||||
System.err.println("Error creating payment session " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
send(session);
|
||||
}
|
||||
}
|
||||
|
||||
private void send(PaymentSession session) {
|
||||
System.out.println("Payment Request");
|
||||
System.out.println("Coin: " + session.getValue().toFriendlyString());
|
||||
System.out.println("Date: " + session.time());
|
||||
System.out.println("Memo: " + session.getMemo());
|
||||
if (session.pkiVerificationData != null) {
|
||||
System.out.println("Pki-Verified Name: " + session.pkiVerificationData.displayName);
|
||||
System.out.println("PKI data verified by: " + session.pkiVerificationData.rootAuthorityName);
|
||||
}
|
||||
final SendRequest req = session.getSendRequest();
|
||||
if (password != null) {
|
||||
req.aesKey = passwordToKey(true);
|
||||
if (req.aesKey == null)
|
||||
return; // Error message already printed.
|
||||
}
|
||||
|
||||
// Complete the transaction
|
||||
try {
|
||||
wallet.completeTx(req); // may throw InsufficientMoneyException.
|
||||
} catch (InsufficientMoneyException e) {
|
||||
System.err.println("Insufficient funds: have " + wallet.getBalance().toFriendlyString());
|
||||
System.exit(1);
|
||||
}
|
||||
if (offline) {
|
||||
wallet.commitTx(req.tx);
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup network communication (but not PeerGroup)
|
||||
try {
|
||||
setup();
|
||||
} catch (BlockStoreException e) {
|
||||
System.err.println("BlockStoreException: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Send the payment
|
||||
try {
|
||||
// No refund address specified, no user-specified memo field.
|
||||
PaymentProtocol.Ack ack = session.sendPayment(Collections.singletonList(req.tx), null, null).get();
|
||||
wallet.commitTx(req.tx);
|
||||
System.out.println("Memo from server: " + ack.getMemo());
|
||||
} catch (ExecutionException e) {
|
||||
try {
|
||||
throw e.getCause();
|
||||
} catch (PaymentProtocolException.InvalidPaymentRequestURL e1) {
|
||||
System.out.println("Missing/Invalid Payment Request URL, broadcasting transaction with PeerGroup");
|
||||
broadcastPayment(req);
|
||||
} catch (PaymentProtocolException e1) {
|
||||
System.err.println("Failed to send payment " + e.getMessage());
|
||||
System.exit(1);
|
||||
} catch (IOException e1) {
|
||||
System.err.println("Invalid payment " + e.getMessage());
|
||||
System.exit(1);
|
||||
} catch (Throwable t) {
|
||||
System.err.println("Unexpected error " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
} catch (VerificationException e) {
|
||||
System.err.println("Failed to send payment " + e.getMessage());
|
||||
System.exit(1);
|
||||
} catch (InterruptedException e) {
|
||||
System.err.println("Interrupted: " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private void broadcastPayment(SendRequest req) {
|
||||
peerGroup.start();
|
||||
TransactionBroadcast broadcast = peerGroup.broadcastTransaction(req.tx);
|
||||
try {
|
||||
// Wait for broadcast to be sent
|
||||
broadcast.awaitRelayed().get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
System.err.println("Failed to broadcast payment " + e.getMessage());
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a condition to be satisfied
|
||||
* @param waitFor condition type to wait for
|
||||
|
|
Loading…
Add table
Reference in a new issue