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:
Andreas Schildbach 2025-02-27 22:22:23 +01:00
parent aa9eaf9af7
commit d97d33de41
25 changed files with 4 additions and 2311 deletions

View file

@ -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));
}
}
}

View file

@ -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.
}
}
}
}

View file

@ -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;

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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));
}
}

View file

@ -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());
}
}

View file

@ -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;
}
}
}
}

View file

@ -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());
}
}

View file

@ -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-----

View file

@ -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-----

View file

@ -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-----

View file

@ -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

View file

@ -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-----

View file

@ -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();
}
}
}

View file

@ -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