mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-11 17:55:53 +00:00
Add Dogecoin payment protocol support
Add Dogecoin payment protocol support, and remove unused proto definition files.
This commit is contained in:
parent
049fa77002
commit
39fe02389b
@ -0,0 +1,423 @@
|
||||
/**
|
||||
* 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 com.dogecoin.dogecoinj.protocols.payments;
|
||||
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.crypto.X509Utils;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import org.bitcoin.protocols.payments.Protos;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.Serializable;
|
||||
import java.security.*;
|
||||
import java.security.cert.*;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.ArrayList;
|
||||
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 DIP71.
|
||||
public static final String MIMETYPE_PAYMENTREQUEST = "application/vnd.doge.payment.request";
|
||||
public static final String MIMETYPE_PAYMENT = "application/vnd.doge.payment.payment";
|
||||
public static final String MIMETYPE_PAYMENTACK = "application/vnd.doge.payment.ack";
|
||||
|
||||
/**
|
||||
* 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, ImmutableList.of(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(params.getPaymentProtocolId());
|
||||
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(Utils.currentTimeSeconds());
|
||||
|
||||
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 (privateKey.getAlgorithm().equalsIgnoreCase("RSA"))
|
||||
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.
|
||||
*/
|
||||
public static @Nullable PkiVerificationData verifyPaymentRequestPki(Protos.PaymentRequest paymentRequest, KeyStore trustStore)
|
||||
throws PaymentProtocolException {
|
||||
List<X509Certificate> certs = null;
|
||||
try {
|
||||
final String pkiType = paymentRequest.getPkiType();
|
||||
if (pkiType.equals("none"))
|
||||
// Nothing to verify. Everything is fine. Move along.
|
||||
return null;
|
||||
|
||||
String algorithm;
|
||||
if (pkiType.equals("x509+sha256"))
|
||||
algorithm = "SHA256withRSA";
|
||||
else if (pkiType.equals("x509+sha1"))
|
||||
algorithm = "SHA1withRSA";
|
||||
else
|
||||
throw new PaymentProtocolException.InvalidPkiType("Unsupported PKI type: " + pkiType);
|
||||
|
||||
Protos.X509Certificates protoCerts = Protos.X509Certificates.parseFrom(paymentRequest.getPkiData());
|
||||
if (protoCerts.getCertificateCount() == 0)
|
||||
throw new PaymentProtocolException.InvalidPkiData("No certificates provided in message: server config error");
|
||||
|
||||
// Parse the certs and turn into a certificate chain object. Cert factories can parse both DER and base64.
|
||||
// The ordering of certificates is defined by the payment protocol spec to be the same as what the Java
|
||||
// crypto API requires - convenient!
|
||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
||||
certs = Lists.newArrayList();
|
||||
for (ByteString bytes : protoCerts.getCertificateList())
|
||||
certs.add((X509Certificate) certificateFactory.generateCertificate(bytes.newInput()));
|
||||
CertPath path = certificateFactory.generateCertPath(certs);
|
||||
|
||||
// Retrieves the most-trusted CAs from keystore.
|
||||
PKIXParameters params = new PKIXParameters(trustStore);
|
||||
// Revocation not supported in the current version.
|
||||
params.setRevocationEnabled(false);
|
||||
|
||||
// Now verify the certificate chain is correct and trusted. This let's us get an identity linked pubkey.
|
||||
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
|
||||
PKIXCertPathValidatorResult result = (PKIXCertPathValidatorResult) validator.validate(path, params);
|
||||
PublicKey publicKey = result.getPublicKey();
|
||||
// OK, we got an identity, now check it was used to sign this message.
|
||||
Signature signature = Signature.getInstance(algorithm);
|
||||
// Note that we don't use signature.initVerify(certs.get(0)) here despite it being the most obvious
|
||||
// way to set it up, because we don't care about the constraints specified on the certificates: any
|
||||
// cert that links a key to a domain name or other identity will do for us.
|
||||
signature.initVerify(publicKey);
|
||||
Protos.PaymentRequest.Builder reqToCheck = paymentRequest.toBuilder();
|
||||
reqToCheck.setSignature(ByteString.EMPTY);
|
||||
signature.update(reqToCheck.build().toByteArray());
|
||||
if (!signature.verify(paymentRequest.getSignature().toByteArray()))
|
||||
throw new PaymentProtocolException.PkiVerificationException("Invalid signature, this payment request is not valid.");
|
||||
|
||||
// Signature verifies, get the names from the identity we just verified for presentation to the user.
|
||||
final X509Certificate cert = certs.get(0);
|
||||
String displayName = X509Utils.getDisplayNameFromCertificate(cert, true);
|
||||
if (displayName == null)
|
||||
throw new PaymentProtocolException.PkiVerificationException("Could not extract name from certificate");
|
||||
// Everything is peachy. Return some useful data to the caller.
|
||||
return new PkiVerificationData(displayName, publicKey, result.getTrustAnchor());
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
// Data structures are malformed.
|
||||
throw new PaymentProtocolException.InvalidPkiData(e);
|
||||
} catch (CertificateException e) {
|
||||
// The X.509 certificate data didn't parse correctly.
|
||||
throw new PaymentProtocolException.PkiVerificationException(e);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Should never happen so don't make users have to think about it. PKIX is always present.
|
||||
throw new RuntimeException(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (CertPathValidatorException e) {
|
||||
// The certificate chain isn't known or trusted, probably, the server is using an SSL root we don't
|
||||
// know about and the user needs to upgrade to a new version of the software (or import a root cert).
|
||||
throw new PaymentProtocolException.PkiVerificationException(e, certs);
|
||||
} catch (InvalidKeyException e) {
|
||||
// Shouldn't happen if the certs verified correctly.
|
||||
throw new PaymentProtocolException.PkiVerificationException(e);
|
||||
} catch (SignatureException e) {
|
||||
// Something went wrong during hashing (yes, despite the name, this does not mean the sig was invalid).
|
||||
throw new PaymentProtocolException.PkiVerificationException(e);
|
||||
} catch (KeyStoreException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Objects.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,
|
||||
ImmutableList.of(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) {
|
||||
transaction.verify();
|
||||
builder.addTransactions(ByteString.copyFrom(transaction.unsafeBitcoinSerialize()));
|
||||
}
|
||||
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<Transaction>(paymentMessage.getTransactionsCount());
|
||||
for (final ByteString transaction : paymentMessage.getTransactionsList())
|
||||
transactions.add(new Transaction(params, 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) {
|
||||
if (amount.compareTo(NetworkParameters.MAX_MONEY) > 0)
|
||||
throw new IllegalArgumentException("Amount too big: " + amount);
|
||||
output.setAmount(amount.value);
|
||||
} else {
|
||||
output.setAmount(0);
|
||||
}
|
||||
output.setScript(ByteString.copyFrom(ScriptBuilder.createOutputScript(address).getProgram()));
|
||||
return output.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to hold amount/script pairs.
|
||||
*/
|
||||
public static class Output implements Serializable {
|
||||
public final @Nullable Coin amount;
|
||||
public final byte[] scriptData;
|
||||
|
||||
public Output(@Nullable Coin amount, byte[] scriptData) {
|
||||
this.amount = amount;
|
||||
this.scriptData = scriptData;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 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 com.dogecoin.dogecoinj.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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,429 @@
|
||||
/**
|
||||
* 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 com.dogecoin.dogecoinj.protocols.payments;
|
||||
|
||||
import com.dogecoin.dogecoinj.protocols.payments.PaymentProtocol.PkiVerificationData;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.crypto.TrustStoreLoader;
|
||||
import org.bitcoinj.params.MainNetParams;
|
||||
import org.bitcoinj.uri.BitcoinURI;
|
||||
import org.bitcoinj.utils.Threading;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListeningExecutorService;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.bitcoin.protocols.payments.Protos;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import java.security.KeyStoreException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* <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
|
||||
* ListenableFuture 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 ListenableFuture 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 static ListeningExecutorService executor = Threading.THREAD_POOL;
|
||||
private NetworkParameters params;
|
||||
private final TrustStoreLoader trustStoreLoader;
|
||||
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 ListenableFuture<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 ListenableFuture<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 ListenableFuture<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 ListenableFuture<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 ListenableFuture<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 ListenableFuture<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 ListenableFuture<PaymentSession> fetchPaymentRequest(final URI uri, final boolean verifyPki, @Nullable final TrustStoreLoader trustStoreLoader) {
|
||||
return executor.submit(new Callable<PaymentSession>() {
|
||||
@Override
|
||||
public PaymentSession call() throws Exception {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
this.trustStoreLoader = trustStoreLoader != null ? trustStoreLoader : new TrustStoreLoader.DefaultTrustStoreLoader();
|
||||
parsePaymentRequest(request);
|
||||
if (verifyPki) {
|
||||
try {
|
||||
pkiVerificationData = PaymentProtocol.verifyPaymentRequestPki(request, this.trustStoreLoader.getKeyStore());
|
||||
} catch (IOException x) {
|
||||
throw new PaymentProtocolException(x);
|
||||
} catch (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<PaymentProtocol.Output>(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 date that the payment request was generated.
|
||||
*/
|
||||
public Date getDate() {
|
||||
return new Date(paymentDetails.getTime() * 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expires time of the payment request, or null if none.
|
||||
*/
|
||||
@Nullable public Date getExpires() {
|
||||
if (paymentDetails.hasExpires())
|
||||
return new Date(paymentDetails.getExpires() * 1000);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This should always be called before attempting to call sendPayment.
|
||||
*/
|
||||
public boolean isExpired() {
|
||||
return paymentDetails.hasExpires() && System.currentTimeMillis() / 1000L > paymentDetails.getExpires();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the payment url where the Payment message should be sent.
|
||||
* Returns null if no payment url was provided in the PaymentRequest.
|
||||
*/
|
||||
public @Nullable 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 Wallet.SendRequest} suitable for broadcasting to the network.
|
||||
*/
|
||||
public Wallet.SendRequest getSendRequest() {
|
||||
Transaction tx = new Transaction(params);
|
||||
for (Protos.Output output : paymentDetails.getOutputsList())
|
||||
tx.addOutput(new TransactionOutput(params, tx, Coin.valueOf(output.getAmount()), output.getScript().toByteArray()));
|
||||
return Wallet.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, returns null and does nothing.
|
||||
* @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.
|
||||
*/
|
||||
public @Nullable ListenableFuture<PaymentProtocol.Ack> sendPayment(List<Transaction> txns, @Nullable Address refundAddr, @Nullable String memo)
|
||||
throws PaymentProtocolException, VerificationException, IOException {
|
||||
Protos.Payment payment = getPayment(txns, refundAddr, memo);
|
||||
if (payment == null)
|
||||
return null;
|
||||
if (isExpired())
|
||||
throw new PaymentProtocolException.Expired("PaymentRequest is expired");
|
||||
URL url;
|
||||
try {
|
||||
url = new URL(paymentDetails.getPaymentUrl());
|
||||
} catch (MalformedURLException e) {
|
||||
throw 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.
|
||||
* If the PaymentRequest did not specify a payment_url, returns null.
|
||||
* @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.
|
||||
*/
|
||||
public @Nullable Protos.Payment getPayment(List<Transaction> txns, @Nullable Address refundAddr, @Nullable String memo)
|
||||
throws IOException, PaymentProtocolException.InvalidNetwork {
|
||||
if (paymentDetails.hasPaymentUrl()) {
|
||||
for (Transaction tx : txns)
|
||||
if (!tx.getParams().equals(params))
|
||||
throw new PaymentProtocolException.InvalidNetwork(params.getPaymentProtocolId());
|
||||
return PaymentProtocol.createPaymentMessage(txns, totalValue, refundAddr, memo, getMerchantData());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
protected ListenableFuture<PaymentProtocol.Ack> sendPayment(final URL url, final Protos.Payment payment) {
|
||||
return executor.submit(new Callable<PaymentProtocol.Ack>() {
|
||||
@Override
|
||||
public PaymentProtocol.Ack call() throws Exception {
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 = NetworkParameters.fromPmtProtocolID(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 (totalValue.compareTo(NetworkParameters.MAX_MONEY) > 0)
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
/**
|
||||
* The BIP70 payment protocol wraps Bitcoin transactions and adds various useful features like memos, refund addresses
|
||||
* and authentication.
|
||||
*/
|
||||
package com.dogecoin.dogecoinj.protocols.payments;
|
Loading…
x
Reference in New Issue
Block a user