diff --git a/core/pom.xml b/core/pom.xml index 346de95d..342ce26a 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -164,6 +164,12 @@ jcip-annotations 1.0 + + + com.lambdaworks + scrypt + 1.3.3 + diff --git a/core/src/bitcoin.proto b/core/src/bitcoin.proto index b855c940..c0afed23 100644 --- a/core/src/bitcoin.proto +++ b/core/src/bitcoin.proto @@ -1,4 +1,4 @@ -/** Copyright 2012 Google Inc. +/** 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. @@ -34,7 +34,16 @@ message PeerAddress { } /** - * A key use to control Bitcoin spending + * The data to store a private key encrypted with Scrypt and AES + * + */ +message EncryptedPrivateKey { + required bytes initialisation_vector = 1; // The initialisation vector for the AES encryption (16 bytes) + required bytes encrypted_private_key = 2; // The encrypted private key +} + +/** + * A key used to control Bitcoin spending. * * Either the private key, the public key or both may be present. It is recommended that * if the private key is provided that the public key is provided too because deriving it is slow. @@ -44,12 +53,19 @@ message PeerAddress { */ message Key { enum Type { - ORIGINAL = 1; // Original bitcoin secp256k1 curve + ORIGINAL = 1; // Unencrypted - Original bitcoin secp256k1 curve + ENCRYPTED_SCRYPT_AES = 2; // Encrypted with Scrypt and AES - - Original bitcoin secp256k1 curve } required Type type = 1; // The private EC key bytes without any ASN.1 wrapping. optional bytes private_key = 2; + + // The message containing the encrypted private EC key information. + // When an EncryptedPrivateKey is present then the (unencrypted) private_key will be a zero length byte array or contain all zeroes. + // This is for security of the private key information. + optional EncryptedPrivateKey encrypted_private_key = 6; + // The public EC key derived from the private key. We allow both to be stored to avoid mobile clients having to // do lots of slow EC math on startup. optional bytes public_key = 3; @@ -176,6 +192,19 @@ message Transaction { optional TransactionConfidence confidence = 9; } +/** The parameters used in the scrypt key derivation function. + * The default values are taken from http://www.tarsnap.com/scrypt/scrypt-slides.pdf. + * They can be increased - n is the number of iterations performed and + * r and p can be used to tweak the algorithm - see: + * http://stackoverflow.com/questions/11126315/what-are-optimal-scrypt-work-factors + */ +message ScryptParameters { + required bytes salt = 1; // Salt to use in generation of the wallet password (8 bytes) + optional int64 n = 2 [default = 16384]; // CPU/ memory cost parameter + optional int32 r = 3 [default = 8]; // Block size parameter + optional int32 p = 4 [default = 1]; // Parallelisation parameter +} + /** An extension to the wallet */ message Extension { required string id = 1; // like org.whatever.foo.bar @@ -188,6 +217,18 @@ message Extension { /** A bitcoin wallet */ message Wallet { + /** + * The encryption type of the wallet. + * + * The encryption type is UNENCRYPTED for wallets where the wallet does not support encryption - wallets prior to + * encryption support are grandfathered in as this wallet type. + * When a wallet is ENCRYPTED_SCRYPT_AES the keys are either encrypted with the wallet password or are unencrypted. + */ + enum EncryptionType { + UNENCRYPTED = 1; // All keys in the wallet are unencrypted + ENCRYPTED_SCRYPT_AES = 2; // All keys are encrypted with a passphrase based KDF of scrypt and AES encryption + } + required string network_identifier = 1; // the network used by this wallet // org.bitcoin.production = production network (Satoshi genesis block) // org.bitcoin.test = test network (Andresen genesis block) @@ -195,9 +236,25 @@ message Wallet { // The SHA256 hash of the head of the best chain seen by this wallet. optional bytes last_seen_block_hash = 2; // The height in the chain of the last seen block. - optional uint32 last_seen_block_height = 5; + optional uint32 last_seen_block_height = 12; repeated Key key = 3; repeated Transaction transaction = 4; + + optional EncryptionType encryption_type = 5 [default=UNENCRYPTED]; + optional ScryptParameters encryption_parameters = 6; + + // The version number of the wallet - used to detect wallets that were produced in the future + // (i.e the wallet may contain some future format this protobuf/ code does not know about) + optional int32 version = 7; + + // deprecated - do not recycle this numeric identifier + // optional int32 minor_version = 8; + repeated Extension extension = 10; + + // A UTF8 encoded text description of the wallet that is intended for end user provided text. + optional string description = 11; + + // (The field number 12 is used by last_seen_block_height) } // end of Wallet diff --git a/core/src/main/java/com/google/bitcoin/core/AbstractBlockChain.java b/core/src/main/java/com/google/bitcoin/core/AbstractBlockChain.java index 19caf51e..84697f82 100644 --- a/core/src/main/java/com/google/bitcoin/core/AbstractBlockChain.java +++ b/core/src/main/java/com/google/bitcoin/core/AbstractBlockChain.java @@ -648,8 +648,8 @@ public abstract class AbstractBlockChain { } } } - - private void setChainHead(StoredBlock chainHead) throws BlockStoreException { + + protected void setChainHead(StoredBlock chainHead) throws BlockStoreException { doSetChainHead(chainHead); synchronized (chainHeadLock) { this.chainHead = chainHead; diff --git a/core/src/main/java/com/google/bitcoin/core/ECKey.java b/core/src/main/java/com/google/bitcoin/core/ECKey.java index 5ac67f66..b5d2b986 100644 --- a/core/src/main/java/com/google/bitcoin/core/ECKey.java +++ b/core/src/main/java/com/google/bitcoin/core/ECKey.java @@ -16,6 +16,8 @@ package com.google.bitcoin.core; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; import org.spongycastle.asn1.*; import org.spongycastle.asn1.sec.SECNamedCurves; @@ -26,12 +28,17 @@ import org.spongycastle.crypto.params.ECDomainParameters; import org.spongycastle.crypto.params.ECKeyGenerationParameters; import org.spongycastle.crypto.params.ECPrivateKeyParameters; import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.crypto.params.KeyParameter; import org.spongycastle.crypto.signers.ECDSASigner; import org.spongycastle.math.ec.ECCurve; import org.spongycastle.math.ec.ECFieldElement; import org.spongycastle.math.ec.ECPoint; import org.spongycastle.util.encoders.Base64; +import com.google.bitcoin.crypto.EncryptedPrivateKey; +import com.google.bitcoin.crypto.KeyCrypter; +import com.google.bitcoin.crypto.KeyCrypterException; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.Serializable; @@ -61,6 +68,8 @@ import static com.google.common.base.Preconditions.checkArgument; * signature and want to find out who signed it, rather than requiring the user to provide the expected identity.

*/ public class ECKey implements Serializable { + private static final Logger log = LoggerFactory.getLogger(ECKey.class); + private static final ECDomainParameters ecParams; private static final SecureRandom secureRandom; @@ -82,6 +91,16 @@ public class ECKey implements Serializable { // not have this field. private long creationTimeSeconds; + /** + * Instance of the KeyCrypter interface to use for encrypting and decrypting the key. + */ + transient private KeyCrypter keyCrypter; + + /** + * The encrypted private key information. + */ + private EncryptedPrivateKey encryptedPrivateKey; + // Transient because it's calculated on demand. transient private byte[] pubKeyHash; @@ -118,30 +137,37 @@ public class ECKey implements Serializable { return new ECKey(extractPrivateKeyFromASN1(asn1privkey)); } - /** - * Output this ECKey as an ASN.1 encoded private key, as understood by OpenSSL or used by the Bitcoin reference - * implementation in its wallet storage format. - */ - public byte[] toASN1() { - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(400); + /** Creates an ECKey given the private key only. The public key is calculated from it (this is slow) */ + public ECKey(BigInteger privKey) { + this(privKey, (byte[])null); + } - // ASN1_SEQUENCE(EC_PRIVATEKEY) = { - // ASN1_SIMPLE(EC_PRIVATEKEY, version, LONG), - // ASN1_SIMPLE(EC_PRIVATEKEY, privateKey, ASN1_OCTET_STRING), - // ASN1_EXP_OPT(EC_PRIVATEKEY, parameters, ECPKPARAMETERS, 0), - // ASN1_EXP_OPT(EC_PRIVATEKEY, publicKey, ASN1_BIT_STRING, 1) - // } ASN1_SEQUENCE_END(EC_PRIVATEKEY) - DERSequenceGenerator seq = new DERSequenceGenerator(baos); - seq.addObject(new ASN1Integer(1)); // version - seq.addObject(new DEROctetString(priv.toByteArray())); - seq.addObject(new DERTaggedObject(0, SECNamedCurves.getByName("secp256k1").toASN1Primitive())); - seq.addObject(new DERTaggedObject(1, new DERBitString(getPubKey()))); - seq.close(); - return baos.toByteArray(); - } catch (IOException e) { - throw new RuntimeException(e); // Cannot happen, writing to memory stream. - } + /** A constructor variant with BigInteger pubkey. See {@link ECKey#ECKey(BigInteger, byte[])}. */ + public ECKey(BigInteger privKey, BigInteger pubKey) { + this(privKey, Utils.bigIntegerToBytes(pubKey, 65)); + } + + /** + * Creates an ECKey given only the private key bytes. This is the same as using the BigInteger constructor, but + * is more convenient if you are importing a key from elsewhere. The public key will be automatically derived + * from the private key. + */ + public ECKey(byte[] privKeyBytes, byte[] pubKey) { + this(privKeyBytes == null ? null : new BigInteger(1, privKeyBytes), pubKey); + } + + /** + * Create a new ECKey with an encrypted private key, a public key and a KeyCrypter. + * + * @param encryptedPrivateKey The private key, encrypted, + * @param pubKey The keys public key + * @param keyCrypter The KeyCrypter that will be used, with an AES key, to encrypt and decrypt the private key + */ + public ECKey(EncryptedPrivateKey encryptedPrivateKey, byte[] pubKey, KeyCrypter keyCrypter) { + this((byte[])null, pubKey); + + this.keyCrypter = Preconditions.checkNotNull(keyCrypter); + this.encryptedPrivateKey = encryptedPrivateKey; } /** @@ -174,24 +200,31 @@ public class ECKey implements Serializable { private ECKey(BigInteger privKey, byte[] pubKey) { this(privKey, pubKey, false); } - - /** Creates an ECKey given the private key only. The public key is calculated from it (this is slow) */ - public ECKey(BigInteger privKey) { - this(privKey, (byte[])null); - } - - /** A constructor variant with BigInteger pubkey. See {@link ECKey#ECKey(BigInteger, byte[])}. */ - public ECKey(BigInteger privKey, BigInteger pubKey) { - this(privKey, Utils.bigIntegerToBytes(pubKey, 65)); - } /** - * Creates an ECKey given only the private key bytes. This is the same as using the BigInteger constructor, but - * is more convenient if you are importing a key from elsewhere. If not provided the public key will be - * automatically derived from the private key. + * Output this ECKey as an ASN.1 encoded private key, as understood by OpenSSL or used by the BitCoin reference + * implementation in its wallet storage format. */ - public ECKey(byte[] privKeyBytes, byte[] pubKey) { - this(privKeyBytes == null ? null : new BigInteger(1, privKeyBytes), pubKey); + public byte[] toASN1() { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(400); + + // ASN1_SEQUENCE(EC_PRIVATEKEY) = { + // ASN1_SIMPLE(EC_PRIVATEKEY, version, LONG), + // ASN1_SIMPLE(EC_PRIVATEKEY, privateKey, ASN1_OCTET_STRING), + // ASN1_EXP_OPT(EC_PRIVATEKEY, parameters, ECPKPARAMETERS, 0), + // ASN1_EXP_OPT(EC_PRIVATEKEY, publicKey, ASN1_BIT_STRING, 1) + // } ASN1_SEQUENCE_END(EC_PRIVATEKEY) + DERSequenceGenerator seq = new DERSequenceGenerator(baos); + seq.addObject(new ASN1Integer(1)); // version + seq.addObject(new DEROctetString(priv.toByteArray())); + seq.addObject(new DERTaggedObject(0, SECNamedCurves.getByName("secp256k1").toASN1Primitive())); + seq.addObject(new DERTaggedObject(1, new DERBitString(getPubKey()))); + seq.close(); + return baos.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); // Cannot happen, writing to memory stream. + } } /** @@ -236,6 +269,10 @@ public class ECKey implements Serializable { return b.toString(); } + /** + * Produce a string rendering of the ECKey INCLUDING the private key. + * Unless you absolutely need the private key it is better for security reasons to just use toString(). + */ public String toStringWithPrivate() { StringBuilder b = new StringBuilder(); b.append(toString()); @@ -254,6 +291,19 @@ public class ECKey implements Serializable { return new Address(params, hash160); } + /** + * Clears all the ECKey private key contents from memory. + * WARNING - this method irreversibly deletes the private key information. + * It turns the ECKEy into a watch only key. + */ + public void clearPrivateKey() { + priv = BigInteger.ZERO; + + if (encryptedPrivateKey != null) { + encryptedPrivateKey.clear(); + } + } + /** * Groups the two components that make up a signature, and provides a way to encode to DER form, which is * how ECDSA signatures are represented when embedded in other data structures in the Bitcoin protocol. The raw @@ -291,13 +341,45 @@ public class ECKey implements Serializable { * Signs the given hash and returns the R and S components as BigIntegers. In the Bitcoin protocol, they are * usually encoded using DER format, so you want {@link ECKey#signToDER(Sha256Hash)} instead. However sometimes * the independent components can be useful, for instance, if you're doing to do further EC maths on them. - * @throws IllegalStateException if this ECKey doesn't have a private part. + * @throws KeyCrypterException if this ECKey doesn't have a private part. */ - public ECDSASignature sign(Sha256Hash input) { - if (priv == null) - throw new IllegalStateException("This ECKey does not have the private key necessary for signing."); + public ECDSASignature sign(Sha256Hash input) throws KeyCrypterException { + return sign(input, null); + } + + /** + * Signs the given hash and returns the R and S components as BigIntegers. In the Bitcoin protocol, they are + * usually encoded using DER format, so you want {@link ECKey#signToDER(Sha256Hash)} instead. However sometimes + * the independent components can be useful, for instance, if you're doing to do further EC maths on them. + * @param aesKey The AES key to use for decryption of the private key. If null then no decryption is required. + * @throws KeyCrypterException if this ECKey doesn't have a private part. + */ + public ECDSASignature sign(Sha256Hash input, KeyParameter aesKey) throws KeyCrypterException { + // The private key bytes to use for signing. + BigInteger privateKeyForSigning; + + if (isEncrypted()) { + // The private key needs decrypting before use. + if (aesKey == null) { + throw new KeyCrypterException("This ECKey is encrypted but no decryption key has been supplied."); + } + + if (keyCrypter == null) { + throw new KeyCrypterException("There is no KeyCrypter to decrypt the private key for signing."); + } + + privateKeyForSigning = new BigInteger(1, keyCrypter.decrypt(encryptedPrivateKey, aesKey)); + } else { + // No decryption of private key required. + if (priv == null) { + throw new KeyCrypterException("This ECKey does not have the private key necessary for signing."); + } else { + privateKeyForSigning = priv; + } + } + ECDSASigner signer = new ECDSASigner(); - ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(priv, ecParams); + ECPrivateKeyParameters privKey = new ECPrivateKeyParameters(privateKeyForSigning, ecParams); signer.init(true, privKey); BigInteger[] sigs = signer.generateSignature(input.getBytes()); return new ECDSASignature(sigs[0], sigs[1]); @@ -328,7 +410,7 @@ public class ECKey implements Serializable { } catch (NullPointerException e) { // Bouncy Castle contains a bug that can cause NPEs given specially crafted signatures. Those signatures // are inherently invalid/attack sigs so we just fail them here rather than crash the thread. - System.err.println("Caught NPE inside bouncy castle: " + e); + log.error("Caught NPE inside bouncy castle"); e.printStackTrace(); return false; } @@ -347,7 +429,6 @@ public class ECKey implements Serializable { return ECKey.verify(data, signature, pub); } - private static BigInteger extractPrivateKeyFromASN1(byte[] asn1privkey) { // To understand this code, see the definition of the ASN.1 format for EC private keys in the OpenSSL source // code in ec_asn1.c: @@ -379,13 +460,25 @@ public class ECKey implements Serializable { * encoded string. * * @throws IllegalStateException if this ECKey does not have the private part. + * @throws KeyCryptException if this ECKey is encrypted and no AESKey is provided or it does not decrypt the ECKey. */ - public String signMessage(String message) { + public String signMessage(String message) throws KeyCrypterException { + return signMessage(message, null); + } + + /** + * Signs a text message using the standard Bitcoin messaging signing format and returns the signature as a base64 + * encoded string. + * + * @throws IllegalStateException if this ECKey does not have the private part. + * @throws KeyCryptException if this ECKey is encrypted and no AESKey is provided or it does not decrypt the ECKey. + */ + public String signMessage(String message, KeyParameter aesKey) throws KeyCrypterException { if (priv == null) throw new IllegalStateException("This ECKey does not have the private key necessary for signing."); byte[] data = Utils.formatMessageForSigning(message); Sha256Hash hash = Sha256Hash.createDouble(data); - ECDSASignature sig = sign(hash); + ECDSASignature sig = sign(hash, aesKey); // Now we have to work backwards to figure out the recId needed to recover the signature. int recId = -1; for (int i = 0; i < 4; i++) { @@ -608,4 +701,113 @@ public class ECKey implements Serializable { // avoid picking up the type code (compressed vs uncompressed) which is tacked on the end. return (pub[0] & 0xFF) | ((pub[1] & 0xFF) << 8) | ((pub[2] & 0xFF) << 16) | ((pub[3] & 0xFF) << 24); } + + /** + * Create an encrypted private key with the keyCrypter and the AES key supplied. + * This method returns a new encrypted key and leaves the original unchanged. + * To be secure you need to clear the original, unencrypted private key bytes. + * + * @param keyCrypter The keyCrypter that specifies exactly how the encrypted bytes are created. + * @param aesKey The KeyParameter with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached as it is slow to create). + * @return encryptedKey + */ + public ECKey encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) throws KeyCrypterException { + Preconditions.checkNotNull(keyCrypter); + EncryptedPrivateKey encryptedPrivateKey = keyCrypter.encrypt(getPrivKeyBytes(), aesKey); + return new ECKey(encryptedPrivateKey, getPubKey(), keyCrypter); + } + + /** + * Create a decrypted private key with the keyCrypter and AES key supplied. + * + * @param keyCrypter The keyCrypter that specifies exactly how the decrypted bytes are created. + * @param aesKey The KeyParameter with the AES encryption key (usually constructed with keyCrypter#deriveKey and cached). + * @return unencryptedKey + */ + public ECKey decrypt(KeyCrypter keyCrypter, KeyParameter aesKey) throws KeyCrypterException { + Preconditions.checkNotNull(keyCrypter); + + // Check that the keyCrypter matches the one used to encrypt the keys, if set. + if (this.keyCrypter != null && !this.keyCrypter.equals(keyCrypter)) { + throw new KeyCrypterException("The keyCrypter being used to decrypt the key is different to the one that was used to encrypt it"); + } + + // Decrypt the private key. + byte[] unencryptedPrivateKey = keyCrypter.decrypt(encryptedPrivateKey, aesKey); + + return new ECKey(unencryptedPrivateKey, getPubKey()); + } + + /** + * Check that it is possible to decrypt the key with the keyCrypter and that the original key is returned. + * + * Because it is a critical failure if the private keys cannot be decrypted successfully (resulting of loss of all bitcoins controlled + * by the private key) you can use this method to check when you *encrypt* a wallet that it can definitely be decrypted successfully. + * See {@link Wallet#encrypt(KeyCrypter keyCrypter, KeyParameter aesKey)} for example usage. + * + * @returns true if the encrypted key can be decrypted back to the original key successfully. + */ + public static boolean encryptionIsReversible(ECKey originalKey, ECKey encryptedKey, KeyCrypter keyCrypter, KeyParameter aesKey) { + String genericErrorText = "The check that encryption could be reversed failed for key " + originalKey.toString() + ". "; + try { + ECKey rebornUnencryptedKey = encryptedKey.decrypt(keyCrypter, aesKey); + if (rebornUnencryptedKey == null) { + log.error(genericErrorText + "The test decrypted key was missing."); + return false; + } + + byte[] originalPrivateKeyBytes = originalKey.getPrivKeyBytes(); + if (originalPrivateKeyBytes != null) { + if (rebornUnencryptedKey.getPrivKeyBytes() == null) { + log.error(genericErrorText + "The test decrypted key was missing."); + return false; + } else { + if (originalPrivateKeyBytes.length != rebornUnencryptedKey.getPrivKeyBytes().length) { + log.error(genericErrorText + "The test decrypted private key was a different length to the original."); + return false; + } else { + for (int i = 0; i < originalPrivateKeyBytes.length; i++) { + if (originalPrivateKeyBytes[i] != rebornUnencryptedKey.getPrivKeyBytes()[i]) { + log.error(genericErrorText + "Byte " + i + " of the private key did not match the original."); + return false; + } + } + } + } + } + } catch (KeyCrypterException kce) { + log.error(kce.getMessage()); + return false; + } + + // Key can successfully be decrypted. + return true; + } + + /** + * Indicates whether the private key is encrypted (true) or not (false). + * A private key is deemed to be encrypted when there is both a KeyCrypter and the encryptedPrivateKey is non-zero. + */ + public boolean isEncrypted() { + return keyCrypter != null && encryptedPrivateKey != null && encryptedPrivateKey.getEncryptedBytes() != null && encryptedPrivateKey.getEncryptedBytes().length > 0; + } + + /** + * @return The encryptedPrivateKey (containing the encrypted private key bytes and initialisation vector) for this ECKey, + * or null if the ECKey is not encrypted. + */ + public EncryptedPrivateKey getEncryptedPrivateKey() { + if (encryptedPrivateKey == null) { + return null; + } else { + return encryptedPrivateKey.clone(); + } + } + + /** + * @return The KeyCrypter that was used to encrypt to encrypt this ECKey. You need this to decrypt the ECKey. + */ + public KeyCrypter getKeyCrypter() { + return keyCrypter; + } } diff --git a/core/src/main/java/com/google/bitcoin/core/Transaction.java b/core/src/main/java/com/google/bitcoin/core/Transaction.java index b23a85f4..5844557f 100644 --- a/core/src/main/java/com/google/bitcoin/core/Transaction.java +++ b/core/src/main/java/com/google/bitcoin/core/Transaction.java @@ -20,6 +20,7 @@ import com.google.bitcoin.core.TransactionConfidence.ConfidenceType; import com.google.common.base.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.spongycastle.crypto.params.KeyParameter; import java.io.*; import java.math.BigInteger; @@ -697,6 +698,21 @@ public class Transaction extends ChildMessage implements Serializable { * @param wallet A wallet is required to fetch the keys needed for signing. */ public synchronized void signInputs(SigHash hashType, Wallet wallet) throws ScriptException { + signInputs(hashType, wallet, null); + } + + /** + * Once a transaction has some inputs and outputs added, the signatures in the inputs can be calculated. The + * signature is over the transaction itself, to prove the redeemer actually created that transaction, + * so we have to do this step last.

+ *

+ * This method is similar to SignatureHash in script.cpp + * + * @param hashType This should always be set to SigHash.ALL currently. Other types are unused. + * @param wallet A wallet is required to fetch the keys needed for signing. + * @param aesKey The AES key to use to decrypt the key before signing. Null if no decryption is required. + */ + public synchronized void signInputs(SigHash hashType, Wallet wallet, KeyParameter aesKey) throws ScriptException { Preconditions.checkState(inputs.size() > 0); Preconditions.checkState(outputs.size() > 0); @@ -733,7 +749,7 @@ public class Transaction extends ChildMessage implements Serializable { try { // Usually 71-73 bytes. ByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(73); - bos.write(key.sign(hash).encodeToDER()); + bos.write(key.sign(hash, aesKey).encodeToDER()); bos.write((hashType.ordinal() + 1) | (anyoneCanPay ? 0x80 : 0)); signatures[i] = bos.toByteArray(); bos.close(); @@ -992,7 +1008,7 @@ public class Transaction extends ChildMessage implements Serializable { maybeParse(); out.defaultWriteObject(); } - + /** * Gets the count of regular SigOps in this transactions */ diff --git a/core/src/main/java/com/google/bitcoin/core/Wallet.java b/core/src/main/java/com/google/bitcoin/core/Wallet.java index d1811174..4787089f 100644 --- a/core/src/main/java/com/google/bitcoin/core/Wallet.java +++ b/core/src/main/java/com/google/bitcoin/core/Wallet.java @@ -16,8 +16,14 @@ package com.google.bitcoin.core; +import org.bitcoinj.wallet.Protos.Wallet.EncryptionType; +import org.spongycastle.crypto.params.KeyParameter; + import com.google.bitcoin.core.TransactionConfidence.ConfidenceType; import com.google.bitcoin.core.WalletTransaction.Pool; +import com.google.bitcoin.crypto.EncryptedPrivateKey; +import com.google.bitcoin.crypto.KeyCrypter; +import com.google.bitcoin.crypto.KeyCrypterException; import com.google.bitcoin.store.WalletProtobufSerializer; import com.google.bitcoin.utils.Locks; import com.google.common.base.Objects; @@ -25,6 +31,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -169,7 +176,7 @@ public class Wallet implements Serializable, BlockChainListener { /** * A list of public/private EC keys owned by this user. Access it using addKey[s], hasKey[s] and findPubKeyFromHash. */ - public final ArrayList keychain; + public ArrayList keychain; private final NetworkParameters params; @@ -191,6 +198,7 @@ public class Wallet implements Serializable, BlockChainListener { // A listener that relays confidence changes from the transaction confidence object to the wallet event listener, // as a convenience to API users so they don't have to register on every transaction themselves. private transient TransactionConfidence.Listener txConfidenceListener; + // If a TX hash appears in this set then notifyNewBestBlock will ignore it, as its confidence was already set up // in receive() via Transaction.setBlockAppearance(). As the BlockChain always calls notifyNewBestBlock even if // it sent transactions to the wallet, without this we'd double count. @@ -280,11 +288,36 @@ public class Wallet implements Serializable, BlockChainListener { private transient CoinSelector coinSelector = new DefaultCoinSelector(); + /** + * The keyCrypter for the wallet. This specifies the algorithm used for encrypting and decrypting the private keys. + */ + private KeyCrypter keyCrypter; + + /** + * The wallet version. This is an int that can be used to track breaking changes in the wallet format. + * You can also use it to detect wallets that come from the future (ie they contain features you + * do not know how to deal with). + */ + private int version; + + /** + * A description for the wallet. + */ + String description; + /** * Creates a new, empty wallet with no keys and no transactions. If you want to restore a wallet from disk instead, * see loadFromFile. */ public Wallet(NetworkParameters params) { + this(params, null); + } + + /** + * Create a wallet with a keyCrypter to use in encrypting and decrypting keys. + */ + public Wallet(NetworkParameters params, KeyCrypter keyCrypter) { + this.keyCrypter = keyCrypter; this.params = checkNotNull(params); keychain = new ArrayList(); unspent = new HashMap(); @@ -299,6 +332,7 @@ public class Wallet implements Serializable, BlockChainListener { private void createTransientState() { ignoreNextNewBlock = new HashSet(); txConfidenceListener = new TransactionConfidence.Listener() { + @Override public void onConfidenceChanged(Transaction tx) { lock.lock(); // The invokers unlock us immediately so if an exception is thrown, the lock will be already open. @@ -1206,7 +1240,7 @@ public class Wallet implements Serializable, BlockChainListener { addWalletTransaction(Pool.DEAD, doubleSpend); // Inform the event listeners of the newly dead tx. doubleSpend.getConfidence().setOverridingTransaction(tx); - } + } } /** @@ -1657,6 +1691,12 @@ public class Wallet implements Serializable, BlockChainListener { */ public BigInteger fee = BigInteger.ZERO; + /** + * The AES key to use to decrypt the private keys before signing. + * If null then no decryption will be performed and if decryption is required an exception will be thrown. + */ + public KeyParameter aesKey = null; + // Tracks if this has been passed to wallet.completeTx already: just a safety check. private boolean completed; @@ -1867,7 +1907,7 @@ public class Wallet implements Serializable, BlockChainListener { // Now sign the inputs, thus proving that we are entitled to redeem the connected outputs. try { - req.tx.signInputs(Transaction.SigHash.ALL, this); + req.tx.signInputs(Transaction.SigHash.ALL, this, req.aesKey); } catch (ScriptException e) { // If this happens it means an output script in a wallet tx could not be understood. That should never // happen, if it does it means the wallet has got into an inconsistent state. @@ -1947,6 +1987,14 @@ public class Wallet implements Serializable, BlockChainListener { // TODO: Consider making keys a sorted list or hashset so membership testing is faster. for (final ECKey key : keys) { if (keychain.contains(key)) continue; + + // If the key has a keyCrypter that does not match the Wallet's then a KeyCrypterException is thrown. + // This is done because only one keyCrypter is persisted per Wallet and hence all the keys must be homogenous. + if (keyCrypter != null && keyCrypter.getUnderstoodEncryptionType() != EncryptionType.UNENCRYPTED) { + if ( key.isEncrypted() && !keyCrypter.equals(key.getKeyCrypter())) { + throw new KeyCrypterException("Cannot add key " + key.toString() + " because the keyCrypter does not match the wallets. Keys must be homogenous."); + } + } keychain.add(key); added++; } @@ -1956,6 +2004,7 @@ public class Wallet implements Serializable, BlockChainListener { } finally { lock.unlock(); } + for (ECKey key : keys) { // TODO: Change this interface to be batch-oriented. for (WalletEventListener listener : eventListeners) { @@ -2146,6 +2195,10 @@ public class Wallet implements Serializable, BlockChainListener { builder.append("\nDEAD:\n"); toStringHelper(builder, dead, chain); } + // Add the keyCrypter so that any setup parameters are in the wallet toString. + if (this.keyCrypter != null) { + builder.append("\n keyCrypter: " + keyCrypter.toString()); + } return builder.toString(); } finally { lock.unlock(); @@ -2574,7 +2627,265 @@ public class Wallet implements Serializable, BlockChainListener { lock.unlock(); } } - + + /** + * Encrypt the wallet using the KeyCrypter and the AES key. + * + * @param keyCrypter The KeyCrypter that specifies how to encrypt/ decrypt a key + * @param aesKey AES key to use (normally created using KeyCrypter#deriveKey and cached as it is time consuming to create from a password) + * @throws KeyCrypterException Thrown if the wallet encryption fails. If so, the wallet state is unchanged. + */ + public void encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) { + lock.lock(); + try { + Preconditions.checkNotNull(keyCrypter); + + // If the wallet is already encrypted then you cannot encrypt it again. + if (getEncryptionType() != EncryptionType.UNENCRYPTED) { + throw new IllegalStateException("Wallet is already encrypted"); + } + + // Create a new arraylist that will contain the encrypted keys + ArrayList encryptedKeyChain = new ArrayList(); + + for (ECKey key : keychain) { + if (key.isEncrypted()) { + // Key is already encrypted - add as is. + encryptedKeyChain.add(key); + } else { + // Encrypt the key. + ECKey encryptedKey = key.encrypt(keyCrypter, aesKey); + + // Check that the encrypted key can be successfully decrypted. + // This is done as it is a critical failure if the private key cannot be decrypted successfully + // (all bitcoin controlled by that private key is lost forever). + // For a correctly constructed keyCrypter the encryption should always be reversible so it is just being as cautious as possible. + if (!ECKey.encryptionIsReversible(key, encryptedKey, keyCrypter, aesKey)) { + // Abort encryption + throw new KeyCrypterException("The key " + key.toString() + " cannot be successfully decrypted after encryption so aborting wallet encryption."); + } + + encryptedKeyChain.add(encryptedKey); + } + } + + // Now ready to use the encrypted keychain so go through the old keychain clearing all the unencrypted private keys. + // (This is to avoid the possibility of key recovery from memory). + for (ECKey key : keychain) { + if (!key.isEncrypted()) { + key.clearPrivateKey(); + } + } + + // Replace the old keychain with the encrypted one. + keychain = encryptedKeyChain; + + // The wallet is now encrypted. + this.keyCrypter = keyCrypter; + + if (autosaveToFile != null) { + autoSave(); + } + } finally { + lock.unlock(); + } + } + + /** + * Decrypt the wallet with the wallets keyCrypter and AES key. + * + * @param aesKey AES key to use (normally created using KeyCrypter#deriveKey and cached as it is time consuming to create from a password) + * @throws KeyCrypterException Thrown if the wallet decryption fails. If so, the wallet state is unchanged. + */ + public void decrypt(KeyParameter aesKey) { + lock.lock(); + try { + // Check the wallet is already encrypted - you cannot decrypt an unencrypted wallet. + if (getEncryptionType() == EncryptionType.UNENCRYPTED) { + throw new IllegalStateException("Wallet is already decrypted"); + } + + // Check that the wallet keyCrypter is non-null. + // This is set either at construction (if an encrypted wallet is created) or by wallet encryption. + Preconditions.checkNotNull(keyCrypter); + + // Create a new arraylist that will contain the decrypted keys + ArrayList decryptedKeyChain = new ArrayList(); + + for (ECKey key : keychain) { + // Decrypt the key. + if (!key.isEncrypted()) { + // Not encrypted - add to chain as is. + decryptedKeyChain.add(key); + } else { + ECKey decryptedECKey = key.decrypt(keyCrypter, aesKey); + decryptedKeyChain.add(decryptedECKey); + } + } + + // Replace the old keychain with the unencrypted one. + keychain = decryptedKeyChain; + + // The wallet is now unencrypted. + keyCrypter = null; + + if (autosaveToFile != null) { + autoSave(); + } + } finally { + lock.unlock(); + } + } + + /** + * Create a new encrypted ECKey and add it to the wallet. + * + * @param keyCrypter The keyCrypter to use in encrypting the new key + * @param aesKey The AES key to use to encrypt the new key + * @return ECKey the new, encrypted ECKey + */ + public ECKey addNewEncryptedKey(KeyCrypter keyCrypter, KeyParameter aesKey) { + lock.lock(); + try { + ECKey newKey = (new ECKey()).encrypt(keyCrypter, aesKey); + addKey(newKey); + return newKey; + } finally { + lock.unlock(); + } + } + + /** + * Check whether the password can decrypt the first key in the wallet. + * This can be used to check the validity of an entered password. + * + * @returns boolean true if password supplied can decrypt the first private key in the wallet, false otherwise. + */ + public boolean checkPassword(CharSequence password) { + lock.lock(); + try { + if (keyCrypter == null) { + // The password cannot decrypt anything as the keyCrypter is null. + return false; + } + return checkAESKey(keyCrypter.deriveKey(password)); + } finally { + lock.unlock(); + } + } + + /** + * Check whether the AES key can decrypt the first encrypted key in the wallet. + * + * @returns boolean true if AES key supplied can decrypt the first encrypted private key in the wallet, false otherwise. + */ + public boolean checkAESKey(KeyParameter aesKey) { + lock.lock(); + try { + // If no keys then cannot decrypt. + if (!getKeys().iterator().hasNext()) { + return false; + } + + // Find the first encrypted key in the wallet. + ECKey firstEncryptedECKey = null; + Iterator iterator = getKeys().iterator(); + while (iterator.hasNext() && firstEncryptedECKey == null) { + ECKey loopECKey = iterator.next(); + if (loopECKey.isEncrypted()) { + firstEncryptedECKey = loopECKey; + } + } + + // There are no encrypted keys in the wallet. + if (firstEncryptedECKey == null) { + return false; + } + + String originalAddress = firstEncryptedECKey.toAddress(getNetworkParameters()).toString(); + + if (firstEncryptedECKey != null && firstEncryptedECKey.isEncrypted() && firstEncryptedECKey.getEncryptedPrivateKey() != null) { + try { + ECKey rebornKey = firstEncryptedECKey.decrypt(keyCrypter, aesKey); + + // Check that the decrypted private key's address is correct ie it decrypted accurately. + String rebornAddress = rebornKey.toAddress(getNetworkParameters()).toString(); + return originalAddress.equals(rebornAddress); + } catch (KeyCrypterException ede) { + // The AES key supplied is incorrect. + return false; + } + } + return false; + } finally { + lock.unlock(); + } + } + + /** + * Get the wallet's KeyCrypter. + * (Used in encrypting/ decrypting an ECKey). + */ + public KeyCrypter getKeyCrypter() { + lock.lock(); + try { + return keyCrypter; + } finally { + lock.unlock(); + } + } + + /** + * Get the type of encryption used for this wallet. + * + * (This is a convenience method - the encryption type is actually stored in the keyCrypter). + */ + public EncryptionType getEncryptionType() { + lock.lock(); + try { + if (keyCrypter == null) { + // Unencrypted wallet. + return EncryptionType.UNENCRYPTED; + } else { + return keyCrypter.getUnderstoodEncryptionType(); + } + } finally { + lock.unlock(); + } + } + + /** + * Get the version of the Wallet. + * This is an int you can use to indicate which versions of wallets your code understands, + * and which come from the future (and hence cannot be safely loaded). + */ + public int getVersion() { + return version; + } + + /** + * Set the version number of the wallet. See {@link Wallet#getVersion()}. + */ + public void setVersion(int version) { + this.version = version; + } + + /** + * Set the description of the wallet. + * This is a Unicode encoding string typically entered by the user as descriptive text for the wallet. + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * Get the description of the wallet. See {@link Wallet#setDescription(String))} + * @return + */ + public String getDescription() { + return description; + } + /** * Gets the number of elements that will be added to a bloom filter returned by getBloomFilter */ diff --git a/core/src/main/java/com/google/bitcoin/crypto/EncryptedPrivateKey.java b/core/src/main/java/com/google/bitcoin/crypto/EncryptedPrivateKey.java new file mode 100644 index 00000000..f9f47958 --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/crypto/EncryptedPrivateKey.java @@ -0,0 +1,137 @@ +/** + * Copyright 2013 Jim Burton. + * + * Licensed under the MIT license (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://opensource.org/licenses/mit-license.php + * + * 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.google.bitcoin.crypto; + +import java.util.Arrays; + +import com.google.bitcoin.core.BlockChain; +import com.google.bitcoin.core.Wallet; +import com.google.common.base.Preconditions; + +/** + *

An EncryptedPrivateKey contains the information produced after encrypting the private key bytes of an ECKey.

+ * + *

It contains two member variables - initialisationVector and encryptedPrivateBytes. The initialisationVector is + * a randomly chosen list of bytes that were used to initialise the AES block cipher when the private key bytes were encrypted. + * You need these for decryption. The encryptedPrivateBytes are the result of AES encrypting the private keys using + * an AES key that is drrived from a user entered password. You need the password to recreate the AES key in order + * to decrypt these bytes.

+ */ +public class EncryptedPrivateKey { + + private byte[] initialisationVector = null; + private byte[] encryptedPrivateBytes = null; + + /** + * Cloning constructor. + * @param encryptedPrivateKey EncryptedPrivateKey to clone. + */ + public EncryptedPrivateKey(EncryptedPrivateKey encryptedPrivateKey) { + Preconditions.checkNotNull(encryptedPrivateKey); + setInitialisationVector(encryptedPrivateKey.getInitialisationVector()); + setEncryptedPrivateBytes(encryptedPrivateKey.getEncryptedBytes()); + } + + /** + * @param iv + * @param encryptedPrivateKeys + */ + public EncryptedPrivateKey(byte[] initialisationVector, byte[] encryptedPrivateKeys) { + setInitialisationVector(initialisationVector); + setEncryptedPrivateBytes(encryptedPrivateKeys); + } + + public byte[] getInitialisationVector() { + return initialisationVector; + } + + /** + * Set the initialisationVector, cloning the bytes. + * + * @param initialisationVector + */ + public void setInitialisationVector(byte[] initialisationVector) { + if (initialisationVector == null) { + this.initialisationVector = null; + return; + } + + byte[] cloneIV = new byte[initialisationVector.length]; + System.arraycopy(initialisationVector, 0, cloneIV, 0, initialisationVector.length); + + this.initialisationVector = cloneIV; + } + + public byte[] getEncryptedBytes() { + return encryptedPrivateBytes; + } + + /** + * Set the encrypted private key bytes, cloning them. + * + * @param encryptedPrivateBytes + */ + public void setEncryptedPrivateBytes(byte[] encryptedPrivateBytes) { + if (encryptedPrivateBytes == null) { + this.encryptedPrivateBytes = null; + return; + } + + this.encryptedPrivateBytes = Arrays.copyOf(encryptedPrivateBytes, encryptedPrivateBytes.length); + } + + @Override + public EncryptedPrivateKey clone() { + return new EncryptedPrivateKey(getInitialisationVector(), getEncryptedBytes()); + } + + @Override + public int hashCode() { + return com.google.common.base.Objects.hashCode(encryptedPrivateBytes, initialisationVector); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final EncryptedPrivateKey other = (EncryptedPrivateKey) obj; + + return com.google.common.base.Objects.equal(this.initialisationVector, other.initialisationVector) + && com.google.common.base.Objects.equal(this.encryptedPrivateBytes, other.encryptedPrivateBytes); + } + + @Override + public String toString() { + return "EncryptedPrivateKey [initialisationVector=" + Arrays.toString(initialisationVector) + ", encryptedPrivateKey=" + Arrays.toString(encryptedPrivateBytes) + "]"; + } + + /** + * Clears all the EncryptedPrivateKey contents from memory (overwriting all data including PRIVATE KEYS). + * WARNING - this method irreversibly deletes the private key information. + */ + public void clear() { + if (encryptedPrivateBytes != null) { + Arrays.fill(encryptedPrivateBytes, (byte)0); + } + if (initialisationVector != null) { + Arrays.fill(initialisationVector, (byte)0); + } + } +} diff --git a/core/src/main/java/com/google/bitcoin/crypto/KeyCrypter.java b/core/src/main/java/com/google/bitcoin/crypto/KeyCrypter.java new file mode 100644 index 00000000..3afa3e93 --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/crypto/KeyCrypter.java @@ -0,0 +1,68 @@ +/** + * Copyright 2013 Jim Burton. + * + * Licensed under the MIT license (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://opensource.org/licenses/mit-license.php + * + * 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.google.bitcoin.crypto; + +import java.io.Serializable; + +import org.bitcoinj.wallet.Protos.Wallet.EncryptionType; +import org.spongycastle.crypto.params.KeyParameter; + +/** + *

A KeyCrypter can be used to encrypt and decrypt a message. The sequence of events to encrypt and then decrypt + * a message are as follows:

+ * + *

(1) Ask the user for a password. deriveKey() is then called to create an KeyParameter. This contains the AES + *key that will be used for encryption.

+ *

(2) Encrypt the message using encrypt(), providing the message bytes and the KeyParameter from (1). This returns + *an EncryptedPrivateKey which contains the encryptedPrivateKey bytes and an initialisation vector.

+ *

(3) To decrypt an EncryptedPrivateKey, repeat step (1) to get a KeyParameter, then call decrypt().

+ * + *

There can be different algorithms used for encryption/ decryption so the getUnderstoodEncryptionType is used + *to determine whether any given KeyCrypter can understand the type of encrypted data you have.

+ */ +public interface KeyCrypter extends Serializable { + + /** + * Return the EncryptionType enum value which denotes the type of encryption/ decryption that this KeyCrypter + * can understand. + */ + public EncryptionType getUnderstoodEncryptionType(); + + /** + * Create a KeyParameter (which typically contains an AES key) + * @param password + * @return KeyParameter The KeyParameter which typically contains the AES key to use for encrypting and decrypting + * @throws KeyCrypterException + */ + public KeyParameter deriveKey(CharSequence password) throws KeyCrypterException; + + /** + * Decrypt the provided encrypted bytes, converting them into unencrypted bytes. + * + * @throws KeyCrypterException if decryption was unsuccessful. + */ + public byte[] decrypt(EncryptedPrivateKey encryptedBytesToDecode, KeyParameter aesKey) throws KeyCrypterException; + + /** + * Encrypt the supplied bytes, converting them into ciphertext. + * + * @param plainBytes + * @param aesKey + * @return encryptedPrivateKey An encryptedPrivateKey containing the encrypted bytes and an initialisation vector. + * @throws keyCrypterException if encryption was unsuccessful + */ + public EncryptedPrivateKey encrypt(byte[] plainBytes, KeyParameter aesKey) throws KeyCrypterException; +} diff --git a/core/src/main/java/com/google/bitcoin/crypto/KeyCrypterException.java b/core/src/main/java/com/google/bitcoin/crypto/KeyCrypterException.java new file mode 100644 index 00000000..efd016b0 --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/crypto/KeyCrypterException.java @@ -0,0 +1,22 @@ +package com.google.bitcoin.crypto; + +/** + *

Exception to provide the following to {@link EncrypterDecrypterOpenSSL}:

+ * + *

This base exception acts as a general failure mode not attributable to a specific cause (other than + * that reported in the exception message). Since this is in English, it may not be worth reporting directly + * to the user other than as part of a "general failure to parse" response.

+ */ +public class KeyCrypterException extends RuntimeException { + private static final long serialVersionUID = -4441989608332681377L; + + public KeyCrypterException(String s) { + super(s); + } + + public KeyCrypterException(String s, Throwable throwable) { + super(s, throwable); + } +} diff --git a/core/src/main/java/com/google/bitcoin/crypto/KeyCrypterScrypt.java b/core/src/main/java/com/google/bitcoin/crypto/KeyCrypterScrypt.java new file mode 100644 index 00000000..19f36018 --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/crypto/KeyCrypterScrypt.java @@ -0,0 +1,262 @@ +/** + * Copyright 2013 Jim Burton. + * + * Licensed under the MIT license (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://opensource.org/licenses/mit-license.php + * + * 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.google.bitcoin.crypto; + +import java.io.Serializable; +import java.security.SecureRandom; + +import org.bitcoinj.wallet.Protos; +import org.bitcoinj.wallet.Protos.ScryptParameters; +import org.bitcoinj.wallet.Protos.Wallet.EncryptionType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.crypto.BufferedBlockCipher; +import org.spongycastle.crypto.engines.AESFastEngine; +import org.spongycastle.crypto.modes.CBCBlockCipher; +import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.crypto.params.ParametersWithIV; + +import com.google.common.base.Preconditions; +import com.google.protobuf.ByteString; +import com.lambdaworks.crypto.SCrypt; + +/** + *

This class encrypts and decrypts byte arrays and strings using scrypt as the + * key derivation function and AES for the encryption.

+ * + *

You can use this class to:

+ * + *

1) Using a user password, create an AES key that can encrypt and decrypt your private keys. + * To convert the password to the AES key, scrypt is used. This is an algorithm resistant + * to brute force attacks. You can use the ScryptParameters to tune how difficult you + * want this to be generation to be.

+ * + *

2) Using the AES Key generated above, you then can encrypt and decrypt any bytes using + * the AES symmetric cipher. Eight bytes of salt is used to prevent dictionary attacks.

+ */ +public class KeyCrypterScrypt implements KeyCrypter, Serializable { + + public Logger log = LoggerFactory.getLogger(KeyCrypterScrypt.class.getName()); + + private static final long serialVersionUID = 949662512049152670L; + + /** + * Key length in bytes. + */ + public static final int KEY_LENGTH = 32; // = 256 bits. + + /** + * The size of an AES block in bytes. + * This is also the length of the initialisation vector. + */ + public static final int BLOCK_LENGTH = 16; // = 128 bits. + + /** + * The length of the salt used. + */ + public static final int SALT_LENGTH = 8; + + transient private static SecureRandom secureRandom = new SecureRandom(); + + // Scrypt parameters. + transient private ScryptParameters scryptParameters; + + /** + * Encryption/ Decryption using default parameters and a random salt + */ + public KeyCrypterScrypt() { + byte[] salt = new byte[SALT_LENGTH]; + secureRandom.nextBytes(salt); + Protos.ScryptParameters.Builder scryptParametersBuilder = Protos.ScryptParameters.newBuilder().setSalt(ByteString.copyFrom(salt)); + this.scryptParameters = scryptParametersBuilder.build(); + } + + /** + * Encryption/ Decryption using specified Scrypt parameters. + * + * @param scryptParameters ScryptParameters to use + * @throws NullPointerException if the scryptParameters or any of its N, R or P is null. + */ + public KeyCrypterScrypt(ScryptParameters scryptParameters) { + this.scryptParameters = Preconditions.checkNotNull(scryptParameters); + // Check there is a non-empty salt. + // (Some early MultiBit wallets has a missing salt so it is not a hard fail). + if (scryptParameters.getSalt() == null + || scryptParameters.getSalt().toByteArray() == null + || scryptParameters.getSalt().toByteArray().length == 0) { + log.warn("You are using a ScryptParameters with no salt. Your encryption may be vulnerable to a dictionary attack."); + } + } + + /** + * Generate AES key. + * + * This is a very slow operation compared to encrypt/ decrypt so it is normally worth caching the result. + * + * @param password The password to use in key generation + * @return The KeyParameter containing the created AES key + * @throws KeyCrypterException + */ + @Override + public KeyParameter deriveKey(CharSequence password) throws KeyCrypterException { + byte[] passwordBytes = null; + try { + passwordBytes = convertToByteArray(password); + byte[] salt = new byte[0]; + if ( scryptParameters.getSalt() != null) { + salt = scryptParameters.getSalt().toByteArray(); + } else { + // Warn the user that they are not using a salt. + // (Some early MultiBit wallets had a blank salt). + log.warn("You are using a ScryptParameters with no salt. Your encryption may be vulnerable to a dictionary attack."); + } + + byte[] keyBytes = SCrypt.scrypt(passwordBytes, salt, (int) scryptParameters.getN(), scryptParameters.getR(), scryptParameters.getP(), KEY_LENGTH); + return new KeyParameter(keyBytes); + } catch (Exception e) { + throw new KeyCrypterException("Could not generate key from password and salt.", e); + } finally { + // Zero the password bytes. + if (passwordBytes != null) { + java.util.Arrays.fill(passwordBytes, (byte) 0); + } + } + } + + /** + * Password based encryption using AES - CBC 256 bits. + * + * @param plain The bytes to encrypt + * @param aesKey The AES key to use for encryption + * @return EncryptedPrivateKey containing IV and the encrypted private key + * @throws KeyCrypterException + */ + @Override + public EncryptedPrivateKey encrypt(byte[] plainBytes, KeyParameter aesKey) throws KeyCrypterException { + Preconditions.checkNotNull(plainBytes); + Preconditions.checkNotNull(aesKey); + + try { + // Generate iv - each encryption call has a different iv. + byte[] iv = new byte[BLOCK_LENGTH]; + secureRandom.nextBytes(iv); + + ParametersWithIV keyWithIv = new ParametersWithIV(aesKey, iv); + + // Encrypt using AES. + BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine())); + cipher.init(true, keyWithIv); + byte[] encryptedBytes = new byte[cipher.getOutputSize(plainBytes.length)]; + int length = cipher.processBytes(plainBytes, 0, plainBytes.length, encryptedBytes, 0); + + cipher.doFinal(encryptedBytes, length); + + return new EncryptedPrivateKey(iv, encryptedBytes); + } catch (Exception e) { + throw new KeyCrypterException("Could not encrypt bytes.", e); + } + } + + /** + * Decrypt bytes previously encrypted with this class. + * + * @param privateKeyToDecode The private key to decrypt + * @param aesKey The AES key to use for decryption + * @return The decrypted bytes + * @throws KeyCrypterException if bytes could not be decoded to a valid key + */ + @Override + public byte[] decrypt(EncryptedPrivateKey privateKeyToDecode, KeyParameter aesKey) throws KeyCrypterException { + Preconditions.checkNotNull(privateKeyToDecode); + Preconditions.checkNotNull(aesKey); + + try { + ParametersWithIV keyWithIv = new ParametersWithIV(new KeyParameter(aesKey.getKey()), privateKeyToDecode.getInitialisationVector()); + + // Decrypt the message. + BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine())); + cipher.init(false, keyWithIv); + + byte[] cipherBytes = privateKeyToDecode.getEncryptedBytes(); + int minimumSize = cipher.getOutputSize(cipherBytes.length); + byte[] outputBuffer = new byte[minimumSize]; + int length1 = cipher.processBytes(cipherBytes, 0, cipherBytes.length, outputBuffer, 0); + int length2 = cipher.doFinal(outputBuffer, length1); + int actualLength = length1 + length2; + + byte[] decryptedBytes = new byte[actualLength]; + System.arraycopy(outputBuffer, 0, decryptedBytes, 0, actualLength); + + return decryptedBytes; + } catch (Exception e) { + throw new KeyCrypterException("Could not decrypt bytes", e); + } + } + + /** + * Convert a CharSequence (which are UTF16) into a byte array. + * + * Note: a String.getBytes() is not used to avoid creating a String of the password in the JVM. + */ + private byte[] convertToByteArray(CharSequence charSequence) { + Preconditions.checkNotNull(charSequence); + + byte[] byteArray = new byte[charSequence.length() << 1]; + for(int i = 0; i < charSequence.length(); i++) { + int bytePosition = i << 1; + byteArray[bytePosition] = (byte) ((charSequence.charAt(i)&0xFF00)>>8); + byteArray[bytePosition + 1] = (byte) (charSequence.charAt(i)&0x00FF); + } + return byteArray; + } + + public ScryptParameters getScryptParameters() { + return scryptParameters; + } + + /** + * Return the EncryptionType enum value which denotes the type of encryption/ decryption that this KeyCrypter + * can understand. + */ + @Override + public EncryptionType getUnderstoodEncryptionType() { + return EncryptionType.ENCRYPTED_SCRYPT_AES; + } + + @Override + public String toString() { + return "EncrypterDecrypterScrypt [scryptParameters=" + scryptParameters.toString() + "]"; + } + + @Override + public int hashCode() { + return com.google.common.base.Objects.hashCode(scryptParameters); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final KeyCrypterScrypt other = (KeyCrypterScrypt) obj; + + return com.google.common.base.Objects.equal(this.scryptParameters, other.scryptParameters); + } +} diff --git a/core/src/main/java/com/google/bitcoin/store/WalletExtensionSerializer.java b/core/src/main/java/com/google/bitcoin/store/WalletExtensionSerializer.java index b68041aa..822f294d 100644 --- a/core/src/main/java/com/google/bitcoin/store/WalletExtensionSerializer.java +++ b/core/src/main/java/com/google/bitcoin/store/WalletExtensionSerializer.java @@ -16,13 +16,15 @@ package com.google.bitcoin.store; -import com.google.bitcoin.core.NetworkParameters; -import com.google.bitcoin.core.Wallet; -import org.bitcoinj.wallet.Protos; - import java.util.Collection; import java.util.Collections; +import org.bitcoinj.wallet.Protos; + +import com.google.bitcoin.core.NetworkParameters; +import com.google.bitcoin.core.Wallet; +import com.google.bitcoin.crypto.KeyCrypter; + /** * Optional helper for WalletProtobufSerializer that allows for serialization and deserialization of Wallet objects * with extensions and corresponding extended Wallet classes. If you want to store proprietary data into the wallet, @@ -33,6 +35,10 @@ public class WalletExtensionSerializer { return new Wallet(params); } + public Wallet newWallet(NetworkParameters params, KeyCrypter keyCrypter) { + return new Wallet(params, keyCrypter); + } + public void readExtension(Wallet wallet, Protos.Extension extProto) { if (extProto.getMandatory()) { throw new IllegalArgumentException("Unknown mandatory extension in the wallet: " + extProto.getId()); diff --git a/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java b/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java index 5e7be49d..b535af6a 100644 --- a/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java +++ b/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java @@ -16,15 +16,6 @@ package com.google.bitcoin.store; -import com.google.bitcoin.core.*; -import com.google.bitcoin.core.TransactionConfidence.ConfidenceType; -import com.google.common.base.Preconditions; -import com.google.protobuf.ByteString; -import com.google.protobuf.TextFormat; -import org.bitcoinj.wallet.Protos; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -33,6 +24,33 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.*; +import org.bitcoinj.wallet.Protos; +import org.bitcoinj.wallet.Protos.Wallet.EncryptionType; +import org.bitcoinj.wallet.Protos.Key.Type; + +import com.google.bitcoin.crypto.EncryptedPrivateKey; +import com.google.bitcoin.crypto.KeyCrypter; +import com.google.bitcoin.crypto.KeyCrypterException; +import com.google.bitcoin.crypto.KeyCrypterScrypt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.bitcoin.core.ECKey; +import com.google.bitcoin.core.NetworkParameters; +import com.google.bitcoin.core.PeerAddress; +import com.google.bitcoin.core.Sha256Hash; +import com.google.bitcoin.core.Transaction; +import com.google.bitcoin.core.TransactionConfidence; +import com.google.bitcoin.core.TransactionConfidence.ConfidenceType; +import com.google.bitcoin.core.TransactionInput; +import com.google.bitcoin.core.TransactionOutPoint; +import com.google.bitcoin.core.TransactionOutput; +import com.google.bitcoin.core.Wallet; +import com.google.bitcoin.core.WalletTransaction; +import com.google.common.base.Preconditions; +import com.google.protobuf.ByteString; +import com.google.protobuf.TextFormat; + /** * Serialize and de-serialize a wallet to a byte stream containing a * protocol buffer. Protocol buffers are @@ -56,8 +74,8 @@ public class WalletProtobufSerializer { private static final Logger log = LoggerFactory.getLogger(WalletProtobufSerializer.class); // Used for de-serialization - private Map txMap; - private WalletExtensionSerializer helper; + protected Map txMap; + protected WalletExtensionSerializer helper; public WalletProtobufSerializer() { txMap = new HashMap(); @@ -72,7 +90,6 @@ public class WalletProtobufSerializer { this.helper = h; } - /** * Formats the given wallet (transactions and keys) to the given output stream in protocol buffer format.

* @@ -102,39 +119,86 @@ public class WalletProtobufSerializer { public Protos.Wallet walletToProto(Wallet wallet) { Protos.Wallet.Builder walletBuilder = Protos.Wallet.newBuilder(); walletBuilder.setNetworkIdentifier(wallet.getNetworkParameters().getId()); + if (wallet.getDescription() != null) { + walletBuilder.setDescription(wallet.getDescription()); + } + for (WalletTransaction wtx : wallet.getWalletTransactions()) { Protos.Transaction txProto = makeTxProto(wtx); walletBuilder.addTransaction(txProto); } - + for (ECKey key : wallet.getKeys()) { - Protos.Key.Builder buf = Protos.Key.newBuilder().setCreationTimestamp(key.getCreationTimeSeconds() * 1000) + Protos.Key.Builder keyBuilder = Protos.Key.newBuilder().setCreationTimestamp(key.getCreationTimeSeconds() * 1000) // .setLabel() TODO .setType(Protos.Key.Type.ORIGINAL); if (key.getPrivKeyBytes() != null) - buf.setPrivateKey(ByteString.copyFrom(key.getPrivKeyBytes())); + keyBuilder.setPrivateKey(ByteString.copyFrom(key.getPrivKeyBytes())); + + EncryptedPrivateKey encryptedPrivateKey = key.getEncryptedPrivateKey(); + if (encryptedPrivateKey != null) { + // Key is encrypted. + Protos.EncryptedPrivateKey.Builder encryptedKeyBuilder = Protos.EncryptedPrivateKey.newBuilder() + .setEncryptedPrivateKey(ByteString.copyFrom(encryptedPrivateKey.getEncryptedBytes())) + .setInitialisationVector(ByteString.copyFrom(encryptedPrivateKey.getInitialisationVector())); + + if (key.getKeyCrypter() == null) { + throw new IllegalStateException("The encrypted key " + key.toString() + " has no KeyCrypter."); + } else { + // If it is a Scrypt + AES encrypted key, set the persisted key type. + if (key.getKeyCrypter().getUnderstoodEncryptionType() == Protos.Wallet.EncryptionType.ENCRYPTED_SCRYPT_AES) { + keyBuilder.setType(Protos.Key.Type.ENCRYPTED_SCRYPT_AES); + } else { + throw new IllegalArgumentException("The key " + key.toString() + " is encrypted with a KeyCrypter of type " + key.getKeyCrypter().getUnderstoodEncryptionType() + + ". This WalletProtobufSerialiser does not understand that type of encryption."); + } + } + keyBuilder.setEncryptedPrivateKey(encryptedKeyBuilder); + } + // We serialize the public key even if the private key is present for speed reasons: we don't want to do // lots of slow EC math to load the wallet, we prefer to store the redundant data instead. It matters more // on mobile platforms. - buf.setPublicKey(ByteString.copyFrom(key.getPubKey())); - walletBuilder.addKey(buf); + keyBuilder.setPublicKey(ByteString.copyFrom(key.getPubKey())); + walletBuilder.addKey(keyBuilder); } + // Populate the lastSeenBlockHash field. Sha256Hash lastSeenBlockHash = wallet.getLastBlockSeenHash(); if (lastSeenBlockHash != null) { walletBuilder.setLastSeenBlockHash(hashToByteString(lastSeenBlockHash)); walletBuilder.setLastSeenBlockHeight(wallet.getLastBlockSeenHeight()); } + // Populate the scrypt parameters. + KeyCrypter keyCrypter = wallet.getKeyCrypter(); + if (keyCrypter == null) { + // The wallet is unencrypted. + walletBuilder.setEncryptionType(EncryptionType.UNENCRYPTED); + } else { + // The wallet is encrypted. + walletBuilder.setEncryptionType(keyCrypter.getUnderstoodEncryptionType()); + if (keyCrypter instanceof KeyCrypterScrypt) { + KeyCrypterScrypt keyCrypterScrypt = (KeyCrypterScrypt) keyCrypter; + walletBuilder.setEncryptionParameters(keyCrypterScrypt.getScryptParameters()); + } else { + // Some other form of encryption has been specified that we do not know how to persist. + throw new RuntimeException("The wallet has encryption of type '" + keyCrypter.getUnderstoodEncryptionType() + "' but this WalletProtobufSerializer does not know how to persist this."); + } + } + + // Populate the wallet version. + walletBuilder.setVersion(wallet.getVersion()); + Collection extensions = helper.getExtensionsToWrite(wallet); for(Protos.Extension ext : extensions) { walletBuilder.addExtension(ext); } - + return walletBuilder.build(); } - private static Protos.Transaction makeTxProto(WalletTransaction wtx) { + protected static Protos.Transaction makeTxProto(WalletTransaction wtx) { Transaction tx = wtx.getTransaction(); Protos.Transaction.Builder txBuilder = Protos.Transaction.newBuilder(); @@ -193,7 +257,7 @@ public class WalletProtobufSerializer { return txBuilder.build(); } - private static void writeConfidence(Protos.Transaction.Builder txBuilder, + protected static void writeConfidence(Protos.Transaction.Builder txBuilder, TransactionConfidence confidence, Protos.TransactionConfidence.Builder confidenceBuilder) { synchronized (confidence) { @@ -206,8 +270,12 @@ public class WalletProtobufSerializer { } } if (confidence.getConfidenceType() == ConfidenceType.DEAD) { - Sha256Hash overridingHash = confidence.getOverridingTransaction().getHash(); - confidenceBuilder.setOverridingTransaction(hashToByteString(overridingHash)); + // Copy in the overriding transaction, if available. + // (A dead coinbase transaction has no overriding transaction). + if (confidence.getOverridingTransaction() != null) { + Sha256Hash overridingHash = confidence.getOverridingTransaction().getHash(); + confidenceBuilder.setOverridingTransaction(hashToByteString(overridingHash)); + } } TransactionConfidence.Source source = confidence.getSource(); switch (source) { @@ -219,6 +287,7 @@ public class WalletProtobufSerializer { confidenceBuilder.setSource(Protos.TransactionConfidence.Source.SOURCE_UNKNOWN); break; } } + for (ListIterator it = confidence.getBroadcastBy(); it.hasNext();) { PeerAddress address = it.next(); Protos.PeerAddress proto = Protos.PeerAddress.newBuilder() @@ -231,11 +300,11 @@ public class WalletProtobufSerializer { txBuilder.setConfidence(confidenceBuilder); } - private static ByteString hashToByteString(Sha256Hash hash) { + public static ByteString hashToByteString(Sha256Hash hash) { return ByteString.copyFrom(hash.getBytes()); } - private static Sha256Hash byteStringToHash(ByteString bs) { + public static Sha256Hash byteStringToHash(ByteString bs) { return new Sha256Hash(bs.toByteArray()); } @@ -245,7 +314,6 @@ public class WalletProtobufSerializer { * * If the stream is invalid or the serialized wallet contains unsupported features, * {@link IllegalArgumentException} is thrown. - * */ public Wallet readWallet(InputStream input) throws IOException { // TODO: This method should throw more specific exception types than IllegalArgumentException. @@ -253,24 +321,48 @@ public class WalletProtobufSerializer { // System.out.println(TextFormat.printToString(walletProto)); + // Read the scrypt parameters that specify how encryption and decryption is performed. + KeyCrypter keyCrypter = null; + if (walletProto.hasEncryptionParameters()) { + Protos.ScryptParameters encryptionParameters = walletProto.getEncryptionParameters(); + keyCrypter = new KeyCrypterScrypt(encryptionParameters); + } + NetworkParameters params = NetworkParameters.fromID(walletProto.getNetworkIdentifier()); - Wallet wallet = helper.newWallet(params); - + Wallet wallet = helper.newWallet(params, keyCrypter); + + if (walletProto.hasDescription()) { + wallet.setDescription(walletProto.getDescription()); + } + // Read all keys for (Protos.Key keyProto : walletProto.getKeyList()) { - if (keyProto.getType() != Protos.Key.Type.ORIGINAL) { - throw new IllegalArgumentException("Unknown key type in wallet"); + if (!(keyProto.getType() == Protos.Key.Type.ORIGINAL || keyProto.getType() == Protos.Key.Type.ENCRYPTED_SCRYPT_AES)) { + throw new IllegalArgumentException("Unknown key type in wallet, type = " + keyProto.getType()); } - byte[] privKey = null; - if (keyProto.hasPrivateKey()) { - privKey = keyProto.getPrivateKey().toByteArray(); + + byte[] privKey = keyProto.hasPrivateKey() ? keyProto.getPrivateKey().toByteArray() : null; + EncryptedPrivateKey encryptedPrivateKey = null; + if (keyProto.hasEncryptedPrivateKey()) { + Protos.EncryptedPrivateKey encryptedPrivateKeyProto = keyProto.getEncryptedPrivateKey(); + encryptedPrivateKey = new EncryptedPrivateKey(encryptedPrivateKeyProto.getInitialisationVector().toByteArray(), + encryptedPrivateKeyProto.getEncryptedPrivateKey().toByteArray()); } + byte[] pubKey = keyProto.hasPublicKey() ? keyProto.getPublicKey().toByteArray() : null; - ECKey ecKey = new ECKey(privKey, pubKey); + + ECKey ecKey = null; + if (keyCrypter != null && keyCrypter.getUnderstoodEncryptionType() != EncryptionType.UNENCRYPTED) { + // If the key is encrypted construct an ECKey using the encrypted private key bytes. + ecKey = new ECKey(encryptedPrivateKey, pubKey, keyCrypter); + } else { + // Construct an unencrypted private key. + ecKey = new ECKey(privKey, pubKey); + } ecKey.setCreationTimeSeconds((keyProto.getCreationTimestamp() + 500) / 1000); wallet.addKey(ecKey); } - + // Read all transactions and insert into the txMap. for (Protos.Transaction txProto : walletProto.getTransactionList()) { readTransaction(txProto, params); @@ -281,7 +373,7 @@ public class WalletProtobufSerializer { WalletTransaction wtx = connectTransactionOutputs(txProto); wallet.addWalletTransaction(wtx); } - + // Update the lastBlockSeenHash. if (!walletProto.hasLastSeenBlockHash()) { wallet.setLastBlockSeenHash(null); @@ -297,7 +389,11 @@ public class WalletProtobufSerializer { for (Protos.Extension extProto : walletProto.getExtensionList()) { helper.readExtension(wallet, extProto); } - + + if (walletProto.hasVersion()) { + wallet.setVersion(walletProto.getVersion()); + } + return wallet; } @@ -310,7 +406,7 @@ public class WalletProtobufSerializer { return Protos.Wallet.parseFrom(input); } - private void readTransaction(Protos.Transaction txProto, NetworkParameters params) { + protected void readTransaction(Protos.Transaction txProto, NetworkParameters params) { Transaction tx = new Transaction(params); if (txProto.hasUpdatedAt()) { tx.setUpdateTime(new Date(txProto.getUpdatedAt())); @@ -353,7 +449,7 @@ public class WalletProtobufSerializer { txMap.put(txProto.getHash(), tx); } - private WalletTransaction connectTransactionOutputs(org.bitcoinj.wallet.Protos.Transaction txProto) { + protected WalletTransaction connectTransactionOutputs(org.bitcoinj.wallet.Protos.Transaction txProto) { Transaction tx = txMap.get(txProto.getHash()); WalletTransaction.Pool pool = WalletTransaction.Pool.valueOf(txProto.getPool().getNumber()); for (int i = 0 ; i < tx.getOutputs().size() ; i++) { @@ -376,7 +472,7 @@ public class WalletProtobufSerializer { return new WalletTransaction(pool, tx); } - private void readConfidence(Transaction tx, Protos.TransactionConfidence confidenceProto, + protected void readConfidence(Transaction tx, Protos.TransactionConfidence confidenceProto, TransactionConfidence confidence) { // We are lenient here because tx confidence is not an essential part of the wallet. // If the tx has an unknown type of confidence, ignore. diff --git a/core/src/main/java/org/bitcoinj/wallet/Protos.java b/core/src/main/java/org/bitcoinj/wallet/Protos.java index 78ae82b9..0804fec0 100644 --- a/core/src/main/java/org/bitcoinj/wallet/Protos.java +++ b/core/src/main/java/org/bitcoinj/wallet/Protos.java @@ -487,6 +487,423 @@ public final class Protos { // @@protoc_insertion_point(class_scope:wallet.PeerAddress) } + public interface EncryptedPrivateKeyOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // required bytes initialisation_vector = 1; + boolean hasInitialisationVector(); + com.google.protobuf.ByteString getInitialisationVector(); + + // required bytes encrypted_private_key = 2; + boolean hasEncryptedPrivateKey(); + com.google.protobuf.ByteString getEncryptedPrivateKey(); + } + public static final class EncryptedPrivateKey extends + com.google.protobuf.GeneratedMessage + implements EncryptedPrivateKeyOrBuilder { + // Use EncryptedPrivateKey.newBuilder() to construct. + private EncryptedPrivateKey(Builder builder) { + super(builder); + } + private EncryptedPrivateKey(boolean noInit) {} + + private static final EncryptedPrivateKey defaultInstance; + public static EncryptedPrivateKey getDefaultInstance() { + return defaultInstance; + } + + public EncryptedPrivateKey getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.bitcoinj.wallet.Protos.internal_static_wallet_EncryptedPrivateKey_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.bitcoinj.wallet.Protos.internal_static_wallet_EncryptedPrivateKey_fieldAccessorTable; + } + + private int bitField0_; + // required bytes initialisation_vector = 1; + public static final int INITIALISATION_VECTOR_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString initialisationVector_; + public boolean hasInitialisationVector() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public com.google.protobuf.ByteString getInitialisationVector() { + return initialisationVector_; + } + + // required bytes encrypted_private_key = 2; + public static final int ENCRYPTED_PRIVATE_KEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString encryptedPrivateKey_; + public boolean hasEncryptedPrivateKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getEncryptedPrivateKey() { + return encryptedPrivateKey_; + } + + private void initFields() { + initialisationVector_ = com.google.protobuf.ByteString.EMPTY; + encryptedPrivateKey_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (!hasInitialisationVector()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasEncryptedPrivateKey()) { + memoizedIsInitialized = 0; + return false; + } + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, initialisationVector_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, encryptedPrivateKey_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, initialisationVector_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, encryptedPrivateKey_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.bitcoinj.wallet.Protos.EncryptedPrivateKey parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.bitcoinj.wallet.Protos.EncryptedPrivateKey parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.bitcoinj.wallet.Protos.EncryptedPrivateKey parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.bitcoinj.wallet.Protos.EncryptedPrivateKey parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.bitcoinj.wallet.Protos.EncryptedPrivateKey parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.bitcoinj.wallet.Protos.EncryptedPrivateKey parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.bitcoinj.wallet.Protos.EncryptedPrivateKey parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.bitcoinj.wallet.Protos.EncryptedPrivateKey parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.bitcoinj.wallet.Protos.EncryptedPrivateKey parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.bitcoinj.wallet.Protos.EncryptedPrivateKey parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.bitcoinj.wallet.Protos.EncryptedPrivateKey prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.bitcoinj.wallet.Protos.EncryptedPrivateKeyOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.bitcoinj.wallet.Protos.internal_static_wallet_EncryptedPrivateKey_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.bitcoinj.wallet.Protos.internal_static_wallet_EncryptedPrivateKey_fieldAccessorTable; + } + + // Construct using org.bitcoinj.wallet.Protos.EncryptedPrivateKey.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + initialisationVector_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + encryptedPrivateKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.bitcoinj.wallet.Protos.EncryptedPrivateKey.getDescriptor(); + } + + public org.bitcoinj.wallet.Protos.EncryptedPrivateKey getDefaultInstanceForType() { + return org.bitcoinj.wallet.Protos.EncryptedPrivateKey.getDefaultInstance(); + } + + public org.bitcoinj.wallet.Protos.EncryptedPrivateKey build() { + org.bitcoinj.wallet.Protos.EncryptedPrivateKey result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.bitcoinj.wallet.Protos.EncryptedPrivateKey buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.bitcoinj.wallet.Protos.EncryptedPrivateKey result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.bitcoinj.wallet.Protos.EncryptedPrivateKey buildPartial() { + org.bitcoinj.wallet.Protos.EncryptedPrivateKey result = new org.bitcoinj.wallet.Protos.EncryptedPrivateKey(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.initialisationVector_ = initialisationVector_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.encryptedPrivateKey_ = encryptedPrivateKey_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.bitcoinj.wallet.Protos.EncryptedPrivateKey) { + return mergeFrom((org.bitcoinj.wallet.Protos.EncryptedPrivateKey)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.bitcoinj.wallet.Protos.EncryptedPrivateKey other) { + if (other == org.bitcoinj.wallet.Protos.EncryptedPrivateKey.getDefaultInstance()) return this; + if (other.hasInitialisationVector()) { + setInitialisationVector(other.getInitialisationVector()); + } + if (other.hasEncryptedPrivateKey()) { + setEncryptedPrivateKey(other.getEncryptedPrivateKey()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (!hasInitialisationVector()) { + + return false; + } + if (!hasEncryptedPrivateKey()) { + + return false; + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + initialisationVector_ = input.readBytes(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + encryptedPrivateKey_ = input.readBytes(); + break; + } + } + } + } + + private int bitField0_; + + // required bytes initialisation_vector = 1; + private com.google.protobuf.ByteString initialisationVector_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasInitialisationVector() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public com.google.protobuf.ByteString getInitialisationVector() { + return initialisationVector_; + } + public Builder setInitialisationVector(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + initialisationVector_ = value; + onChanged(); + return this; + } + public Builder clearInitialisationVector() { + bitField0_ = (bitField0_ & ~0x00000001); + initialisationVector_ = getDefaultInstance().getInitialisationVector(); + onChanged(); + return this; + } + + // required bytes encrypted_private_key = 2; + private com.google.protobuf.ByteString encryptedPrivateKey_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasEncryptedPrivateKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public com.google.protobuf.ByteString getEncryptedPrivateKey() { + return encryptedPrivateKey_; + } + public Builder setEncryptedPrivateKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + encryptedPrivateKey_ = value; + onChanged(); + return this; + } + public Builder clearEncryptedPrivateKey() { + bitField0_ = (bitField0_ & ~0x00000002); + encryptedPrivateKey_ = getDefaultInstance().getEncryptedPrivateKey(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:wallet.EncryptedPrivateKey) + } + + static { + defaultInstance = new EncryptedPrivateKey(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:wallet.EncryptedPrivateKey) + } + public interface KeyOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -498,6 +915,11 @@ public final class Protos { boolean hasPrivateKey(); com.google.protobuf.ByteString getPrivateKey(); + // optional .wallet.EncryptedPrivateKey encrypted_private_key = 6; + boolean hasEncryptedPrivateKey(); + org.bitcoinj.wallet.Protos.EncryptedPrivateKey getEncryptedPrivateKey(); + org.bitcoinj.wallet.Protos.EncryptedPrivateKeyOrBuilder getEncryptedPrivateKeyOrBuilder(); + // optional bytes public_key = 3; boolean hasPublicKey(); com.google.protobuf.ByteString getPublicKey(); @@ -541,9 +963,11 @@ public final class Protos { public enum Type implements com.google.protobuf.ProtocolMessageEnum { ORIGINAL(0, 1), + ENCRYPTED_SCRYPT_AES(1, 2), ; public static final int ORIGINAL_VALUE = 1; + public static final int ENCRYPTED_SCRYPT_AES_VALUE = 2; public final int getNumber() { return value; } @@ -551,6 +975,7 @@ public final class Protos { public static Type valueOf(int value) { switch (value) { case 1: return ORIGINAL; + case 2: return ENCRYPTED_SCRYPT_AES; default: return null; } } @@ -581,7 +1006,7 @@ public final class Protos { } private static final Type[] VALUES = { - ORIGINAL, + ORIGINAL, ENCRYPTED_SCRYPT_AES, }; public static Type valueOf( @@ -625,11 +1050,24 @@ public final class Protos { return privateKey_; } + // optional .wallet.EncryptedPrivateKey encrypted_private_key = 6; + public static final int ENCRYPTED_PRIVATE_KEY_FIELD_NUMBER = 6; + private org.bitcoinj.wallet.Protos.EncryptedPrivateKey encryptedPrivateKey_; + public boolean hasEncryptedPrivateKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public org.bitcoinj.wallet.Protos.EncryptedPrivateKey getEncryptedPrivateKey() { + return encryptedPrivateKey_; + } + public org.bitcoinj.wallet.Protos.EncryptedPrivateKeyOrBuilder getEncryptedPrivateKeyOrBuilder() { + return encryptedPrivateKey_; + } + // optional bytes public_key = 3; public static final int PUBLIC_KEY_FIELD_NUMBER = 3; private com.google.protobuf.ByteString publicKey_; public boolean hasPublicKey() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000008) == 0x00000008); } public com.google.protobuf.ByteString getPublicKey() { return publicKey_; @@ -639,7 +1077,7 @@ public final class Protos { public static final int LABEL_FIELD_NUMBER = 4; private java.lang.Object label_; public boolean hasLabel() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } public String getLabel() { java.lang.Object ref = label_; @@ -671,7 +1109,7 @@ public final class Protos { public static final int CREATION_TIMESTAMP_FIELD_NUMBER = 5; private long creationTimestamp_; public boolean hasCreationTimestamp() { - return ((bitField0_ & 0x00000010) == 0x00000010); + return ((bitField0_ & 0x00000020) == 0x00000020); } public long getCreationTimestamp() { return creationTimestamp_; @@ -680,6 +1118,7 @@ public final class Protos { private void initFields() { type_ = org.bitcoinj.wallet.Protos.Key.Type.ORIGINAL; privateKey_ = com.google.protobuf.ByteString.EMPTY; + encryptedPrivateKey_ = org.bitcoinj.wallet.Protos.EncryptedPrivateKey.getDefaultInstance(); publicKey_ = com.google.protobuf.ByteString.EMPTY; label_ = ""; creationTimestamp_ = 0L; @@ -693,6 +1132,12 @@ public final class Protos { memoizedIsInitialized = 0; return false; } + if (hasEncryptedPrivateKey()) { + if (!getEncryptedPrivateKey().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } memoizedIsInitialized = 1; return true; } @@ -706,15 +1151,18 @@ public final class Protos { if (((bitField0_ & 0x00000002) == 0x00000002)) { output.writeBytes(2, privateKey_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (((bitField0_ & 0x00000008) == 0x00000008)) { output.writeBytes(3, publicKey_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (((bitField0_ & 0x00000010) == 0x00000010)) { output.writeBytes(4, getLabelBytes()); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (((bitField0_ & 0x00000020) == 0x00000020)) { output.writeInt64(5, creationTimestamp_); } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeMessage(6, encryptedPrivateKey_); + } getUnknownFields().writeTo(output); } @@ -732,18 +1180,22 @@ public final class Protos { size += com.google.protobuf.CodedOutputStream .computeBytesSize(2, privateKey_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(3, publicKey_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (((bitField0_ & 0x00000010) == 0x00000010)) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(4, getLabelBytes()); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (((bitField0_ & 0x00000020) == 0x00000020)) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(5, creationTimestamp_); } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(6, encryptedPrivateKey_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -860,6 +1312,7 @@ public final class Protos { } private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getEncryptedPrivateKeyFieldBuilder(); } } private static Builder create() { @@ -872,12 +1325,18 @@ public final class Protos { bitField0_ = (bitField0_ & ~0x00000001); privateKey_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000002); - publicKey_ = com.google.protobuf.ByteString.EMPTY; + if (encryptedPrivateKeyBuilder_ == null) { + encryptedPrivateKey_ = org.bitcoinj.wallet.Protos.EncryptedPrivateKey.getDefaultInstance(); + } else { + encryptedPrivateKeyBuilder_.clear(); + } bitField0_ = (bitField0_ & ~0x00000004); - label_ = ""; + publicKey_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000008); - creationTimestamp_ = 0L; + label_ = ""; bitField0_ = (bitField0_ & ~0x00000010); + creationTimestamp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000020); return this; } @@ -927,14 +1386,22 @@ public final class Protos { if (((from_bitField0_ & 0x00000004) == 0x00000004)) { to_bitField0_ |= 0x00000004; } - result.publicKey_ = publicKey_; + if (encryptedPrivateKeyBuilder_ == null) { + result.encryptedPrivateKey_ = encryptedPrivateKey_; + } else { + result.encryptedPrivateKey_ = encryptedPrivateKeyBuilder_.build(); + } if (((from_bitField0_ & 0x00000008) == 0x00000008)) { to_bitField0_ |= 0x00000008; } - result.label_ = label_; + result.publicKey_ = publicKey_; if (((from_bitField0_ & 0x00000010) == 0x00000010)) { to_bitField0_ |= 0x00000010; } + result.label_ = label_; + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000020; + } result.creationTimestamp_ = creationTimestamp_; result.bitField0_ = to_bitField0_; onBuilt(); @@ -958,6 +1425,9 @@ public final class Protos { if (other.hasPrivateKey()) { setPrivateKey(other.getPrivateKey()); } + if (other.hasEncryptedPrivateKey()) { + mergeEncryptedPrivateKey(other.getEncryptedPrivateKey()); + } if (other.hasPublicKey()) { setPublicKey(other.getPublicKey()); } @@ -976,6 +1446,12 @@ public final class Protos { return false; } + if (hasEncryptedPrivateKey()) { + if (!getEncryptedPrivateKey().isInitialized()) { + + return false; + } + } return true; } @@ -1019,20 +1495,29 @@ public final class Protos { break; } case 26: { - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; publicKey_ = input.readBytes(); break; } case 34: { - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; label_ = input.readBytes(); break; } case 40: { - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; creationTimestamp_ = input.readInt64(); break; } + case 50: { + org.bitcoinj.wallet.Protos.EncryptedPrivateKey.Builder subBuilder = org.bitcoinj.wallet.Protos.EncryptedPrivateKey.newBuilder(); + if (hasEncryptedPrivateKey()) { + subBuilder.mergeFrom(getEncryptedPrivateKey()); + } + input.readMessage(subBuilder, extensionRegistry); + setEncryptedPrivateKey(subBuilder.buildPartial()); + break; + } } } } @@ -1087,10 +1572,100 @@ public final class Protos { return this; } + // optional .wallet.EncryptedPrivateKey encrypted_private_key = 6; + private org.bitcoinj.wallet.Protos.EncryptedPrivateKey encryptedPrivateKey_ = org.bitcoinj.wallet.Protos.EncryptedPrivateKey.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.bitcoinj.wallet.Protos.EncryptedPrivateKey, org.bitcoinj.wallet.Protos.EncryptedPrivateKey.Builder, org.bitcoinj.wallet.Protos.EncryptedPrivateKeyOrBuilder> encryptedPrivateKeyBuilder_; + public boolean hasEncryptedPrivateKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public org.bitcoinj.wallet.Protos.EncryptedPrivateKey getEncryptedPrivateKey() { + if (encryptedPrivateKeyBuilder_ == null) { + return encryptedPrivateKey_; + } else { + return encryptedPrivateKeyBuilder_.getMessage(); + } + } + public Builder setEncryptedPrivateKey(org.bitcoinj.wallet.Protos.EncryptedPrivateKey value) { + if (encryptedPrivateKeyBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + encryptedPrivateKey_ = value; + onChanged(); + } else { + encryptedPrivateKeyBuilder_.setMessage(value); + } + bitField0_ |= 0x00000004; + return this; + } + public Builder setEncryptedPrivateKey( + org.bitcoinj.wallet.Protos.EncryptedPrivateKey.Builder builderForValue) { + if (encryptedPrivateKeyBuilder_ == null) { + encryptedPrivateKey_ = builderForValue.build(); + onChanged(); + } else { + encryptedPrivateKeyBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000004; + return this; + } + public Builder mergeEncryptedPrivateKey(org.bitcoinj.wallet.Protos.EncryptedPrivateKey value) { + if (encryptedPrivateKeyBuilder_ == null) { + if (((bitField0_ & 0x00000004) == 0x00000004) && + encryptedPrivateKey_ != org.bitcoinj.wallet.Protos.EncryptedPrivateKey.getDefaultInstance()) { + encryptedPrivateKey_ = + org.bitcoinj.wallet.Protos.EncryptedPrivateKey.newBuilder(encryptedPrivateKey_).mergeFrom(value).buildPartial(); + } else { + encryptedPrivateKey_ = value; + } + onChanged(); + } else { + encryptedPrivateKeyBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000004; + return this; + } + public Builder clearEncryptedPrivateKey() { + if (encryptedPrivateKeyBuilder_ == null) { + encryptedPrivateKey_ = org.bitcoinj.wallet.Protos.EncryptedPrivateKey.getDefaultInstance(); + onChanged(); + } else { + encryptedPrivateKeyBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + public org.bitcoinj.wallet.Protos.EncryptedPrivateKey.Builder getEncryptedPrivateKeyBuilder() { + bitField0_ |= 0x00000004; + onChanged(); + return getEncryptedPrivateKeyFieldBuilder().getBuilder(); + } + public org.bitcoinj.wallet.Protos.EncryptedPrivateKeyOrBuilder getEncryptedPrivateKeyOrBuilder() { + if (encryptedPrivateKeyBuilder_ != null) { + return encryptedPrivateKeyBuilder_.getMessageOrBuilder(); + } else { + return encryptedPrivateKey_; + } + } + private com.google.protobuf.SingleFieldBuilder< + org.bitcoinj.wallet.Protos.EncryptedPrivateKey, org.bitcoinj.wallet.Protos.EncryptedPrivateKey.Builder, org.bitcoinj.wallet.Protos.EncryptedPrivateKeyOrBuilder> + getEncryptedPrivateKeyFieldBuilder() { + if (encryptedPrivateKeyBuilder_ == null) { + encryptedPrivateKeyBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.bitcoinj.wallet.Protos.EncryptedPrivateKey, org.bitcoinj.wallet.Protos.EncryptedPrivateKey.Builder, org.bitcoinj.wallet.Protos.EncryptedPrivateKeyOrBuilder>( + encryptedPrivateKey_, + getParentForChildren(), + isClean()); + encryptedPrivateKey_ = null; + } + return encryptedPrivateKeyBuilder_; + } + // optional bytes public_key = 3; private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; public boolean hasPublicKey() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000008) == 0x00000008); } public com.google.protobuf.ByteString getPublicKey() { return publicKey_; @@ -1099,13 +1674,13 @@ public final class Protos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; publicKey_ = value; onChanged(); return this; } public Builder clearPublicKey() { - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000008); publicKey_ = getDefaultInstance().getPublicKey(); onChanged(); return this; @@ -1114,7 +1689,7 @@ public final class Protos { // optional string label = 4; private java.lang.Object label_ = ""; public boolean hasLabel() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } public String getLabel() { java.lang.Object ref = label_; @@ -1130,19 +1705,19 @@ public final class Protos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; label_ = value; onChanged(); return this; } public Builder clearLabel() { - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000010); label_ = getDefaultInstance().getLabel(); onChanged(); return this; } void setLabel(com.google.protobuf.ByteString value) { - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; label_ = value; onChanged(); } @@ -1150,19 +1725,19 @@ public final class Protos { // optional int64 creation_timestamp = 5; private long creationTimestamp_ ; public boolean hasCreationTimestamp() { - return ((bitField0_ & 0x00000010) == 0x00000010); + return ((bitField0_ & 0x00000020) == 0x00000020); } public long getCreationTimestamp() { return creationTimestamp_; } public Builder setCreationTimestamp(long value) { - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; creationTimestamp_ = value; onChanged(); return this; } public Builder clearCreationTimestamp() { - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000020); creationTimestamp_ = 0L; onChanged(); return this; @@ -4833,6 +5408,526 @@ public final class Protos { // @@protoc_insertion_point(class_scope:wallet.Transaction) } + public interface ScryptParametersOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // required bytes salt = 1; + boolean hasSalt(); + com.google.protobuf.ByteString getSalt(); + + // optional int64 n = 2 [default = 16384]; + boolean hasN(); + long getN(); + + // optional int32 r = 3 [default = 8]; + boolean hasR(); + int getR(); + + // optional int32 p = 4 [default = 1]; + boolean hasP(); + int getP(); + } + public static final class ScryptParameters extends + com.google.protobuf.GeneratedMessage + implements ScryptParametersOrBuilder { + // Use ScryptParameters.newBuilder() to construct. + private ScryptParameters(Builder builder) { + super(builder); + } + private ScryptParameters(boolean noInit) {} + + private static final ScryptParameters defaultInstance; + public static ScryptParameters getDefaultInstance() { + return defaultInstance; + } + + public ScryptParameters getDefaultInstanceForType() { + return defaultInstance; + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.bitcoinj.wallet.Protos.internal_static_wallet_ScryptParameters_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.bitcoinj.wallet.Protos.internal_static_wallet_ScryptParameters_fieldAccessorTable; + } + + private int bitField0_; + // required bytes salt = 1; + public static final int SALT_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString salt_; + public boolean hasSalt() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public com.google.protobuf.ByteString getSalt() { + return salt_; + } + + // optional int64 n = 2 [default = 16384]; + public static final int N_FIELD_NUMBER = 2; + private long n_; + public boolean hasN() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public long getN() { + return n_; + } + + // optional int32 r = 3 [default = 8]; + public static final int R_FIELD_NUMBER = 3; + private int r_; + public boolean hasR() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public int getR() { + return r_; + } + + // optional int32 p = 4 [default = 1]; + public static final int P_FIELD_NUMBER = 4; + private int p_; + public boolean hasP() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public int getP() { + return p_; + } + + private void initFields() { + salt_ = com.google.protobuf.ByteString.EMPTY; + n_ = 16384L; + r_ = 8; + p_ = 1; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (!hasSalt()) { + memoizedIsInitialized = 0; + return false; + } + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, salt_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeInt64(2, n_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeInt32(3, r_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeInt32(4, p_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, salt_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(2, n_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(3, r_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(4, p_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.bitcoinj.wallet.Protos.ScryptParameters parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.bitcoinj.wallet.Protos.ScryptParameters parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.bitcoinj.wallet.Protos.ScryptParameters parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data).buildParsed(); + } + public static org.bitcoinj.wallet.Protos.ScryptParameters parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return newBuilder().mergeFrom(data, extensionRegistry) + .buildParsed(); + } + public static org.bitcoinj.wallet.Protos.ScryptParameters parseFrom(java.io.InputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.bitcoinj.wallet.Protos.ScryptParameters parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + public static org.bitcoinj.wallet.Protos.ScryptParameters parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.bitcoinj.wallet.Protos.ScryptParameters parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Builder builder = newBuilder(); + if (builder.mergeDelimitedFrom(input, extensionRegistry)) { + return builder.buildParsed(); + } else { + return null; + } + } + public static org.bitcoinj.wallet.Protos.ScryptParameters parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return newBuilder().mergeFrom(input).buildParsed(); + } + public static org.bitcoinj.wallet.Protos.ScryptParameters parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return newBuilder().mergeFrom(input, extensionRegistry) + .buildParsed(); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.bitcoinj.wallet.Protos.ScryptParameters prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.bitcoinj.wallet.Protos.ScryptParametersOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.bitcoinj.wallet.Protos.internal_static_wallet_ScryptParameters_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.bitcoinj.wallet.Protos.internal_static_wallet_ScryptParameters_fieldAccessorTable; + } + + // Construct using org.bitcoinj.wallet.Protos.ScryptParameters.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder(BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + salt_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + n_ = 16384L; + bitField0_ = (bitField0_ & ~0x00000002); + r_ = 8; + bitField0_ = (bitField0_ & ~0x00000004); + p_ = 1; + bitField0_ = (bitField0_ & ~0x00000008); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.bitcoinj.wallet.Protos.ScryptParameters.getDescriptor(); + } + + public org.bitcoinj.wallet.Protos.ScryptParameters getDefaultInstanceForType() { + return org.bitcoinj.wallet.Protos.ScryptParameters.getDefaultInstance(); + } + + public org.bitcoinj.wallet.Protos.ScryptParameters build() { + org.bitcoinj.wallet.Protos.ScryptParameters result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + private org.bitcoinj.wallet.Protos.ScryptParameters buildParsed() + throws com.google.protobuf.InvalidProtocolBufferException { + org.bitcoinj.wallet.Protos.ScryptParameters result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException( + result).asInvalidProtocolBufferException(); + } + return result; + } + + public org.bitcoinj.wallet.Protos.ScryptParameters buildPartial() { + org.bitcoinj.wallet.Protos.ScryptParameters result = new org.bitcoinj.wallet.Protos.ScryptParameters(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.salt_ = salt_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.n_ = n_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.r_ = r_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.p_ = p_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.bitcoinj.wallet.Protos.ScryptParameters) { + return mergeFrom((org.bitcoinj.wallet.Protos.ScryptParameters)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.bitcoinj.wallet.Protos.ScryptParameters other) { + if (other == org.bitcoinj.wallet.Protos.ScryptParameters.getDefaultInstance()) return this; + if (other.hasSalt()) { + setSalt(other.getSalt()); + } + if (other.hasN()) { + setN(other.getN()); + } + if (other.hasR()) { + setR(other.getR()); + } + if (other.hasP()) { + setP(other.getP()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (!hasSalt()) { + + return false; + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder( + this.getUnknownFields()); + while (true) { + int tag = input.readTag(); + switch (tag) { + case 0: + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + this.setUnknownFields(unknownFields.build()); + onChanged(); + return this; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + salt_ = input.readBytes(); + break; + } + case 16: { + bitField0_ |= 0x00000002; + n_ = input.readInt64(); + break; + } + case 24: { + bitField0_ |= 0x00000004; + r_ = input.readInt32(); + break; + } + case 32: { + bitField0_ |= 0x00000008; + p_ = input.readInt32(); + break; + } + } + } + } + + private int bitField0_; + + // required bytes salt = 1; + private com.google.protobuf.ByteString salt_ = com.google.protobuf.ByteString.EMPTY; + public boolean hasSalt() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + public com.google.protobuf.ByteString getSalt() { + return salt_; + } + public Builder setSalt(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + salt_ = value; + onChanged(); + return this; + } + public Builder clearSalt() { + bitField0_ = (bitField0_ & ~0x00000001); + salt_ = getDefaultInstance().getSalt(); + onChanged(); + return this; + } + + // optional int64 n = 2 [default = 16384]; + private long n_ = 16384L; + public boolean hasN() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + public long getN() { + return n_; + } + public Builder setN(long value) { + bitField0_ |= 0x00000002; + n_ = value; + onChanged(); + return this; + } + public Builder clearN() { + bitField0_ = (bitField0_ & ~0x00000002); + n_ = 16384L; + onChanged(); + return this; + } + + // optional int32 r = 3 [default = 8]; + private int r_ = 8; + public boolean hasR() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public int getR() { + return r_; + } + public Builder setR(int value) { + bitField0_ |= 0x00000004; + r_ = value; + onChanged(); + return this; + } + public Builder clearR() { + bitField0_ = (bitField0_ & ~0x00000004); + r_ = 8; + onChanged(); + return this; + } + + // optional int32 p = 4 [default = 1]; + private int p_ = 1; + public boolean hasP() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public int getP() { + return p_; + } + public Builder setP(int value) { + bitField0_ |= 0x00000008; + p_ = value; + onChanged(); + return this; + } + public Builder clearP() { + bitField0_ = (bitField0_ & ~0x00000008); + p_ = 1; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:wallet.ScryptParameters) + } + + static { + defaultInstance = new ScryptParameters(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:wallet.ScryptParameters) + } + public interface ExtensionOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -5360,7 +6455,7 @@ public final class Protos { boolean hasLastSeenBlockHash(); com.google.protobuf.ByteString getLastSeenBlockHash(); - // optional uint32 last_seen_block_height = 5; + // optional uint32 last_seen_block_height = 12; boolean hasLastSeenBlockHeight(); int getLastSeenBlockHeight(); @@ -5384,6 +6479,19 @@ public final class Protos { org.bitcoinj.wallet.Protos.TransactionOrBuilder getTransactionOrBuilder( int index); + // optional .wallet.Wallet.EncryptionType encryption_type = 5 [default = UNENCRYPTED]; + boolean hasEncryptionType(); + org.bitcoinj.wallet.Protos.Wallet.EncryptionType getEncryptionType(); + + // optional .wallet.ScryptParameters encryption_parameters = 6; + boolean hasEncryptionParameters(); + org.bitcoinj.wallet.Protos.ScryptParameters getEncryptionParameters(); + org.bitcoinj.wallet.Protos.ScryptParametersOrBuilder getEncryptionParametersOrBuilder(); + + // optional int32 version = 7; + boolean hasVersion(); + int getVersion(); + // repeated .wallet.Extension extension = 10; java.util.List getExtensionList(); @@ -5393,6 +6501,10 @@ public final class Protos { getExtensionOrBuilderList(); org.bitcoinj.wallet.Protos.ExtensionOrBuilder getExtensionOrBuilder( int index); + + // optional string description = 11; + boolean hasDescription(); + String getDescription(); } public static final class Wallet extends com.google.protobuf.GeneratedMessage @@ -5422,6 +6534,75 @@ public final class Protos { return org.bitcoinj.wallet.Protos.internal_static_wallet_Wallet_fieldAccessorTable; } + public enum EncryptionType + implements com.google.protobuf.ProtocolMessageEnum { + UNENCRYPTED(0, 1), + ENCRYPTED_SCRYPT_AES(1, 2), + ; + + public static final int UNENCRYPTED_VALUE = 1; + public static final int ENCRYPTED_SCRYPT_AES_VALUE = 2; + + + public final int getNumber() { return value; } + + public static EncryptionType valueOf(int value) { + switch (value) { + case 1: return UNENCRYPTED; + case 2: return ENCRYPTED_SCRYPT_AES; + default: return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public EncryptionType findValueByNumber(int number) { + return EncryptionType.valueOf(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(index); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return org.bitcoinj.wallet.Protos.Wallet.getDescriptor().getEnumTypes().get(0); + } + + private static final EncryptionType[] VALUES = { + UNENCRYPTED, ENCRYPTED_SCRYPT_AES, + }; + + public static EncryptionType valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } + + private final int index; + private final int value; + + private EncryptionType(int index, int value) { + this.index = index; + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:wallet.Wallet.EncryptionType) + } + private int bitField0_; // required string network_identifier = 1; public static final int NETWORK_IDENTIFIER_FIELD_NUMBER = 1; @@ -5465,8 +6646,8 @@ public final class Protos { return lastSeenBlockHash_; } - // optional uint32 last_seen_block_height = 5; - public static final int LAST_SEEN_BLOCK_HEIGHT_FIELD_NUMBER = 5; + // optional uint32 last_seen_block_height = 12; + public static final int LAST_SEEN_BLOCK_HEIGHT_FIELD_NUMBER = 12; private int lastSeenBlockHeight_; public boolean hasLastSeenBlockHeight() { return ((bitField0_ & 0x00000004) == 0x00000004); @@ -5517,6 +6698,39 @@ public final class Protos { return transaction_.get(index); } + // optional .wallet.Wallet.EncryptionType encryption_type = 5 [default = UNENCRYPTED]; + public static final int ENCRYPTION_TYPE_FIELD_NUMBER = 5; + private org.bitcoinj.wallet.Protos.Wallet.EncryptionType encryptionType_; + public boolean hasEncryptionType() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + public org.bitcoinj.wallet.Protos.Wallet.EncryptionType getEncryptionType() { + return encryptionType_; + } + + // optional .wallet.ScryptParameters encryption_parameters = 6; + public static final int ENCRYPTION_PARAMETERS_FIELD_NUMBER = 6; + private org.bitcoinj.wallet.Protos.ScryptParameters encryptionParameters_; + public boolean hasEncryptionParameters() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + public org.bitcoinj.wallet.Protos.ScryptParameters getEncryptionParameters() { + return encryptionParameters_; + } + public org.bitcoinj.wallet.Protos.ScryptParametersOrBuilder getEncryptionParametersOrBuilder() { + return encryptionParameters_; + } + + // optional int32 version = 7; + public static final int VERSION_FIELD_NUMBER = 7; + private int version_; + public boolean hasVersion() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + public int getVersion() { + return version_; + } + // repeated .wallet.Extension extension = 10; public static final int EXTENSION_FIELD_NUMBER = 10; private java.util.List extension_; @@ -5538,13 +6752,49 @@ public final class Protos { return extension_.get(index); } + // optional string description = 11; + public static final int DESCRIPTION_FIELD_NUMBER = 11; + private java.lang.Object description_; + public boolean hasDescription() { + return ((bitField0_ & 0x00000040) == 0x00000040); + } + public String getDescription() { + java.lang.Object ref = description_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (com.google.protobuf.Internal.isValidUtf8(bs)) { + description_ = s; + } + return s; + } + } + private com.google.protobuf.ByteString getDescriptionBytes() { + java.lang.Object ref = description_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8((String) ref); + description_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + private void initFields() { networkIdentifier_ = ""; lastSeenBlockHash_ = com.google.protobuf.ByteString.EMPTY; lastSeenBlockHeight_ = 0; key_ = java.util.Collections.emptyList(); transaction_ = java.util.Collections.emptyList(); + encryptionType_ = org.bitcoinj.wallet.Protos.Wallet.EncryptionType.UNENCRYPTED; + encryptionParameters_ = org.bitcoinj.wallet.Protos.ScryptParameters.getDefaultInstance(); + version_ = 0; extension_ = java.util.Collections.emptyList(); + description_ = ""; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -5567,6 +6817,12 @@ public final class Protos { return false; } } + if (hasEncryptionParameters()) { + if (!getEncryptionParameters().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } for (int i = 0; i < getExtensionCount(); i++) { if (!getExtension(i).isInitialized()) { memoizedIsInitialized = 0; @@ -5592,12 +6848,24 @@ public final class Protos { for (int i = 0; i < transaction_.size(); i++) { output.writeMessage(4, transaction_.get(i)); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeUInt32(5, lastSeenBlockHeight_); + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeEnum(5, encryptionType_.getNumber()); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeMessage(6, encryptionParameters_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeInt32(7, version_); } for (int i = 0; i < extension_.size(); i++) { output.writeMessage(10, extension_.get(i)); } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + output.writeBytes(11, getDescriptionBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeUInt32(12, lastSeenBlockHeight_); + } getUnknownFields().writeTo(output); } @@ -5623,14 +6891,30 @@ public final class Protos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(4, transaction_.get(i)); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(5, lastSeenBlockHeight_); + .computeEnumSize(5, encryptionType_.getNumber()); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(6, encryptionParameters_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(7, version_); } for (int i = 0; i < extension_.size(); i++) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(10, extension_.get(i)); } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(11, getDescriptionBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(12, lastSeenBlockHeight_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -5749,6 +7033,7 @@ public final class Protos { if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { getKeyFieldBuilder(); getTransactionFieldBuilder(); + getEncryptionParametersFieldBuilder(); getExtensionFieldBuilder(); } } @@ -5776,12 +7061,24 @@ public final class Protos { } else { transactionBuilder_.clear(); } + encryptionType_ = org.bitcoinj.wallet.Protos.Wallet.EncryptionType.UNENCRYPTED; + bitField0_ = (bitField0_ & ~0x00000020); + if (encryptionParametersBuilder_ == null) { + encryptionParameters_ = org.bitcoinj.wallet.Protos.ScryptParameters.getDefaultInstance(); + } else { + encryptionParametersBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000040); + version_ = 0; + bitField0_ = (bitField0_ & ~0x00000080); if (extensionBuilder_ == null) { extension_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000100); } else { extensionBuilder_.clear(); } + description_ = ""; + bitField0_ = (bitField0_ & ~0x00000200); return this; } @@ -5850,15 +7147,35 @@ public final class Protos { } else { result.transaction_ = transactionBuilder_.build(); } + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000008; + } + result.encryptionType_ = encryptionType_; + if (((from_bitField0_ & 0x00000040) == 0x00000040)) { + to_bitField0_ |= 0x00000010; + } + if (encryptionParametersBuilder_ == null) { + result.encryptionParameters_ = encryptionParameters_; + } else { + result.encryptionParameters_ = encryptionParametersBuilder_.build(); + } + if (((from_bitField0_ & 0x00000080) == 0x00000080)) { + to_bitField0_ |= 0x00000020; + } + result.version_ = version_; if (extensionBuilder_ == null) { - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (((bitField0_ & 0x00000100) == 0x00000100)) { extension_ = java.util.Collections.unmodifiableList(extension_); - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000100); } result.extension_ = extension_; } else { result.extension_ = extensionBuilder_.build(); } + if (((from_bitField0_ & 0x00000200) == 0x00000200)) { + to_bitField0_ |= 0x00000040; + } + result.description_ = description_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -5936,11 +7253,20 @@ public final class Protos { } } } + if (other.hasEncryptionType()) { + setEncryptionType(other.getEncryptionType()); + } + if (other.hasEncryptionParameters()) { + mergeEncryptionParameters(other.getEncryptionParameters()); + } + if (other.hasVersion()) { + setVersion(other.getVersion()); + } if (extensionBuilder_ == null) { if (!other.extension_.isEmpty()) { if (extension_.isEmpty()) { extension_ = other.extension_; - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000100); } else { ensureExtensionIsMutable(); extension_.addAll(other.extension_); @@ -5953,7 +7279,7 @@ public final class Protos { extensionBuilder_.dispose(); extensionBuilder_ = null; extension_ = other.extension_; - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000100); extensionBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getExtensionFieldBuilder() : null; @@ -5962,6 +7288,9 @@ public final class Protos { } } } + if (other.hasDescription()) { + setDescription(other.getDescription()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -5983,6 +7312,12 @@ public final class Protos { return false; } } + if (hasEncryptionParameters()) { + if (!getEncryptionParameters().isInitialized()) { + + return false; + } + } for (int i = 0; i < getExtensionCount(); i++) { if (!getExtension(i).isInitialized()) { @@ -6038,8 +7373,28 @@ public final class Protos { break; } case 40: { - bitField0_ |= 0x00000004; - lastSeenBlockHeight_ = input.readUInt32(); + int rawValue = input.readEnum(); + org.bitcoinj.wallet.Protos.Wallet.EncryptionType value = org.bitcoinj.wallet.Protos.Wallet.EncryptionType.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(5, rawValue); + } else { + bitField0_ |= 0x00000020; + encryptionType_ = value; + } + break; + } + case 50: { + org.bitcoinj.wallet.Protos.ScryptParameters.Builder subBuilder = org.bitcoinj.wallet.Protos.ScryptParameters.newBuilder(); + if (hasEncryptionParameters()) { + subBuilder.mergeFrom(getEncryptionParameters()); + } + input.readMessage(subBuilder, extensionRegistry); + setEncryptionParameters(subBuilder.buildPartial()); + break; + } + case 56: { + bitField0_ |= 0x00000080; + version_ = input.readInt32(); break; } case 82: { @@ -6048,6 +7403,16 @@ public final class Protos { addExtension(subBuilder.buildPartial()); break; } + case 90: { + bitField0_ |= 0x00000200; + description_ = input.readBytes(); + break; + } + case 96: { + bitField0_ |= 0x00000004; + lastSeenBlockHeight_ = input.readUInt32(); + break; + } } } } @@ -6114,7 +7479,7 @@ public final class Protos { return this; } - // optional uint32 last_seen_block_height = 5; + // optional uint32 last_seen_block_height = 12; private int lastSeenBlockHeight_ ; public boolean hasLastSeenBlockHeight() { return ((bitField0_ & 0x00000004) == 0x00000004); @@ -6507,13 +7872,148 @@ public final class Protos { return transactionBuilder_; } + // optional .wallet.Wallet.EncryptionType encryption_type = 5 [default = UNENCRYPTED]; + private org.bitcoinj.wallet.Protos.Wallet.EncryptionType encryptionType_ = org.bitcoinj.wallet.Protos.Wallet.EncryptionType.UNENCRYPTED; + public boolean hasEncryptionType() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + public org.bitcoinj.wallet.Protos.Wallet.EncryptionType getEncryptionType() { + return encryptionType_; + } + public Builder setEncryptionType(org.bitcoinj.wallet.Protos.Wallet.EncryptionType value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000020; + encryptionType_ = value; + onChanged(); + return this; + } + public Builder clearEncryptionType() { + bitField0_ = (bitField0_ & ~0x00000020); + encryptionType_ = org.bitcoinj.wallet.Protos.Wallet.EncryptionType.UNENCRYPTED; + onChanged(); + return this; + } + + // optional .wallet.ScryptParameters encryption_parameters = 6; + private org.bitcoinj.wallet.Protos.ScryptParameters encryptionParameters_ = org.bitcoinj.wallet.Protos.ScryptParameters.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.bitcoinj.wallet.Protos.ScryptParameters, org.bitcoinj.wallet.Protos.ScryptParameters.Builder, org.bitcoinj.wallet.Protos.ScryptParametersOrBuilder> encryptionParametersBuilder_; + public boolean hasEncryptionParameters() { + return ((bitField0_ & 0x00000040) == 0x00000040); + } + public org.bitcoinj.wallet.Protos.ScryptParameters getEncryptionParameters() { + if (encryptionParametersBuilder_ == null) { + return encryptionParameters_; + } else { + return encryptionParametersBuilder_.getMessage(); + } + } + public Builder setEncryptionParameters(org.bitcoinj.wallet.Protos.ScryptParameters value) { + if (encryptionParametersBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + encryptionParameters_ = value; + onChanged(); + } else { + encryptionParametersBuilder_.setMessage(value); + } + bitField0_ |= 0x00000040; + return this; + } + public Builder setEncryptionParameters( + org.bitcoinj.wallet.Protos.ScryptParameters.Builder builderForValue) { + if (encryptionParametersBuilder_ == null) { + encryptionParameters_ = builderForValue.build(); + onChanged(); + } else { + encryptionParametersBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000040; + return this; + } + public Builder mergeEncryptionParameters(org.bitcoinj.wallet.Protos.ScryptParameters value) { + if (encryptionParametersBuilder_ == null) { + if (((bitField0_ & 0x00000040) == 0x00000040) && + encryptionParameters_ != org.bitcoinj.wallet.Protos.ScryptParameters.getDefaultInstance()) { + encryptionParameters_ = + org.bitcoinj.wallet.Protos.ScryptParameters.newBuilder(encryptionParameters_).mergeFrom(value).buildPartial(); + } else { + encryptionParameters_ = value; + } + onChanged(); + } else { + encryptionParametersBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000040; + return this; + } + public Builder clearEncryptionParameters() { + if (encryptionParametersBuilder_ == null) { + encryptionParameters_ = org.bitcoinj.wallet.Protos.ScryptParameters.getDefaultInstance(); + onChanged(); + } else { + encryptionParametersBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000040); + return this; + } + public org.bitcoinj.wallet.Protos.ScryptParameters.Builder getEncryptionParametersBuilder() { + bitField0_ |= 0x00000040; + onChanged(); + return getEncryptionParametersFieldBuilder().getBuilder(); + } + public org.bitcoinj.wallet.Protos.ScryptParametersOrBuilder getEncryptionParametersOrBuilder() { + if (encryptionParametersBuilder_ != null) { + return encryptionParametersBuilder_.getMessageOrBuilder(); + } else { + return encryptionParameters_; + } + } + private com.google.protobuf.SingleFieldBuilder< + org.bitcoinj.wallet.Protos.ScryptParameters, org.bitcoinj.wallet.Protos.ScryptParameters.Builder, org.bitcoinj.wallet.Protos.ScryptParametersOrBuilder> + getEncryptionParametersFieldBuilder() { + if (encryptionParametersBuilder_ == null) { + encryptionParametersBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.bitcoinj.wallet.Protos.ScryptParameters, org.bitcoinj.wallet.Protos.ScryptParameters.Builder, org.bitcoinj.wallet.Protos.ScryptParametersOrBuilder>( + encryptionParameters_, + getParentForChildren(), + isClean()); + encryptionParameters_ = null; + } + return encryptionParametersBuilder_; + } + + // optional int32 version = 7; + private int version_ ; + public boolean hasVersion() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + public int getVersion() { + return version_; + } + public Builder setVersion(int value) { + bitField0_ |= 0x00000080; + version_ = value; + onChanged(); + return this; + } + public Builder clearVersion() { + bitField0_ = (bitField0_ & ~0x00000080); + version_ = 0; + onChanged(); + return this; + } + // repeated .wallet.Extension extension = 10; private java.util.List extension_ = java.util.Collections.emptyList(); private void ensureExtensionIsMutable() { - if (!((bitField0_ & 0x00000020) == 0x00000020)) { + if (!((bitField0_ & 0x00000100) == 0x00000100)) { extension_ = new java.util.ArrayList(extension_); - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000100; } } @@ -6629,7 +8129,7 @@ public final class Protos { public Builder clearExtension() { if (extensionBuilder_ == null) { extension_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000100); onChanged(); } else { extensionBuilder_.clear(); @@ -6685,7 +8185,7 @@ public final class Protos { extensionBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< org.bitcoinj.wallet.Protos.Extension, org.bitcoinj.wallet.Protos.Extension.Builder, org.bitcoinj.wallet.Protos.ExtensionOrBuilder>( extension_, - ((bitField0_ & 0x00000020) == 0x00000020), + ((bitField0_ & 0x00000100) == 0x00000100), getParentForChildren(), isClean()); extension_ = null; @@ -6693,6 +8193,42 @@ public final class Protos { return extensionBuilder_; } + // optional string description = 11; + private java.lang.Object description_ = ""; + public boolean hasDescription() { + return ((bitField0_ & 0x00000200) == 0x00000200); + } + public String getDescription() { + java.lang.Object ref = description_; + if (!(ref instanceof String)) { + String s = ((com.google.protobuf.ByteString) ref).toStringUtf8(); + description_ = s; + return s; + } else { + return (String) ref; + } + } + public Builder setDescription(String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000200; + description_ = value; + onChanged(); + return this; + } + public Builder clearDescription() { + bitField0_ = (bitField0_ & ~0x00000200); + description_ = getDefaultInstance().getDescription(); + onChanged(); + return this; + } + void setDescription(com.google.protobuf.ByteString value) { + bitField0_ |= 0x00000200; + description_ = value; + onChanged(); + } + // @@protoc_insertion_point(builder_scope:wallet.Wallet) } @@ -6709,6 +8245,11 @@ public final class Protos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_wallet_PeerAddress_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_wallet_EncryptedPrivateKey_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_wallet_EncryptedPrivateKey_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_wallet_Key_descriptor; private static @@ -6734,6 +8275,11 @@ public final class Protos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_wallet_Transaction_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_wallet_ScryptParameters_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_wallet_ScryptParameters_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_wallet_Extension_descriptor; private static @@ -6755,45 +8301,57 @@ public final class Protos { java.lang.String[] descriptorData = { "\n\rbitcoin.proto\022\006wallet\"A\n\013PeerAddress\022\022" + "\n\nip_address\030\001 \002(\014\022\014\n\004port\030\002 \002(\r\022\020\n\010serv" + - "ices\030\003 \002(\004\"\217\001\n\003Key\022\036\n\004type\030\001 \002(\0162\020.walle" + - "t.Key.Type\022\023\n\013private_key\030\002 \001(\014\022\022\n\npubli" + - "c_key\030\003 \001(\014\022\r\n\005label\030\004 \001(\t\022\032\n\022creation_t" + - "imestamp\030\005 \001(\003\"\024\n\004Type\022\014\n\010ORIGINAL\020\001\"\203\001\n" + - "\020TransactionInput\022\"\n\032transaction_out_poi" + - "nt_hash\030\001 \002(\014\022#\n\033transaction_out_point_i" + - "ndex\030\002 \002(\005\022\024\n\014script_bytes\030\003 \002(\014\022\020\n\010sequ" + - "ence\030\004 \001(\r\"\177\n\021TransactionOutput\022\r\n\005value", - "\030\001 \002(\003\022\024\n\014script_bytes\030\002 \002(\014\022!\n\031spent_by" + - "_transaction_hash\030\003 \001(\014\022\"\n\032spent_by_tran" + - "saction_index\030\004 \001(\005\"\246\003\n\025TransactionConfi" + - "dence\0220\n\004type\030\001 \001(\0162\".wallet.Transaction" + - "Confidence.Type\022\032\n\022appeared_at_height\030\002 " + - "\001(\005\022\036\n\026overriding_transaction\030\003 \001(\014\022\r\n\005d" + - "epth\030\004 \001(\005\022\021\n\twork_done\030\005 \001(\003\022)\n\014broadca" + - "st_by\030\006 \003(\0132\023.wallet.PeerAddress\0224\n\006sour" + - "ce\030\007 \001(\0162$.wallet.TransactionConfidence." + - "Source\"Y\n\004Type\022\013\n\007UNKNOWN\020\000\022\014\n\010BUILDING\020", - "\001\022\025\n\021NOT_SEEN_IN_CHAIN\020\002\022\025\n\021NOT_IN_BEST_" + - "CHAIN\020\003\022\010\n\004DEAD\020\004\"A\n\006Source\022\022\n\016SOURCE_UN" + - "KNOWN\020\000\022\022\n\016SOURCE_NETWORK\020\001\022\017\n\013SOURCE_SE" + - "LF\020\002\"\211\003\n\013Transaction\022\017\n\007version\030\001 \002(\005\022\014\n" + - "\004hash\030\002 \002(\014\022&\n\004pool\030\003 \001(\0162\030.wallet.Trans" + - "action.Pool\022\021\n\tlock_time\030\004 \001(\r\022\022\n\nupdate" + - "d_at\030\005 \001(\003\0223\n\021transaction_input\030\006 \003(\0132\030." + - "wallet.TransactionInput\0225\n\022transaction_o" + - "utput\030\007 \003(\0132\031.wallet.TransactionOutput\022\022" + - "\n\nblock_hash\030\010 \003(\014\0221\n\nconfidence\030\t \001(\0132\035", - ".wallet.TransactionConfidence\"Y\n\004Pool\022\013\n" + - "\007UNSPENT\020\004\022\t\n\005SPENT\020\005\022\014\n\010INACTIVE\020\002\022\010\n\004D" + - "EAD\020\n\022\013\n\007PENDING\020\020\022\024\n\020PENDING_INACTIVE\020\022" + - "\"8\n\tExtension\022\n\n\002id\030\001 \002(\t\022\014\n\004data\030\002 \002(\014\022" + - "\021\n\tmandatory\030\003 \002(\010\"\314\001\n\006Wallet\022\032\n\022network" + - "_identifier\030\001 \002(\t\022\034\n\024last_seen_block_has" + - "h\030\002 \001(\014\022\036\n\026last_seen_block_height\030\005 \001(\r\022" + - "\030\n\003key\030\003 \003(\0132\013.wallet.Key\022(\n\013transaction" + - "\030\004 \003(\0132\023.wallet.Transaction\022$\n\textension" + - "\030\n \003(\0132\021.wallet.ExtensionB\035\n\023org.bitcoin", - "j.walletB\006Protos" + "ices\030\003 \002(\004\"S\n\023EncryptedPrivateKey\022\035\n\025ini" + + "tialisation_vector\030\001 \002(\014\022\035\n\025encrypted_pr" + + "ivate_key\030\002 \002(\014\"\345\001\n\003Key\022\036\n\004type\030\001 \002(\0162\020." + + "wallet.Key.Type\022\023\n\013private_key\030\002 \001(\014\022:\n\025" + + "encrypted_private_key\030\006 \001(\0132\033.wallet.Enc" + + "ryptedPrivateKey\022\022\n\npublic_key\030\003 \001(\014\022\r\n\005" + + "label\030\004 \001(\t\022\032\n\022creation_timestamp\030\005 \001(\003\"" + + ".\n\004Type\022\014\n\010ORIGINAL\020\001\022\030\n\024ENCRYPTED_SCRYP", + "T_AES\020\002\"\203\001\n\020TransactionInput\022\"\n\032transact" + + "ion_out_point_hash\030\001 \002(\014\022#\n\033transaction_" + + "out_point_index\030\002 \002(\005\022\024\n\014script_bytes\030\003 " + + "\002(\014\022\020\n\010sequence\030\004 \001(\r\"\177\n\021TransactionOutp" + + "ut\022\r\n\005value\030\001 \002(\003\022\024\n\014script_bytes\030\002 \002(\014\022" + + "!\n\031spent_by_transaction_hash\030\003 \001(\014\022\"\n\032sp" + + "ent_by_transaction_index\030\004 \001(\005\"\246\003\n\025Trans" + + "actionConfidence\0220\n\004type\030\001 \001(\0162\".wallet." + + "TransactionConfidence.Type\022\032\n\022appeared_a" + + "t_height\030\002 \001(\005\022\036\n\026overriding_transaction", + "\030\003 \001(\014\022\r\n\005depth\030\004 \001(\005\022\021\n\twork_done\030\005 \001(\003" + + "\022)\n\014broadcast_by\030\006 \003(\0132\023.wallet.PeerAddr" + + "ess\0224\n\006source\030\007 \001(\0162$.wallet.Transaction" + + "Confidence.Source\"Y\n\004Type\022\013\n\007UNKNOWN\020\000\022\014" + + "\n\010BUILDING\020\001\022\025\n\021NOT_SEEN_IN_CHAIN\020\002\022\025\n\021N" + + "OT_IN_BEST_CHAIN\020\003\022\010\n\004DEAD\020\004\"A\n\006Source\022\022" + + "\n\016SOURCE_UNKNOWN\020\000\022\022\n\016SOURCE_NETWORK\020\001\022\017" + + "\n\013SOURCE_SELF\020\002\"\211\003\n\013Transaction\022\017\n\007versi" + + "on\030\001 \002(\005\022\014\n\004hash\030\002 \002(\014\022&\n\004pool\030\003 \001(\0162\030.w" + + "allet.Transaction.Pool\022\021\n\tlock_time\030\004 \001(", + "\r\022\022\n\nupdated_at\030\005 \001(\003\0223\n\021transaction_inp" + + "ut\030\006 \003(\0132\030.wallet.TransactionInput\0225\n\022tr" + + "ansaction_output\030\007 \003(\0132\031.wallet.Transact" + + "ionOutput\022\022\n\nblock_hash\030\010 \003(\014\0221\n\nconfide" + + "nce\030\t \001(\0132\035.wallet.TransactionConfidence" + + "\"Y\n\004Pool\022\013\n\007UNSPENT\020\004\022\t\n\005SPENT\020\005\022\014\n\010INAC" + + "TIVE\020\002\022\010\n\004DEAD\020\n\022\013\n\007PENDING\020\020\022\024\n\020PENDING" + + "_INACTIVE\020\022\"N\n\020ScryptParameters\022\014\n\004salt\030" + + "\001 \002(\014\022\020\n\001n\030\002 \001(\003:\00516384\022\014\n\001r\030\003 \001(\005:\0018\022\014\n" + + "\001p\030\004 \001(\005:\0011\"8\n\tExtension\022\n\n\002id\030\001 \002(\t\022\014\n\004", + "data\030\002 \002(\014\022\021\n\tmandatory\030\003 \002(\010\"\255\003\n\006Wallet" + + "\022\032\n\022network_identifier\030\001 \002(\t\022\034\n\024last_see" + + "n_block_hash\030\002 \001(\014\022\036\n\026last_seen_block_he" + + "ight\030\014 \001(\r\022\030\n\003key\030\003 \003(\0132\013.wallet.Key\022(\n\013" + + "transaction\030\004 \003(\0132\023.wallet.Transaction\022C" + + "\n\017encryption_type\030\005 \001(\0162\035.wallet.Wallet." + + "EncryptionType:\013UNENCRYPTED\0227\n\025encryptio" + + "n_parameters\030\006 \001(\0132\030.wallet.ScryptParame" + + "ters\022\017\n\007version\030\007 \001(\005\022$\n\textension\030\n \003(\013" + + "2\021.wallet.Extension\022\023\n\013description\030\013 \001(\t", + "\";\n\016EncryptionType\022\017\n\013UNENCRYPTED\020\001\022\030\n\024E" + + "NCRYPTED_SCRYPT_AES\020\002B\035\n\023org.bitcoinj.wa" + + "lletB\006Protos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -6808,16 +8366,24 @@ public final class Protos { new java.lang.String[] { "IpAddress", "Port", "Services", }, org.bitcoinj.wallet.Protos.PeerAddress.class, org.bitcoinj.wallet.Protos.PeerAddress.Builder.class); - internal_static_wallet_Key_descriptor = + internal_static_wallet_EncryptedPrivateKey_descriptor = getDescriptor().getMessageTypes().get(1); + internal_static_wallet_EncryptedPrivateKey_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_wallet_EncryptedPrivateKey_descriptor, + new java.lang.String[] { "InitialisationVector", "EncryptedPrivateKey", }, + org.bitcoinj.wallet.Protos.EncryptedPrivateKey.class, + org.bitcoinj.wallet.Protos.EncryptedPrivateKey.Builder.class); + internal_static_wallet_Key_descriptor = + getDescriptor().getMessageTypes().get(2); internal_static_wallet_Key_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_wallet_Key_descriptor, - new java.lang.String[] { "Type", "PrivateKey", "PublicKey", "Label", "CreationTimestamp", }, + new java.lang.String[] { "Type", "PrivateKey", "EncryptedPrivateKey", "PublicKey", "Label", "CreationTimestamp", }, org.bitcoinj.wallet.Protos.Key.class, org.bitcoinj.wallet.Protos.Key.Builder.class); internal_static_wallet_TransactionInput_descriptor = - getDescriptor().getMessageTypes().get(2); + getDescriptor().getMessageTypes().get(3); internal_static_wallet_TransactionInput_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_wallet_TransactionInput_descriptor, @@ -6825,7 +8391,7 @@ public final class Protos { org.bitcoinj.wallet.Protos.TransactionInput.class, org.bitcoinj.wallet.Protos.TransactionInput.Builder.class); internal_static_wallet_TransactionOutput_descriptor = - getDescriptor().getMessageTypes().get(3); + getDescriptor().getMessageTypes().get(4); internal_static_wallet_TransactionOutput_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_wallet_TransactionOutput_descriptor, @@ -6833,7 +8399,7 @@ public final class Protos { org.bitcoinj.wallet.Protos.TransactionOutput.class, org.bitcoinj.wallet.Protos.TransactionOutput.Builder.class); internal_static_wallet_TransactionConfidence_descriptor = - getDescriptor().getMessageTypes().get(4); + getDescriptor().getMessageTypes().get(5); internal_static_wallet_TransactionConfidence_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_wallet_TransactionConfidence_descriptor, @@ -6841,15 +8407,23 @@ public final class Protos { org.bitcoinj.wallet.Protos.TransactionConfidence.class, org.bitcoinj.wallet.Protos.TransactionConfidence.Builder.class); internal_static_wallet_Transaction_descriptor = - getDescriptor().getMessageTypes().get(5); + getDescriptor().getMessageTypes().get(6); internal_static_wallet_Transaction_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_wallet_Transaction_descriptor, new java.lang.String[] { "Version", "Hash", "Pool", "LockTime", "UpdatedAt", "TransactionInput", "TransactionOutput", "BlockHash", "Confidence", }, org.bitcoinj.wallet.Protos.Transaction.class, org.bitcoinj.wallet.Protos.Transaction.Builder.class); + internal_static_wallet_ScryptParameters_descriptor = + getDescriptor().getMessageTypes().get(7); + internal_static_wallet_ScryptParameters_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_wallet_ScryptParameters_descriptor, + new java.lang.String[] { "Salt", "N", "R", "P", }, + org.bitcoinj.wallet.Protos.ScryptParameters.class, + org.bitcoinj.wallet.Protos.ScryptParameters.Builder.class); internal_static_wallet_Extension_descriptor = - getDescriptor().getMessageTypes().get(6); + getDescriptor().getMessageTypes().get(8); internal_static_wallet_Extension_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_wallet_Extension_descriptor, @@ -6857,11 +8431,11 @@ public final class Protos { org.bitcoinj.wallet.Protos.Extension.class, org.bitcoinj.wallet.Protos.Extension.Builder.class); internal_static_wallet_Wallet_descriptor = - getDescriptor().getMessageTypes().get(7); + getDescriptor().getMessageTypes().get(9); internal_static_wallet_Wallet_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_wallet_Wallet_descriptor, - new java.lang.String[] { "NetworkIdentifier", "LastSeenBlockHash", "LastSeenBlockHeight", "Key", "Transaction", "Extension", }, + new java.lang.String[] { "NetworkIdentifier", "LastSeenBlockHash", "LastSeenBlockHeight", "Key", "Transaction", "EncryptionType", "EncryptionParameters", "Version", "Extension", "Description", }, org.bitcoinj.wallet.Protos.Wallet.class, org.bitcoinj.wallet.Protos.Wallet.Builder.class); return null; diff --git a/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java b/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java index 7c8f89dc..4c61713b 100644 --- a/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java +++ b/core/src/test/java/com/google/bitcoin/core/ECKeyTest.java @@ -16,19 +16,57 @@ package com.google.bitcoin.core; -import org.junit.Test; -import org.spongycastle.util.encoders.Hex; +import static com.google.bitcoin.core.Utils.reverseBytes; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.math.BigInteger; +import java.security.SecureRandom; import java.security.SignatureException; import java.util.Arrays; -import static com.google.bitcoin.core.Utils.reverseBytes; -import static org.junit.Assert.*; +import org.bitcoinj.wallet.Protos; +import org.bitcoinj.wallet.Protos.ScryptParameters; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.crypto.params.KeyParameter; +import org.spongycastle.util.encoders.Hex; + +import com.google.bitcoin.crypto.EncryptedPrivateKey; +import com.google.bitcoin.crypto.KeyCrypter; +import com.google.bitcoin.crypto.KeyCrypterScrypt; +import com.google.bitcoin.utils.BriefLogFormatter; +import com.google.protobuf.ByteString; public class ECKeyTest { + public Logger log = LoggerFactory.getLogger(ECKeyTest.class.getName()); + + private SecureRandom secureRandom; + + private KeyCrypter keyCrypter; + + private static CharSequence PASSWORD1 = "my hovercraft has eels"; + private static CharSequence WRONG_PASSWORD = "it is a snowy day today"; + + @Before + public void setUp() throws Exception { + secureRandom = new SecureRandom(); + + byte[] salt = new byte[KeyCrypterScrypt.SALT_LENGTH]; + secureRandom.nextBytes(salt); + Protos.ScryptParameters.Builder scryptParametersBuilder = Protos.ScryptParameters.newBuilder().setSalt(ByteString.copyFrom(salt)); + ScryptParameters scryptParameters = scryptParametersBuilder.build(); + keyCrypter = new KeyCrypterScrypt(scryptParameters); + + BriefLogFormatter.init(); + } + @Test - public void testSignatures() { + public void testSignatures() throws Exception { // Test that we can construct an ECKey from a private key (deriving the public from the private), then signing // a message with it. BigInteger privkey = new BigInteger(1, Hex.decode("180cb41c7c600be951b5d3d0a7334acc7506173875834f7a6c4c786a28fcbb19")); @@ -43,7 +81,7 @@ public class ECKeyTest { } @Test - public void testASN1Roundtrip() { + public void testASN1Roundtrip() throws Exception { byte[] privkeyASN1 = Hex.decode( "3082011302010104205c0b98e524ad188ddef35dc6abba13c34a351a05409e5d285403718b93336a4aa081a53081a2020101302c06072a8648ce3d0101022100fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f300604010004010704410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141020101a144034200042af7a2aafe8dafd7dc7f9cfb58ce09bda7dce28653ab229b98d1d3d759660c672dd0db18c8c2d76aa470448e876fc2089ab1354c01a6e72cefc50915f4a963ee"); ECKey decodedKey = ECKey.fromASN1(privkeyASN1); @@ -73,7 +111,7 @@ public class ECKeyTest { } @Test - public void testKeyPairRoundtrip() { + public void testKeyPairRoundtrip() throws Exception { byte[] privkeyASN1 = Hex.decode( "3082011302010104205c0b98e524ad188ddef35dc6abba13c34a351a05409e5d285403718b93336a4aa081a53081a2020101302c06072a8648ce3d0101022100fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f300604010004010704410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8022100fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141020101a144034200042af7a2aafe8dafd7dc7f9cfb58ce09bda7dce28653ab229b98d1d3d759660c672dd0db18c8c2d76aa470448e876fc2089ab1354c01a6e72cefc50915f4a963ee"); ECKey decodedKey = ECKey.fromASN1(privkeyASN1); @@ -135,7 +173,7 @@ public class ECKeyTest { ECKey key = new ECKey(); String message = "Hello World!"; String signatureBase64 = key.signMessage(message); - System.out.println("Message signed with " + key.toAddress(NetworkParameters.prodNet()) + ": " + signatureBase64); + log.info("Message signed with " + key.toAddress(NetworkParameters.prodNet()) + ": " + signatureBase64); // Should verify correctly. key.verifyMessage(message, signatureBase64); try { @@ -176,6 +214,146 @@ public class ECKeyTest { } @Test + public void testUnencryptedCreate() throws Exception { + ECKey unencryptedKey = new ECKey(); + + // The key should initially be unencrypted. + assertTrue(!unencryptedKey.isEncrypted()); + + // Copy the private key bytes for checking later. + byte[] originalPrivateKeyBytes = new byte[32]; + System.arraycopy(unencryptedKey.getPrivKeyBytes(), 0, originalPrivateKeyBytes, 0, 32); + log.info("Original private key = " + Utils.bytesToHexString(originalPrivateKeyBytes)); + + // Encrypt the key. + ECKey encryptedKey = unencryptedKey.encrypt(keyCrypter, keyCrypter.deriveKey(PASSWORD1)); + + // The key should now be encrypted. + assertTrue("Key is not encrypted but it should be", encryptedKey.isEncrypted()); + + // The unencrypted private key bytes of the encrypted keychain + // should be null or all be blank. + byte[] privateKeyBytes = encryptedKey.getPrivKeyBytes(); + if (privateKeyBytes != null) { + for (int i = 0; i < privateKeyBytes.length; i++) { + assertEquals("Byte " + i + " of the private key was not zero but should be", 0, privateKeyBytes[i]); + } + } + + // Decrypt the key. + unencryptedKey = encryptedKey.decrypt(keyCrypter, keyCrypter.deriveKey(PASSWORD1)); + + // The key should be unencrypted + assertTrue("Key is not unencrypted but it should be", !unencryptedKey.isEncrypted()); + + // The reborn unencrypted private key bytes should match the + // original private key. + privateKeyBytes = unencryptedKey.getPrivKeyBytes(); + log.info("Reborn decrypted private key = " + Utils.bytesToHexString(privateKeyBytes)); + + for (int i = 0; i < privateKeyBytes.length; i++) { + assertEquals("Byte " + i + " of the private key did not match the original", originalPrivateKeyBytes[i], + privateKeyBytes[i]); + } + } + + @Test + public void testEncryptedCreate() throws Exception { + ECKey unencryptedKey = new ECKey(); + + // Copy the private key bytes for checking later. + byte[] originalPrivateKeyBytes = new byte[32]; + System.arraycopy(unencryptedKey.getPrivKeyBytes(), 0, originalPrivateKeyBytes, 0, 32); + log.info("Original private key = " + Utils.bytesToHexString(originalPrivateKeyBytes)); + + EncryptedPrivateKey encryptedPrivateKey = keyCrypter.encrypt(unencryptedKey.getPrivKeyBytes(), keyCrypter.deriveKey(PASSWORD1)); + ECKey encryptedKey = new ECKey(encryptedPrivateKey, unencryptedKey.getPubKey(), keyCrypter); + + // The key should initially be encrypted + assertTrue("Key not encrypted at start", encryptedKey.isEncrypted()); + + // The unencrypted private key bytes of the encrypted keychain should all be blank. + byte[] privateKeyBytes = encryptedKey.getPrivKeyBytes(); + if (privateKeyBytes != null) { + for (int i = 0; i < privateKeyBytes.length; i++) { + assertEquals("Byte " + i + " of the private key was not zero but should be", 0, privateKeyBytes[i]); + } + } + + // Decrypt the key. + ECKey rebornUnencryptedKey = encryptedKey.decrypt(keyCrypter, keyCrypter.deriveKey(PASSWORD1)); + + // The key should be unencrypted + assertTrue("Key is not unencrypted but it should be", !rebornUnencryptedKey.isEncrypted()); + + // The reborn unencrypted private key bytes should match the original private key. + privateKeyBytes = rebornUnencryptedKey.getPrivKeyBytes(); + log.info("Reborn decrypted private key = " + Utils.bytesToHexString(privateKeyBytes)); + + for (int i = 0; i < privateKeyBytes.length; i++) { + assertEquals("Byte " + i + " of the private key did not match the original", originalPrivateKeyBytes[i], privateKeyBytes[i]); + } + } + + @Test + public void testEncryptionIsReversible() throws Exception { + ECKey originalUnencryptedKey = new ECKey(); + EncryptedPrivateKey encryptedPrivateKey = keyCrypter.encrypt(originalUnencryptedKey.getPrivKeyBytes(), keyCrypter.deriveKey(PASSWORD1)); + ECKey encryptedKey = new ECKey(encryptedPrivateKey, originalUnencryptedKey.getPubKey(), keyCrypter); + + // The key should be encrypted + assertTrue("Key not encrypted at start", encryptedKey.isEncrypted()); + + // Check that the key can be successfully decrypted back to the original. + assertTrue("Key encryption is not reversible but it should be", ECKey.encryptionIsReversible(originalUnencryptedKey, encryptedKey, keyCrypter, keyCrypter.deriveKey(PASSWORD1))); + + // Check that key encryption is not reversible if a password other than the original is used to generate the AES key. + assertTrue("Key encryption is reversible with wrong password", !ECKey.encryptionIsReversible(originalUnencryptedKey, encryptedKey, keyCrypter, keyCrypter.deriveKey(WRONG_PASSWORD))); + + // Change one of the encrypted key bytes (this is to simulate a faulty keyCrypter). + // Encryption should not be reversible + byte[] goodEncryptedPrivateKeyBytes = encryptedPrivateKey.getEncryptedBytes(); + + // Break the encrypted private key and check it is broken. + byte[] badEncryptedPrivateKeyBytes = goodEncryptedPrivateKeyBytes; + + // XOR the 16th byte with 0x0A (this is fairly arbitary) to break it. + badEncryptedPrivateKeyBytes[16] = (byte) (badEncryptedPrivateKeyBytes[12] ^ new Byte("12").byteValue()); + + encryptedPrivateKey.setEncryptedPrivateBytes(badEncryptedPrivateKeyBytes); + ECKey badEncryptedKey = new ECKey(encryptedPrivateKey, originalUnencryptedKey.getPubKey(), keyCrypter); + assertTrue("Key encryption is reversible with faulty encrypted bytes", !ECKey.encryptionIsReversible(originalUnencryptedKey, badEncryptedKey, keyCrypter, keyCrypter.deriveKey(PASSWORD1))); + } + + @Test + public void testToString() throws Exception { + ECKey key = new ECKey(BigInteger.TEN); // An example private key. + + assertEquals("pub:04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7", key.toString()); + assertEquals("pub:04a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7893aba425419bc27a3b6c7e693a24c696f794c2ed877a1593cbee53b037368d7 priv:0a", key.toStringWithPrivate()); + } + + @Test + public void keyRecoveryWithEncryptedKey() throws Exception { + ECKey unencryptedKey = new ECKey(); + KeyParameter aesKey = keyCrypter.deriveKey(PASSWORD1); + ECKey encryptedKey = unencryptedKey.encrypt(keyCrypter,aesKey); + + String message = "Goodbye Jupiter!"; + Sha256Hash hash = Sha256Hash.create(message.getBytes()); + ECKey.ECDSASignature sig = encryptedKey.sign(hash, aesKey); + unencryptedKey = new ECKey(null, unencryptedKey.getPubKey()); + boolean found = false; + for (int i = 0; i < 4; i++) { + ECKey key2 = ECKey.recoverFromSignature(i, sig, hash, true); + if (unencryptedKey.equals(key2)) { + found = true; + break; + } + } + assertTrue(found); + } + public void roundTripDumpedPrivKey() throws Exception { ECKey key = new ECKey(); assertTrue(key.isCompressed()); @@ -186,4 +364,51 @@ public class ECKeyTest { assertTrue(Arrays.equals(key.getPrivKeyBytes(), key2.getPrivKeyBytes())); assertTrue(Arrays.equals(key.getPubKey(), key2.getPubKey())); } + + @Test + public void clear() throws Exception { + ECKey unencryptedKey = new ECKey(); + ECKey encryptedKey = (new ECKey()).encrypt(keyCrypter, keyCrypter.deriveKey(PASSWORD1)); + + checkSomeBytesAreNonZero(unencryptedKey.getPrivKeyBytes()); + unencryptedKey.clearPrivateKey(); + checkAllBytesAreZero(unencryptedKey.getPrivKeyBytes()); + + // The encryptedPrivateKey should be null in an unencrypted ECKey anyhow but check all the same. + assertTrue(unencryptedKey.getEncryptedPrivateKey() == null); + + checkSomeBytesAreNonZero(encryptedKey.getPrivKeyBytes()); + checkSomeBytesAreNonZero(encryptedKey.getEncryptedPrivateKey().getEncryptedBytes()); + checkSomeBytesAreNonZero(encryptedKey.getEncryptedPrivateKey().getInitialisationVector()); + encryptedKey.clearPrivateKey(); + checkAllBytesAreZero(encryptedKey.getPrivKeyBytes()); + checkAllBytesAreZero(encryptedKey.getEncryptedPrivateKey().getEncryptedBytes()); + checkAllBytesAreZero(encryptedKey.getEncryptedPrivateKey().getInitialisationVector()); + } + + private boolean checkSomeBytesAreNonZero(byte[] bytes) { + if (bytes == null) { + return false; + } else { + for (int i = 0; i < bytes.length; i++) { + if (bytes[i] != 0) { + return true; + } + } + return false; + } + } + + private boolean checkAllBytesAreZero(byte[] bytes) { + if (bytes == null) { + return true; + } else { + for (int i = 0; i < bytes.length; i++) { + if (bytes[i] != 0) { + return false; + } + } + return true; + } + } } diff --git a/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java b/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java index cf866f40..3cd2a2ed 100644 --- a/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java +++ b/core/src/test/java/com/google/bitcoin/core/FullBlockTestGenerator.java @@ -48,7 +48,7 @@ public class FullBlockTestGenerator { coinbaseOutKeyPubKey = coinbaseOutKey.getPubKey(); Utils.rollMockClock(0); // Set a mock clock for timestamp tests } - + public List getBlocksToTest(boolean addExpensiveBlocks) throws ScriptException, ProtocolException, IOException { List blocks = new LinkedList(); diff --git a/core/src/test/java/com/google/bitcoin/core/WalletTest.java b/core/src/test/java/com/google/bitcoin/core/WalletTest.java index 8d18cb03..4aa08418 100644 --- a/core/src/test/java/com/google/bitcoin/core/WalletTest.java +++ b/core/src/test/java/com/google/bitcoin/core/WalletTest.java @@ -18,20 +18,33 @@ package com.google.bitcoin.core; import com.google.bitcoin.core.Transaction.SigHash; import com.google.bitcoin.core.WalletTransaction.Pool; +import com.google.bitcoin.crypto.KeyCrypter; +import com.google.bitcoin.crypto.KeyCrypterException; +import com.google.bitcoin.crypto.KeyCrypterScrypt; import com.google.bitcoin.store.BlockStore; import com.google.bitcoin.store.MemoryBlockStore; import com.google.bitcoin.utils.BriefLogFormatter; import com.google.bitcoin.utils.Locks; import com.google.common.collect.Lists; import com.google.common.util.concurrent.CycleDetectingLockFactory; +import com.google.protobuf.ByteString; + +import org.bitcoinj.wallet.Protos; +import org.bitcoinj.wallet.Protos.ScryptParameters; +import org.bitcoinj.wallet.Protos.Wallet.EncryptionType; import org.junit.Before; import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.crypto.params.KeyParameter; import java.io.File; import java.io.IOException; import java.math.BigInteger; +import java.security.SecureRandom; import java.net.InetAddress; import java.util.ArrayList; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Random; @@ -45,26 +58,74 @@ import static com.google.bitcoin.core.Utils.toNanoCoins; import static org.junit.Assert.*; public class WalletTest { + public Logger log = LoggerFactory.getLogger(WalletTest.class.getName()); + static final NetworkParameters params = NetworkParameters.unitTests(); private Address myAddress; + private Address myEncryptedAddress; + private Address myEncryptedAddress2; + private Wallet wallet; + private Wallet encryptedWallet; + // A wallet with an initial unencrypted private key and an encrypted private key. + private Wallet encryptedHetergeneousWallet; + private BlockChain chain; private BlockStore blockStore; private ECKey myKey; + private ECKey myEncryptedKey; + + private ECKey myKey2; + private ECKey myEncryptedKey2; + + private static CharSequence PASSWORD1 = "my helicopter contains eels"; + private static CharSequence WRONG_PASSWORD = "nothing noone nobody nowhere"; + + private KeyParameter aesKey; + private KeyParameter wrongAesKey; + + private KeyCrypter keyCrypter; + + private SecureRandom secureRandom = new SecureRandom(); @Before public void setUp() throws Exception { myKey = new ECKey(); + myKey2 = new ECKey(); myAddress = myKey.toAddress(params); wallet = new Wallet(params); wallet.addKey(myKey); + + byte[] salt = new byte[KeyCrypterScrypt.SALT_LENGTH]; + secureRandom.nextBytes(salt); + Protos.ScryptParameters.Builder scryptParametersBuilder = Protos.ScryptParameters.newBuilder().setSalt(ByteString.copyFrom(salt)); + ScryptParameters scryptParameters = scryptParametersBuilder.build(); + + keyCrypter = new KeyCrypterScrypt(scryptParameters); + + wallet = new Wallet(params); + encryptedWallet = new Wallet(params, keyCrypter); + encryptedHetergeneousWallet = new Wallet(params, keyCrypter); + + aesKey = keyCrypter.deriveKey(PASSWORD1); + wrongAesKey = keyCrypter.deriveKey(WRONG_PASSWORD); + + wallet.addKey(myKey); + + myEncryptedKey = encryptedWallet.addNewEncryptedKey(keyCrypter, aesKey); + myEncryptedAddress = myEncryptedKey.toAddress(params); + + encryptedHetergeneousWallet.addKey(myKey2); + myEncryptedKey2 = encryptedHetergeneousWallet.addNewEncryptedKey(keyCrypter, aesKey); + myEncryptedAddress2 = myEncryptedKey2.toAddress(params); + blockStore = new MemoryBlockStore(params); chain = new BlockChain(params, wallet, blockStore); BriefLogFormatter.init(); } - private Transaction sendMoneyToWallet(Transaction tx, AbstractBlockChain.NewBlockType type) + private Transaction sendMoneyToWallet(Wallet wallet, Transaction tx, AbstractBlockChain.NewBlockType type) throws IOException, ProtocolException, VerificationException { if (type == null) { // Pending/broadcast tx. @@ -79,53 +140,133 @@ public class WalletTest { return tx; } - private Transaction sendMoneyToWallet(BigInteger value, AbstractBlockChain.NewBlockType type) + private Transaction sendMoneyToWallet(Transaction tx, AbstractBlockChain.NewBlockType type) throws IOException, + ProtocolException, VerificationException { + return sendMoneyToWallet(this.wallet, tx, type); + } + + private Transaction sendMoneyToWallet(Wallet wallet, BigInteger value, Address toAddress, AbstractBlockChain.NewBlockType type) throws IOException, ProtocolException, VerificationException { - return sendMoneyToWallet(createFakeTx(params, value, myAddress), type); + return sendMoneyToWallet(wallet, createFakeTx(params, value, toAddress), type); + } + + private Transaction sendMoneyToWallet(BigInteger value, AbstractBlockChain.NewBlockType type) throws IOException, + ProtocolException, VerificationException { + return sendMoneyToWallet(this.wallet, createFakeTx(params, value, myAddress), type); } @Test public void basicSpending() throws Exception { - // We'll set up a wallet that receives a coin, then sends a coin of lesser value and keeps the change. We - // will attach a small fee. Because the Bitcoin protocol makes it difficult to determine the fee of an - // arbitrary transaction in isolation, we'll check that the fee was set by examining the size of the change. + basicSpendingCommon(wallet, myAddress, false); + } + + @Test + public void basicSpendingWithEncryptedWallet() throws Exception { + basicSpendingCommon(encryptedWallet, myEncryptedAddress, true); + } + + @Test + public void basicSpendingWithEncryptedHetergeneousWallet() throws Exception { + basicSpendingCommon(encryptedHetergeneousWallet, myEncryptedAddress2, true); + } + + private void basicSpendingCommon(Wallet wallet, Address toAddress, boolean testEncryption) throws Exception { + // We'll set up a wallet that receives a coin, then sends a coin of + // lesser value and keeps the change. We + // will attach a small fee. Because the Bitcoin protocol makes it + // difficult to determine the fee of an + // arbitrary transaction in isolation, we'll check that the fee was set + // by examining the size of the change. // Receive some money as a pending transaction. - BigInteger v1 = Utils.toNanoCoins(1, 0); - Transaction t1 = sendMoneyToWallet(v1, null); - assertEquals(BigInteger.ZERO, wallet.getBalance()); - assertEquals(v1, wallet.getBalance(Wallet.BalanceType.ESTIMATED)); - assertEquals(1, wallet.getPoolSize(Pool.PENDING)); - assertEquals(0, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT)); - sendMoneyToWallet(t1, AbstractBlockChain.NewBlockType.BEST_CHAIN); - assertEquals(v1, wallet.getBalance()); - assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT)); - assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.ALL)); + receiveAPendingTransaction(wallet, toAddress); - // Create a send with a fee. + // Prepare to send. Address destination = new ECKey().toAddress(params); BigInteger v2 = toNanoCoins(0, 50); Wallet.SendRequest req = Wallet.SendRequest.to(destination, v2); req.fee = toNanoCoins(0, 1); + + if (testEncryption) { + // Try to create a send with a fee but no password (this should fail). + try { + wallet.completeTx(req); + fail("No exception was thrown trying to sign an encrypted key with no password supplied."); + } catch (KeyCrypterException kce) { + assertEquals("This ECKey is encrypted but no decryption key has been supplied.", kce.getMessage()); + } + assertEquals("Wrong number of UNSPENT.1", 1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT)); + assertEquals("Wrong number of ALL.1", 1, wallet.getPoolSize(WalletTransaction.Pool.ALL)); + + // Try to create a send with a fee but the wrong password (this should fail). + req = Wallet.SendRequest.to(destination, v2); + req.aesKey = wrongAesKey; + req.fee = toNanoCoins(0, 1); + + try { + wallet.completeTx(req); + fail("No exception was thrown trying to sign an encrypted key with the wrong password supplied."); + } catch (KeyCrypterException kce) { + assertEquals("Could not decrypt bytes", kce.getMessage()); + } + + assertEquals("Wrong number of UNSPENT.2", 1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT)); + assertEquals("Wrong number of ALL.2", 1, wallet.getPoolSize(WalletTransaction.Pool.ALL)); + + // Create a send with a fee with the correct password (this should succeed). + req = Wallet.SendRequest.to(destination, v2); + req.aesKey = aesKey; + req.fee = toNanoCoins(0, 1); + } + + // Complete the transaction successfully. wallet.completeTx(req); + Transaction t2 = req.tx; - assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT)); - assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.ALL)); + assertEquals("Wrong number of UNSPENT.3", 1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT)); + assertEquals("Wrong number of ALL.3", 1, wallet.getPoolSize(WalletTransaction.Pool.ALL)); assertEquals(TransactionConfidence.Source.SELF, t2.getConfidence().getSource()); assertEquals(wallet.getChangeAddress(), t2.getOutput(1).getScriptPubKey().getToAddress()); // Do some basic sanity checks. - assertEquals(1, t2.getInputs().size()); - assertEquals(myAddress, t2.getInputs().get(0).getScriptSig().getFromAddress()); - assertEquals(t2.getConfidence().getConfidenceType(), TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN); - assertEquals(2, t2.getOutputs().size()); - assertEquals(destination, t2.getOutputs().get(0).getScriptPubKey().getToAddress()); - assertEquals(wallet.getChangeAddress(), t2.getOutputs().get(1).getScriptPubKey().getToAddress()); - BigInteger v3 = toNanoCoins(0, 49); - assertEquals(v3, t2.getOutputs().get(1).getValue()); - // Check the script runs and signatures verify. - t2.getInputs().get(0).verify(); + basicSanityChecks(wallet, t2, toAddress, destination); + // Broadcast the transaction and commit. + broadcastAndCommit(wallet, t2); + + // Now check that we can spend the unconfirmed change, with a new change + // address of our own selection. + // (req.aesKey is null for unencrypted / the correct aesKey for encrypted.) + spendUnconfirmedChange(wallet, t2, req.aesKey); + } + + private void receiveAPendingTransaction(Wallet wallet, Address toAddress) throws Exception { + BigInteger v1 = Utils.toNanoCoins(1, 0); + Transaction t1 = sendMoneyToWallet(wallet, v1, toAddress, null); + assertEquals(BigInteger.ZERO, wallet.getBalance()); + assertEquals(v1, wallet.getBalance(Wallet.BalanceType.ESTIMATED)); + assertEquals(1, wallet.getPoolSize(Pool.PENDING)); + assertEquals(0, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT)); + sendMoneyToWallet(wallet, t1, AbstractBlockChain.NewBlockType.BEST_CHAIN); + assertEquals("Incorrect confirmed tx balance", v1, wallet.getBalance()); + assertEquals("Incorrect confirmed tx PENDING pool size", 0, wallet.getPoolSize(WalletTransaction.Pool.PENDING)); + assertEquals("Incorrect confirmed tx UNSPENT pool size", 1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT)); + assertEquals("Incorrect confirmed tx ALL pool size", 1, wallet.getPoolSize(WalletTransaction.Pool.ALL)); + } + + private void basicSanityChecks(Wallet wallet, Transaction t, Address fromAddress, Address destination) throws ScriptException { + assertEquals("Wrong number of tx inputs", 1, t.getInputs().size()); + assertEquals(fromAddress, t.getInputs().get(0).getScriptSig().getFromAddress()); + assertEquals(t.getConfidence().getConfidenceType(), TransactionConfidence.ConfidenceType.NOT_SEEN_IN_CHAIN); + assertEquals("Wrong number of tx outputs",2, t.getOutputs().size()); + assertEquals(destination, t.getOutputs().get(0).getScriptPubKey().getToAddress()); + assertEquals(wallet.getChangeAddress(), t.getOutputs().get(1).getScriptPubKey().getToAddress()); + assertEquals(toNanoCoins(0, 49), t.getOutputs().get(1).getValue()); + // Check the script runs and signatures verify. + t.getInputs().get(0).verify(); + } + + private void broadcastAndCommit(Wallet wallet, Transaction t) throws Exception { final LinkedList txns = Lists.newLinkedList(); wallet.addEventListener(new AbstractWalletEventListener() { @Override @@ -133,19 +274,22 @@ public class WalletTest { txns.add(tx); } }); - // We broadcast the TX over the network, and then commit to it. - t2.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByAddress(new byte[]{1,2,3,4}))); - t2.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByAddress(new byte[]{10,2,3,4}))); - wallet.commitTx(t2); + + t.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByAddress(new byte[]{1,2,3,4}))); + t.getConfidence().markBroadcastBy(new PeerAddress(InetAddress.getByAddress(new byte[]{10,2,3,4}))); + wallet.commitTx(t); assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.PENDING)); assertEquals(1, wallet.getPoolSize(WalletTransaction.Pool.SPENT)); assertEquals(2, wallet.getPoolSize(WalletTransaction.Pool.ALL)); - assertEquals(t2, txns.getFirst()); + assertEquals(t, txns.getFirst()); assertEquals(1, txns.size()); + } - // Now check that we can spend the unconfirmed change, with a new change address of our own selection. + private void spendUnconfirmedChange(Wallet wallet, Transaction t2, KeyParameter aesKey) throws Exception { + BigInteger v3 = toNanoCoins(0, 49); assertEquals(v3, wallet.getBalance()); - req = Wallet.SendRequest.to(new ECKey().toAddress(params), toNanoCoins(0, 48)); + Wallet.SendRequest req = Wallet.SendRequest.to(new ECKey().toAddress(params), toNanoCoins(0, 48)); + req.aesKey = aesKey; Address a = req.changeAddress = new ECKey().toAddress(params); wallet.completeTx(req); Transaction t3 = req.tx; @@ -153,7 +297,7 @@ public class WalletTest { assertNotNull(t3); wallet.commitTx(t3); assertTrue(wallet.isConsistent()); - // t2 and t3 gets confirmed in the same block. + // t2 and t3 gets confirmed in the same block. BlockPair bp = createFakeBlock(blockStore, t2, t3); wallet.receiveFromBlock(t2, bp.storedBlock, AbstractBlockChain.NewBlockType.BEST_CHAIN); wallet.receiveFromBlock(t3, bp.storedBlock, AbstractBlockChain.NewBlockType.BEST_CHAIN); @@ -340,12 +484,12 @@ public class WalletTest { tx.addOutput(output); wallet.receiveFromBlock(tx, null, BlockChain.NewBlockType.BEST_CHAIN); - assertTrue(wallet.isConsistent()); + assertTrue("Wallet is not consistent", wallet.isConsistent()); Transaction txClone = new Transaction(params, tx.bitcoinSerialize()); try { wallet.receiveFromBlock(txClone, null, BlockChain.NewBlockType.BEST_CHAIN); - fail(); + fail("Illegal argument not thrown when it should have been."); } catch (IllegalStateException ex) { // expected } @@ -751,7 +895,7 @@ public class WalletTest { // TODO: This code is messy, improve the Script class and fixinate! assertEquals(t2.toString(), 1, t2.getInputs().get(0).getScriptSig().chunks.size()); assertTrue(t2.getInputs().get(0).getScriptSig().chunks.get(0).data.length > 50); - System.out.println(t2); + log.info(t2.toString(chain)); } @Test @@ -764,23 +908,23 @@ public class WalletTest { ECKey key = new ECKey(); wallet.addKey(key); Sha256Hash hash2 = Sha256Hash.hashFileContents(f); - assertFalse(hash1.equals(hash2)); // File has changed. + assertFalse("Wallet not saved after addKey", hash1.equals(hash2)); // File has changed. Transaction t1 = createFakeTx(params, toNanoCoins(5, 0), key); if (wallet.isPendingTransactionRelevant(t1)) wallet.receivePending(t1, null); Sha256Hash hash3 = Sha256Hash.hashFileContents(f); - assertFalse(hash2.equals(hash3)); // File has changed again. + assertFalse("Wallet not saved after receivePending", hash2.equals(hash3)); // File has changed again. Block b1 = createFakeBlock(blockStore, t1).block; chain.add(b1); Sha256Hash hash4 = Sha256Hash.hashFileContents(f); - assertFalse(hash3.equals(hash4)); // File has changed again. + assertFalse("Wallet not saved after chain add.1", hash3.equals(hash4)); // File has changed again. // Check that receiving some block without any relevant transactions still triggers a save. Block b2 = b1.createNextBlock(new ECKey().toAddress(params)); chain.add(b2); - assertFalse(hash4.equals(Sha256Hash.hashFileContents(f))); // File has changed again. + assertFalse("Wallet not saved after chain add.2", hash4.equals(Sha256Hash.hashFileContents(f))); // File has changed again. } @Test @@ -846,7 +990,7 @@ public class WalletTest { @Test public void spendOutputFromPendingTransaction() throws Exception { - // We'll set up a wallet that receives a coin, then sends a coin of lesser value and keeps the change. + // We'll set up a wallet that receives a coin, then sends a coin of lesser value and keeps the change. BigInteger v1 = Utils.toNanoCoins(1, 0); sendMoneyToWallet(v1, AbstractBlockChain.NewBlockType.BEST_CHAIN); // First create our current transaction @@ -884,6 +1028,127 @@ public class WalletTest { } @Test + public void encryptionDecryptionBasic() throws Exception { + encryptionDecryptionBasicCommon(encryptedWallet); + encryptionDecryptionBasicCommon(encryptedHetergeneousWallet); + } + + private void encryptionDecryptionBasicCommon(Wallet wallet) { + // Check the wallet is initially of WalletType ENCRYPTED. + assertTrue("Wallet is not an encrypted wallet", wallet.getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES); + + // Correct password should decrypt first encrypted private key. + assertTrue("checkPasswordCanDecryptFirstPrivateKey result is wrong with correct password.2", wallet.checkPassword(PASSWORD1)); + + // Incorrect password should not decrypt first encrypted private key. + assertTrue("checkPasswordCanDecryptFirstPrivateKey result is wrong with incorrect password.3", !wallet.checkPassword(WRONG_PASSWORD)); + + // Decrypt wallet. + assertTrue("The keyCrypter is missing but should not be", keyCrypter != null); + wallet.decrypt(aesKey); + + // Wallet should now be unencrypted. + assertTrue("Wallet is not an unencrypted wallet", wallet.getKeyCrypter() == null); + + // Correct password should not decrypt first encrypted private key as wallet is unencrypted. + assertTrue("checkPasswordCanDecryptFirstPrivateKey result is wrong with correct password", !wallet.checkPassword(PASSWORD1)); + + // Incorrect password should not decrypt first encrypted private key as wallet is unencrypted. + assertTrue("checkPasswordCanDecryptFirstPrivateKey result is wrong with incorrect password", !wallet.checkPassword(WRONG_PASSWORD)); + + // Encrypt wallet. + wallet.encrypt(keyCrypter, aesKey); + + // Wallet should now be of type WalletType.ENCRYPTED_SCRYPT_AES. + assertTrue("Wallet is not an encrypted wallet", wallet.getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES); + } + + @Test + public void encryptionDecryptionBadPassword() throws Exception { + // Check the wallet is currently encrypted + assertTrue("Wallet is not an encrypted wallet", encryptedWallet.getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES); + + // Chek that the wrong password does not decrypt the wallet. + try { + encryptedWallet.decrypt(wrongAesKey); + fail("Incorrectly decoded wallet with wrong password"); + } catch (KeyCrypterException ede) { + assertTrue("Wrong message in EncrypterDecrypterException", ede.getMessage().indexOf("Could not decrypt bytes") > -1); + } + } + + @Test + public void encryptionDecryptionCheckExceptions() throws Exception { + // Check the wallet is currently encrypted + assertTrue("Wallet is not an encrypted wallet", encryptedWallet.getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES); + + // Decrypt wallet. + assertTrue("The keyCrypter is missing but should not be.1", keyCrypter != null); + encryptedWallet.decrypt(aesKey); + + // Try decrypting it again + try { + assertTrue("The keyCrypter is missing but should not be.2", keyCrypter != null); + encryptedWallet.decrypt(aesKey); + fail("Should not be able to decrypt a decrypted wallet"); + } catch (IllegalStateException e) { + assertTrue("Expected behaviour", true); + } + assertTrue("Wallet is not an unencrypted wallet.2", encryptedWallet.getKeyCrypter() == null); + + // Encrypt wallet. + encryptedWallet.encrypt(keyCrypter, aesKey); + + assertTrue("Wallet is not an encrypted wallet.2", encryptedWallet.getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES); + + // Try encrypting it again + try { + encryptedWallet.encrypt(keyCrypter, aesKey); + fail("Should not be able to encrypt an encrypted wallet"); + } catch (IllegalStateException e) { + assertTrue("Expected behaviour", true); + } + assertTrue("Wallet is not an encrypted wallet.3", encryptedWallet.getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES); + } + + @Test + public void encryptionDecryptionHomogenousKeys() throws Exception { + // Check the wallet is currently encrypted + assertTrue("Wallet is not an encrypted wallet", encryptedWallet.getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES); + + // Try added an ECKey that was encrypted with a differenct ScryptParameters (i.e. a non-homogenous key). + // This is not allowed as the ScryptParameters is stored at the Wallet level. + byte[] salt = new byte[KeyCrypterScrypt.SALT_LENGTH]; + secureRandom.nextBytes(salt); + Protos.ScryptParameters.Builder scryptParametersBuilder = Protos.ScryptParameters.newBuilder().setSalt(ByteString.copyFrom(salt)); + ScryptParameters scryptParameters = scryptParametersBuilder.build(); + + KeyCrypter keyCrypterDifferent = new KeyCrypterScrypt(scryptParameters); + + ECKey ecKeyDifferent = new ECKey(); + ecKeyDifferent = ecKeyDifferent.encrypt(keyCrypterDifferent, aesKey); + + Iterable keys = encryptedWallet.getKeys(); + Iterator iterator = keys.iterator(); + boolean oneKey = iterator.hasNext(); + iterator.next(); + assertTrue("Wrong number of keys in wallet before key addition", oneKey && !iterator.hasNext()); + + try { + encryptedWallet.addKey(ecKeyDifferent); + fail("AddKey should have thrown an EncrypterDecrypterException but did not."); + } catch (KeyCrypterException ede) { + // Expected behaviour. + } + + keys = encryptedWallet.getKeys(); + iterator = keys.iterator(); + oneKey = iterator.hasNext(); + + iterator.next(); + assertTrue("Wrong number of keys in wallet after key addition", oneKey && !iterator.hasNext()); + } + public void ageMattersDuringSelection() throws Exception { // Test that we prefer older coins to newer coins when building spends. This reduces required fees and improves // time to confirmation as the transaction will appear less spammy. diff --git a/core/src/test/java/com/google/bitcoin/crypto/KeyCrypterScryptTest.java b/core/src/test/java/com/google/bitcoin/crypto/KeyCrypterScryptTest.java new file mode 100644 index 00000000..5e72f8bf --- /dev/null +++ b/core/src/test/java/com/google/bitcoin/crypto/KeyCrypterScryptTest.java @@ -0,0 +1,163 @@ +/** + * Copyright 2013 Jim Burton. + * + * Licensed under the MIT license (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://opensource.org/licenses/mit-license.php + * + * 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.google.bitcoin.crypto; + +import java.io.UnsupportedEncodingException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.UUID; + +import junit.framework.TestCase; + +import org.bitcoinj.wallet.Protos; +import org.bitcoinj.wallet.Protos.ScryptParameters; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.bitcoin.core.Utils; +import com.google.bitcoin.utils.BriefLogFormatter; +import com.google.protobuf.ByteString; + +public class KeyCrypterScryptTest extends TestCase { + + private static final Logger log = LoggerFactory.getLogger(KeyCrypterScryptTest.class); + + // Nonsense bytes for encryption test. + private static final byte[] TEST_BYTES1 = new byte[]{0, -101, 2, 103, -4, 105, 6, 107, 8, -109, 10, 111, -12, 113, 14, -115, 16, 117, -18, 119, 20, 121, 22, 123, -24, 125, 26, 127, -28, 29, -30, 31}; + + private static CharSequence PASSWORD1 = "aTestPassword"; + private static CharSequence PASSWORD2 = "0123456789"; + + private static CharSequence WRONG_PASSWORD = "thisIsTheWrongPassword"; + + private SecureRandom secureRandom; + private ScryptParameters scryptParameters; + + @Before + public void setUp() throws Exception { + secureRandom = new SecureRandom(); + + byte[] salt = new byte[KeyCrypterScrypt.SALT_LENGTH]; + secureRandom.nextBytes(salt); + Protos.ScryptParameters.Builder scryptParametersBuilder = Protos.ScryptParameters.newBuilder().setSalt(ByteString.copyFrom(salt)); + scryptParameters = scryptParametersBuilder.build(); + + BriefLogFormatter.init(); + } + + @Test + public void testKeyCrypterGood1() throws KeyCrypterException { + KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(scryptParameters); + + // Encrypt. + EncryptedPrivateKey encryptedPrivateKey = keyCrypter.encrypt(TEST_BYTES1, keyCrypter.deriveKey(PASSWORD1)); + assertNotNull(encryptedPrivateKey); + + // Decrypt. + byte[] reborn = keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(PASSWORD1)); + log.debug("Original: " + Utils.bytesToHexString(TEST_BYTES1)); + log.debug("Reborn : " + Utils.bytesToHexString(reborn)); + assertEquals(Utils.bytesToHexString(TEST_BYTES1), Utils.bytesToHexString(reborn)); + } + + /** + * Test with random plain text strings and random passwords. + * UUIDs are used and hence will only cover hex characters (and the separator hyphen). + * @throws KeyCrypterException + * @throws UnsupportedEncodingException + */ + public void testKeyCrypterGood2() throws KeyCrypterException, UnsupportedEncodingException { + KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(scryptParameters); + + int numberOfTests = 16; + System.out.print("EncrypterDecrypterTest: Trying random UUIDs for plainText and passwords :"); + for (int i = 0; i < numberOfTests; i++) { + // Create a UUID as the plaintext and use another for the password. + String plainText = UUID.randomUUID().toString(); + CharSequence password = UUID.randomUUID().toString(); + + EncryptedPrivateKey encryptedPrivateKey = keyCrypter.encrypt(plainText.getBytes(), keyCrypter.deriveKey(password)); + + assertNotNull(encryptedPrivateKey); + + byte[] reconstructedPlainBytes = keyCrypter.decrypt(encryptedPrivateKey,keyCrypter.deriveKey(password)); + assertEquals(Utils.bytesToHexString(plainText.getBytes()), Utils.bytesToHexString(reconstructedPlainBytes)); + System.out.print('.'); + } + System.out.println(" Done."); + } + + public void testKeyCrypterWrongPassword() throws KeyCrypterException { + KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(scryptParameters); + + // create a longer encryption string + StringBuffer stringBuffer = new StringBuffer(); + for (int i = 0; i < 100; i++) { + stringBuffer.append(i + " ").append("The quick brown fox"); + } + + EncryptedPrivateKey encryptedPrivateKey = keyCrypter.encrypt(stringBuffer.toString().getBytes(), keyCrypter.deriveKey(PASSWORD2)); + assertNotNull(encryptedPrivateKey); + + try { + keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(WRONG_PASSWORD)); + fail("Decrypt with wrong password did not throw exception"); + } catch (KeyCrypterException ede) { + assertTrue(ede.getMessage().indexOf("Could not decrypt") > -1); + } + } + + @Test + public void testEncryptDecryptBytes1() throws KeyCrypterException { + KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(scryptParameters); + + // Encrypt bytes. + EncryptedPrivateKey encryptedPrivateKey = keyCrypter.encrypt(TEST_BYTES1, keyCrypter.deriveKey(PASSWORD1)); + assertNotNull(encryptedPrivateKey); + log.debug("\nEncrypterDecrypterTest: cipherBytes = \nlength = " + encryptedPrivateKey.getEncryptedBytes().length + "\n---------------\n" + Utils.bytesToHexString(encryptedPrivateKey.getEncryptedBytes()) + "\n---------------\n"); + + byte[] rebornPlainBytes = keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(PASSWORD1)); + + log.debug("Original: " + Utils.bytesToHexString(TEST_BYTES1)); + log.debug("Reborn1 : " + Utils.bytesToHexString(rebornPlainBytes)); + assertEquals( Utils.bytesToHexString(TEST_BYTES1), Utils.bytesToHexString(rebornPlainBytes)); + } + + @Test + public void testEncryptDecryptBytes2() throws KeyCrypterException { + KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(scryptParameters); + + // Encrypt random bytes of various lengths up to length 50. + Random random = new Random(); + + for (int i = 0; i < 50; i++) { + byte[] plainBytes = new byte[i]; + random.nextBytes(plainBytes); + + EncryptedPrivateKey encryptedPrivateKey = keyCrypter.encrypt(plainBytes, keyCrypter.deriveKey(PASSWORD1)); + assertNotNull(encryptedPrivateKey); + //log.debug("\nEncrypterDecrypterTest: cipherBytes = \nlength = " + cipherBytes.length + "\n---------------\n" + Utils.bytesToHexString(cipherBytes) + "\n---------------\n"); + + byte[] rebornPlainBytes = keyCrypter.decrypt(encryptedPrivateKey, keyCrypter.deriveKey(PASSWORD1)); + + log.debug("Original: (" + i + ") " + Utils.bytesToHexString(plainBytes)); + log.debug("Reborn1 : (" + i + ") " + Utils.bytesToHexString(rebornPlainBytes)); + assertEquals( Utils.bytesToHexString(plainBytes), Utils.bytesToHexString(rebornPlainBytes)); + } + } +} diff --git a/core/src/test/java/com/google/bitcoin/store/WalletProtobufSerializerTest.java b/core/src/test/java/com/google/bitcoin/store/WalletProtobufSerializerTest.java index 5f34d265..321572ce 100644 --- a/core/src/test/java/com/google/bitcoin/store/WalletProtobufSerializerTest.java +++ b/core/src/test/java/com/google/bitcoin/store/WalletProtobufSerializerTest.java @@ -6,6 +6,7 @@ import com.google.bitcoin.core.TransactionConfidence.ConfidenceType; import com.google.bitcoin.utils.BriefLogFormatter; import com.google.protobuf.ByteString; import org.bitcoinj.wallet.Protos; +import org.bitcoinj.wallet.Protos.Wallet.EncryptionType; import org.junit.Before; import org.junit.Test; @@ -19,12 +20,16 @@ import java.util.*; import static com.google.bitcoin.core.TestUtils.createFakeTx; import static org.junit.Assert.*; +import com.google.bitcoin.crypto.KeyCrypter; + public class WalletProtobufSerializerTest { static final NetworkParameters params = NetworkParameters.unitTests(); private ECKey myKey; private Address myAddress; private Wallet myWallet; + public static String WALLET_DESCRIPTION = "The quick brown fox lives in \u4f26\u6566"; // Beijing in Chinese + @Before public void setUp() throws Exception { BriefLogFormatter.initVerbose(); @@ -33,6 +38,7 @@ public class WalletProtobufSerializerTest { myAddress = myKey.toAddress(params); myWallet = new Wallet(params); myWallet.addKey(myKey); + myWallet.setDescription(WALLET_DESCRIPTION); } @Test @@ -47,6 +53,7 @@ public class WalletProtobufSerializerTest { wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes()); assertEquals(myKey.getCreationTimeSeconds(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getCreationTimeSeconds()); + assertEquals(WALLET_DESCRIPTION, wallet1.getDescription()); } @Test @@ -220,7 +227,7 @@ public class WalletProtobufSerializerTest { assertEquals(work2, rebornConfidence1.getWorkDone()); } - private Wallet roundTrip(Wallet wallet) throws IOException { + private Wallet roundTrip(Wallet wallet) throws Exception { ByteArrayOutputStream output = new ByteArrayOutputStream(); //System.out.println(WalletProtobufSerializer.walletToText(wallet)); new WalletProtobufSerializer().writeWallet(wallet, output); @@ -252,14 +259,14 @@ public class WalletProtobufSerializerTest { rnd.nextBytes(wallet1.random_bytes); Wallet wallet2 = roundTripExtension(wallet1); - assertTrue(wallet2 instanceof WalletExtension); + assertTrue("Wallet2 is not an instance of WalletExtension. It is a " + wallet2.getClass().getCanonicalName(), wallet2 instanceof WalletExtension); WalletExtension wallet2ext = (WalletExtension)wallet2; - assertNotNull(wallet2ext.random_bytes); + assertNotNull("Wallet2s random bytes were null", wallet2ext.random_bytes); for (int i = 0; i < 100; i++) { - assertEquals(wallet1.random_bytes[i], wallet2ext.random_bytes[i]); + assertEquals("Wallet extension byte different at byte " + i, wallet1.random_bytes[i], wallet2ext.random_bytes[i]); } } @@ -277,7 +284,7 @@ public class WalletProtobufSerializerTest { } - private Wallet roundTripExtension(Wallet wallet) throws IOException { + private Wallet roundTripExtension(Wallet wallet) throws Exception { ByteArrayOutputStream output = new ByteArrayOutputStream(); WalletProtobufSerializer serializer = new WalletProtobufSerializer(); @@ -320,7 +327,13 @@ public class WalletProtobufSerializerTest { public Wallet newWallet(NetworkParameters params) { return new WalletExtension(params); } - + + @Override + public Wallet newWallet(NetworkParameters params, KeyCrypter keyCrypter) { + // Ignore encryption. + return new WalletExtension(params); + } + @Override public void readExtension(Wallet wallet, Protos.Extension extProto) { if (wallet instanceof WalletExtension) { diff --git a/examples/src/main/java/com/google/bitcoin/examples/PeerMonitor.java b/examples/src/main/java/com/google/bitcoin/examples/PeerMonitor.java index d9a5c0f2..6de99816 100644 --- a/examples/src/main/java/com/google/bitcoin/examples/PeerMonitor.java +++ b/examples/src/main/java/com/google/bitcoin/examples/PeerMonitor.java @@ -288,7 +288,7 @@ public class PeerMonitor { if (column == PeerTableModel.CHAIN_HEIGHT) { long height = (Long) contents; if (height != peerGroup.getMostCommonChainHeight()) { - str = height + " • "; + str = height + " \u2022 "; } } } diff --git a/examples/src/main/java/com/google/bitcoin/examples/PingService.java b/examples/src/main/java/com/google/bitcoin/examples/PingService.java index 88cb5994..4d032b03 100644 --- a/examples/src/main/java/com/google/bitcoin/examples/PingService.java +++ b/examples/src/main/java/com/google/bitcoin/examples/PingService.java @@ -17,6 +17,7 @@ package com.google.bitcoin.examples; import com.google.bitcoin.core.*; +import com.google.bitcoin.crypto.KeyCrypterException; import com.google.bitcoin.discovery.DnsDiscovery; import com.google.bitcoin.discovery.IrcDiscovery; import com.google.bitcoin.store.BlockStore; @@ -188,6 +189,9 @@ public class PingService { } catch (ScriptException e) { // If we didn't understand the scriptSig, just crash. throw new RuntimeException(e); + } catch (KeyCrypterException e) { + e.printStackTrace(); + throw new RuntimeException(e); } } } diff --git a/pom.xml b/pom.xml index 96584587..eedd1068 100644 --- a/pom.xml +++ b/pom.xml @@ -189,6 +189,13 @@ protobuf-java ${protobuf.version} + + + com.lambdaworks + scrypt + 1.3.3 + + @@ -206,5 +213,4 @@ 3.6.3.Final gen - diff --git a/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java b/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java index 988848c0..33bc6240 100644 --- a/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java +++ b/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java @@ -17,6 +17,7 @@ package com.google.bitcoin.tools; import com.google.bitcoin.core.*; +import com.google.bitcoin.crypto.KeyCrypterException; import com.google.bitcoin.discovery.DnsDiscovery; import com.google.bitcoin.discovery.IrcDiscovery; import com.google.bitcoin.discovery.PeerDiscovery; @@ -432,6 +433,8 @@ public class WalletTool { return; } catch (ScriptException e) { throw new RuntimeException(e); + } catch (KeyCrypterException e) { + throw new RuntimeException(e); } t = req.tx; // Not strictly required today. setup(); @@ -443,6 +446,8 @@ public class WalletTool { System.out.println(t.getHashAsString()); } catch (BlockStoreException e) { throw new RuntimeException(e); + } catch (KeyCrypterException e) { + throw new RuntimeException(e); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { @@ -666,7 +671,11 @@ public class WalletTool { System.err.println("That key already exists in this wallet."); return; } - wallet.addKey(key); + try { + wallet.addKey(key); + } catch (KeyCrypterException kce) { + System.err.println("There was an encryption related error when adding the key. The error was '" + kce.getMessage() + "'."); + } System.out.println("addr:" + key.toAddress(params) + " " + key); }