All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bitcoinj.wallet.WalletProtobufSerializer Maven / Gradle / Ivy

There is a newer version: 21.1.2
Show newest version
/*
 * Copyright 2012 Google Inc.
 * Copyright 2014 Andreas Schildbach
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.bitcoinj.wallet;

import org.bitcoinj.core.*;
import org.bitcoinj.core.TransactionConfidence.ConfidenceType;
import org.bitcoinj.crypto.BLSLazySignature;
import org.bitcoinj.crypto.KeyCrypter;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.crypto.factory.BLSKeyFactory;
import org.bitcoinj.crypto.factory.ECKeyFactory;
import org.bitcoinj.crypto.factory.Ed25519KeyFactory;
import org.bitcoinj.crypto.factory.KeyFactory;
import org.bitcoinj.quorums.InstantSendLock;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptException;
import org.bitcoinj.utils.ExchangeRate;
import org.bitcoinj.utils.Fiat;
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;

import com.google.common.collect.Lists;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.WireFormat;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;

import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Serialize and de-serialize a wallet to a byte stream containing a
 * protocol buffer. Protocol buffers are
 * a data interchange format developed by Google with an efficient binary representation, a type safe specification
 * language and compilers that generate code to work with those data structures for many languages. Protocol buffers
 * can have their format evolved over time: conceptually they represent data using (tag, length, value) tuples. The
 * format is defined by the {@code wallet.proto} file in the bitcoinj source distribution.

* * This class is used through its static methods. The most common operations are writeWallet and readWallet, which do * the obvious operations on Output/InputStreams. You can use a {@link ByteArrayInputStream} and equivalent * {@link ByteArrayOutputStream} if you'd like byte arrays instead. The protocol buffer can also be manipulated * in its object form if you'd like to modify the flattened data structure before serialization to binary.

* * You can extend the wallet format with additional fields specific to your application if you want, but make sure * to either put the extra data in the provided extension areas, or select tag numbers that are unlikely to be used * by anyone else.

* * @author Miron Cuperman * @author Andreas Schildbach */ public class WalletProtobufSerializer { private static final Logger log = LoggerFactory.getLogger(WalletProtobufSerializer.class); /** Current version used for serializing wallets. A version higher than this is considered from the future. */ public static final int CURRENT_WALLET_VERSION = Protos.Wallet.getDefaultInstance().getVersion(); // 512 MB private static final int WALLET_SIZE_LIMIT = 512 * 1024 * 1024; // Used for de-serialization protected Map txMap; private boolean requireMandatoryExtensions = true; private boolean requireAllExtensionsKnown = false; private int walletWriteBufferSize = CodedOutputStream.DEFAULT_BUFFER_SIZE; public interface WalletFactory { Wallet create(NetworkParameters params, KeyChainGroup keyChainGroup); } private final WalletFactory factory; private KeyChainFactory keyChainFactory; public WalletProtobufSerializer() { this(new WalletFactory() { @Override public Wallet create(NetworkParameters params, KeyChainGroup keyChainGroup) { return new WalletEx(params, keyChainGroup); } }); } public WalletProtobufSerializer(WalletFactory factory) { txMap = new HashMap<>(); this.factory = factory; this.keyChainFactory = new DefaultKeyChainFactory(); } public void setKeyChainFactory(KeyChainFactory keyChainFactory) { this.keyChainFactory = keyChainFactory; } /** * If this property is set to false, then unknown mandatory extensions will be ignored instead of causing load * errors. You should only use this if you know exactly what you are doing, as the extension data will NOT be * round-tripped, possibly resulting in a corrupted wallet if you save it back out again. */ public void setRequireMandatoryExtensions(boolean value) { requireMandatoryExtensions = value; } /** * If this property is set to true, the wallet will fail to load if any found extensions are unknown.. */ public void setRequireAllExtensionsKnown(boolean value) { requireAllExtensionsKnown = value; } /** * Change buffer size for writing wallet to output stream. Default is {@link com.google.protobuf.CodedOutputStream#DEFAULT_BUFFER_SIZE} * @param walletWriteBufferSize - buffer size in bytes */ public void setWalletWriteBufferSize(int walletWriteBufferSize) { this.walletWriteBufferSize = walletWriteBufferSize; } /** * Formats the given wallet (transactions and keys) to the given output stream in protocol buffer format.

* * Equivalent to {@code walletToProto(wallet).writeTo(output);} */ public void writeWallet(Wallet wallet, OutputStream output) throws IOException { Protos.Wallet walletProto = walletToProto(wallet); final CodedOutputStream codedOutput = CodedOutputStream.newInstance(output, this.walletWriteBufferSize); walletProto.writeTo(codedOutput); codedOutput.flush(); } /** * Returns the given wallet formatted as text. The text format is that used by protocol buffers and * it is designed more for debugging than storage. It is not well specified and wallets are largely binary data * structures anyway, consisting as they do of keys (large random numbers) and {@link Transaction}s which also * mostly contain keys and hashes. */ public String walletToText(Wallet wallet) { Protos.Wallet walletProto = walletToProto(wallet); return walletProto.toString(); } /** * Converts the given wallet to the object representation of the protocol buffers. This can be modified, or * additional data fields set, before serialization takes place. */ 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); } walletBuilder.addAllKey(wallet.serializeKeyChainGroupToProtobuf()); for (Script script : wallet.getWatchedScripts()) { Protos.Script protoScript = Protos.Script.newBuilder() .setProgram(ByteString.copyFrom(script.getProgram())) .setCreationTimestamp(script.getCreationTimeSeconds() * 1000) .build(); walletBuilder.addWatchedScript(protoScript); } // Populate the lastSeenBlockHash field. Sha256Hash lastSeenBlockHash = wallet.getLastBlockSeenHash(); if (lastSeenBlockHash != null) { walletBuilder.setLastSeenBlockHash(hashToByteString(lastSeenBlockHash)); walletBuilder.setLastSeenBlockHeight(wallet.getLastBlockSeenHeight()); } if (wallet.getLastBlockSeenTimeSecs() > 0) walletBuilder.setLastSeenBlockTimeSecs(wallet.getLastBlockSeenTimeSecs()); // 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."); } } if (wallet.getKeyRotationTime() != null) { long timeSecs = wallet.getKeyRotationTime().getTime() / 1000; walletBuilder.setKeyRotationTime(timeSecs); } populateExtensions(wallet, walletBuilder); for (Map.Entry entry : wallet.getTags().entrySet()) { Protos.Tag.Builder tag = Protos.Tag.newBuilder().setTag(entry.getKey()).setData(entry.getValue()); walletBuilder.addTags(tag); } // Populate the wallet version. walletBuilder.setVersion(wallet.getVersion()); //Add FriendKeyChains: Receiving if(wallet.receivingFromFriendsGroup != null && wallet.receivingFromFriendsGroup.hasKeyChains()) { List keys = wallet.receivingFromFriendsGroup.serializeToProtobuf(); walletBuilder.addAllKeysForFriends(keys); } //Add FriendKeyChains: Sending if(wallet.sendingToFriendsGroup != null && wallet.sendingToFriendsGroup.hasKeyChains()) { List keys = wallet.sendingToFriendsGroup.serializeToProtobuf(); walletBuilder.addAllKeysFromFriends(keys); } return walletBuilder.build(); } private static void populateExtensions(Wallet wallet, Protos.Wallet.Builder walletBuilder) { for (WalletExtension extension : wallet.getExtensions().values()) { Protos.Extension.Builder proto = Protos.Extension.newBuilder(); proto.setId(extension.getWalletExtensionID()); proto.setMandatory(extension.isWalletExtensionMandatory()); proto.setData(ByteString.copyFrom(extension.serializeWalletExtension())); walletBuilder.addExtension(proto); } for (KeyChainGroupExtension extension : wallet.getKeyChainExtensions().values()) { Protos.Extension.Builder proto = Protos.Extension.newBuilder(); proto.setId(extension.getWalletExtensionID()); proto.setMandatory(extension.isWalletExtensionMandatory()); proto.setData(ByteString.copyFrom(extension.serializeWalletExtension())); walletBuilder.addExtension(proto); } } private static Protos.Transaction makeTxProto(WalletTransaction wtx) { Transaction tx = wtx.getTransaction(); Protos.Transaction.Builder txBuilder = Protos.Transaction.newBuilder(); txBuilder.setPool(getProtoPool(wtx)) .setHash(hashToByteString(tx.getTxId())) .setVersion((int) tx.getVersion()); if (tx.getUpdateTime() != null) { txBuilder.setUpdatedAt(tx.getUpdateTime().getTime()); } if (tx.getLockTime() > 0) { txBuilder.setLockTime((int)tx.getLockTime()); } // Handle inputs. for (TransactionInput input : tx.getInputs()) { Protos.TransactionInput.Builder inputBuilder = Protos.TransactionInput.newBuilder() .setScriptBytes(ByteString.copyFrom(input.getScriptBytes())) .setTransactionOutPointHash(hashToByteString(input.getOutpoint().getHash())) .setTransactionOutPointIndex((int) input.getOutpoint().getIndex()); if (input.hasSequence()) inputBuilder.setSequence((int) input.getSequenceNumber()); if (input.getValue() != null) inputBuilder.setValue(input.getValue().value); txBuilder.addTransactionInput(inputBuilder); } // Handle outputs. for (TransactionOutput output : tx.getOutputs()) { Protos.TransactionOutput.Builder outputBuilder = Protos.TransactionOutput.newBuilder() .setScriptBytes(ByteString.copyFrom(output.getScriptBytes())) .setValue(output.getValue().value); final TransactionInput spentBy = output.getSpentBy(); if (spentBy != null) { Sha256Hash spendingHash = spentBy.getParentTransaction().getTxId(); outputBuilder.setSpentByTransactionHash(hashToByteString(spendingHash)) .setSpentByTransactionIndex(spentBy.getIndex()); } txBuilder.addTransactionOutput(outputBuilder); } // Handle which blocks tx was seen in. final Map appearsInHashes = tx.getAppearsInHashes(); if (appearsInHashes != null) { for (Map.Entry entry : appearsInHashes.entrySet()) { txBuilder.addBlockHash(hashToByteString(entry.getKey())); txBuilder.addBlockRelativityOffsets(entry.getValue()); } } if (tx.hasConfidence()) { TransactionConfidence confidence = tx.getConfidence(); Protos.TransactionConfidence.Builder confidenceBuilder = Protos.TransactionConfidence.newBuilder(); writeConfidence(txBuilder, confidence, confidenceBuilder); } Protos.Transaction.Purpose purpose; switch (tx.getPurpose()) { case UNKNOWN: purpose = Protos.Transaction.Purpose.UNKNOWN; break; case USER_PAYMENT: purpose = Protos.Transaction.Purpose.USER_PAYMENT; break; case KEY_ROTATION: purpose = Protos.Transaction.Purpose.KEY_ROTATION; break; case ASSURANCE_CONTRACT_CLAIM: purpose = Protos.Transaction.Purpose.ASSURANCE_CONTRACT_CLAIM; break; case ASSURANCE_CONTRACT_PLEDGE: purpose = Protos.Transaction.Purpose.ASSURANCE_CONTRACT_PLEDGE; break; case ASSURANCE_CONTRACT_STUB: purpose = Protos.Transaction.Purpose.ASSURANCE_CONTRACT_STUB; break; case RAISE_FEE: purpose = Protos.Transaction.Purpose.RAISE_FEE; break; default: throw new RuntimeException("New tx purpose serialization not implemented."); } txBuilder.setPurpose(purpose); ExchangeRate exchangeRate = tx.getExchangeRate(); if (exchangeRate != null) { Protos.ExchangeRate.Builder exchangeRateBuilder = Protos.ExchangeRate.newBuilder() .setCoinValue(exchangeRate.coin.value).setFiatValue(exchangeRate.fiat.value) .setFiatCurrencyCode(exchangeRate.fiat.currencyCode); txBuilder.setExchangeRate(exchangeRateBuilder); } if (tx.getMemo() != null) txBuilder.setMemo(tx.getMemo()); byte [] extraPayload = tx.getExtraPayload(); if(extraPayload != null && extraPayload.length > 0) txBuilder.setExtraPayload(ByteString.copyFrom(extraPayload)); return txBuilder.build(); } private static Protos.Transaction.Pool getProtoPool(WalletTransaction wtx) { switch (wtx.getPool()) { case UNSPENT: return Protos.Transaction.Pool.UNSPENT; case SPENT: return Protos.Transaction.Pool.SPENT; case DEAD: return Protos.Transaction.Pool.DEAD; case PENDING: return Protos.Transaction.Pool.PENDING; default: throw new RuntimeException("Unreachable"); } } private static void writeConfidence(Protos.Transaction.Builder txBuilder, TransactionConfidence confidence, Protos.TransactionConfidence.Builder confidenceBuilder) { synchronized (confidence) { confidenceBuilder.setType(Protos.TransactionConfidence.Type.forNumber(confidence.getConfidenceType().getValue())); if (confidence.getConfidenceType() == ConfidenceType.BUILDING) { confidenceBuilder.setAppearedAtHeight(confidence.getAppearedAtChainHeight()); confidenceBuilder.setDepth(confidence.getDepthInBlocks()); } if (confidence.getConfidenceType() == ConfidenceType.DEAD) { // Copy in the overriding transaction, if available. // (A dead coinbase transaction has no overriding transaction). if (confidence.getOverridingTransaction() != null) { Sha256Hash overridingHash = confidence.getOverridingTransaction().getTxId(); confidenceBuilder.setOverridingTransaction(hashToByteString(overridingHash)); } } TransactionConfidence.Source source = confidence.getSource(); switch (source) { case SELF: confidenceBuilder.setSource(Protos.TransactionConfidence.Source.SOURCE_SELF); break; case NETWORK: confidenceBuilder.setSource(Protos.TransactionConfidence.Source.SOURCE_NETWORK); break; case UNKNOWN: // Fall through. default: confidenceBuilder.setSource(Protos.TransactionConfidence.Source.SOURCE_UNKNOWN); break; } TransactionConfidence.IXType ixType = confidence.getIXType(); switch(ixType) { case IX_LOCKED: confidenceBuilder.setIxType(Protos.TransactionConfidence.IXType.IX_LOCKED); break; case IX_REQUEST: confidenceBuilder.setIxType(Protos.TransactionConfidence.IXType.IX_REQUEST); break; case IX_LOCK_FAILED: confidenceBuilder.setIxType(Protos.TransactionConfidence.IXType.IX_LOCK_FAILED); break; case IX_NONE: default: confidenceBuilder.setIxType(Protos.TransactionConfidence.IXType.IX_NONE); break; } InstantSendLock isLock = confidence.getInstantSendlock(); if (isLock != null) { Protos.InstantSendLock.Builder isLockProto = Protos.InstantSendLock.newBuilder(); for (TransactionOutPoint input: isLock.getInputs()) { isLockProto.addInputs(Protos.TransactionOutput.newBuilder() .setSpentByTransactionIndex((int)input.getIndex()) .setValue(0) .setSpentByTransactionHash(ByteString.copyFrom(input.getHash().getBytes())) .setScriptBytes(ByteString.EMPTY) .build()); } isLockProto.setSignature(ByteString.copyFrom(isLock.getSignature().bitcoinSerialize())); isLockProto.setTxid(ByteString.copyFrom(isLock.getHash().getBytes())); } } for (PeerAddress address : confidence.getBroadcastBy()) { Protos.PeerAddress proto = Protos.PeerAddress.newBuilder() .setIpAddress(ByteString.copyFrom(address.getAddr().getAddress())) .setPort(address.getPort()) .setServices(address.getServices().longValue()) .build(); confidenceBuilder.addBroadcastBy(proto); } Date lastBroadcastedAt = confidence.getLastBroadcastedAt(); if (lastBroadcastedAt != null) confidenceBuilder.setLastBroadcastedAt(lastBroadcastedAt.getTime()); confidenceBuilder.setMinConnections(confidence.getMinConnections()); confidenceBuilder.setPeerCount(confidence.getPeerCount()); Date sentAt = confidence.getSentAt(); if(sentAt != null) confidenceBuilder.setSentTime(sentAt.getTime()); if(confidence.hasRejections()) { Protos.RejectMessage.Builder builder = Protos.RejectMessage.newBuilder(); for(Map.Entry entry : confidence.getRejections().entrySet()) { Protos.RejectMessage.RejectCode code; RejectMessage reject = entry.getValue(); switch(entry.getValue().getReasonCode()) { case MALFORMED: code = Protos.RejectMessage.RejectCode.MALFORMED; break; case INVALID: code = Protos.RejectMessage.RejectCode.INVALID; break; case INSUFFICIENTFEE: code = Protos.RejectMessage.RejectCode.INSUFFICIENTFEE; break; case DUPLICATE: code = Protos.RejectMessage.RejectCode.DUPLICATE; break; case DUST: code = Protos.RejectMessage.RejectCode.DUST; break; case OBSOLETE: code = Protos.RejectMessage.RejectCode.OBSOLETE; break; case NONSTANDARD: code = Protos.RejectMessage.RejectCode.NONSTANDARD; break; case OTHER: default: code = Protos.RejectMessage.RejectCode.OTHER; break; } builder.setCode(code); builder.setMessage(reject.getRejectedMessage()); builder.setReason(reject.getReasonString()); PeerAddress address = entry.getKey(); Protos.PeerAddress peerProto = Protos.PeerAddress.newBuilder() .setIpAddress(ByteString.copyFrom(address.getAddr().getAddress())) .setPort(address.getPort()) .setServices(address.getServices().longValue()) .build(); builder.setPeer(peerProto); confidenceBuilder.addRejects(builder.build()); } } txBuilder.setConfidence(confidenceBuilder); } public static ByteString hashToByteString(Sha256Hash hash) { return ByteString.copyFrom(hash.getBytes()); } public static Sha256Hash byteStringToHash(ByteString bs) { return Sha256Hash.wrap(bs.toByteArray()); } /** *

Loads wallet data from the given protocol buffer and inserts it into the given Wallet object. This is primarily * useful when you wish to pre-register extension objects. Note that if loading fails the provided Wallet object * may be in an indeterminate state and should be thrown away.

* *

A wallet can be unreadable for various reasons, such as inability to open the file, corrupt data, internally * inconsistent data, a wallet extension marked as mandatory that cannot be handled and so on. You should always * handle {@link UnreadableWalletException} and communicate failure to the user in an appropriate manner.

* * @throws UnreadableWalletException thrown in various error conditions (see description). */ public Wallet readWallet(InputStream input, @Nullable WalletExtension... walletExtensions) throws UnreadableWalletException { return readWallet(input, false, walletExtensions); } /** *

Loads wallet data from the given protocol buffer and inserts it into the given Wallet object. This is primarily * useful when you wish to pre-register extension objects. Note that if loading fails the provided Wallet object * may be in an indeterminate state and should be thrown away. Do not simply call this method again on the same * Wallet object with {@code forceReset} set {@code true}. It won't work.

* *

If {@code forceReset} is {@code true}, then no transactions are loaded from the wallet, and it is configured * to replay transactions from the blockchain (as if the wallet had been loaded and {@link Wallet#reset()} * had been called immediately thereafter). * *

A wallet can be unreadable for various reasons, such as inability to open the file, corrupt data, internally * inconsistent data, a wallet extension marked as mandatory that cannot be handled and so on. You should always * handle {@link UnreadableWalletException} and communicate failure to the user in an appropriate manner.

* * @throws UnreadableWalletException thrown in various error conditions (see description). */ public Wallet readWallet(InputStream input, boolean forceReset, @Nullable WalletExtension[] extensions) throws UnreadableWalletException { try { Protos.Wallet walletProto = parseToProto(input); final String paramsID = walletProto.getNetworkIdentifier(); NetworkParameters params = NetworkParameters.fromID(paramsID); if (params == null) throw new UnreadableWalletException("Unknown network parameters ID " + paramsID); return readWallet(params, extensions, walletProto, forceReset); } catch (IOException e) { throw new UnreadableWalletException("Could not parse input stream to protobuf", e); } catch (IllegalStateException e) { throw new UnreadableWalletException("Could not parse input stream to protobuf", e); } catch (IllegalArgumentException e) { throw new UnreadableWalletException("Could not parse input stream to protobuf", e); } } /** *

Loads wallet data from the given protocol buffer and inserts it into the given Wallet object. This is primarily * useful when you wish to pre-register extension objects. Note that if loading fails the provided Wallet object * may be in an indeterminate state and should be thrown away.

* *

A wallet can be unreadable for various reasons, such as inability to open the file, corrupt data, internally * inconsistent data, a wallet extension marked as mandatory that cannot be handled and so on. You should always * handle {@link UnreadableWalletException} and communicate failure to the user in an appropriate manner.

* * @throws UnreadableWalletException thrown in various error conditions (see description). */ public Wallet readWallet(NetworkParameters params, @Nullable WalletExtension[] extensions, Protos.Wallet walletProto) throws UnreadableWalletException { return readWallet(params, extensions, walletProto, false); } /** *

Loads wallet data from the given protocol buffer and inserts it into the given Wallet object. This is primarily * useful when you wish to pre-register extension objects. Note that if loading fails the provided Wallet object * may be in an indeterminate state and should be thrown away. Do not simply call this method again on the same * Wallet object with {@code forceReset} set {@code true}. It won't work.

* *

If {@code forceReset} is {@code true}, then no transactions are loaded from the wallet, and it is configured * to replay transactions from the blockchain (as if the wallet had been loaded and {@link Wallet#reset()} * had been called immediately thereafter). * *

A wallet can be unreadable for various reasons, such as inability to open the file, corrupt data, internally * inconsistent data, a wallet extension marked as mandatory that cannot be handled and so on. You should always * handle {@link UnreadableWalletException} and communicate failure to the user in an appropriate manner.

* * @throws UnreadableWalletException thrown in various error conditions (see description). */ public Wallet readWallet(NetworkParameters params, @Nullable WalletExtension[] extensions, Protos.Wallet walletProto, boolean forceReset) throws UnreadableWalletException { if (walletProto.getVersion() > CURRENT_WALLET_VERSION) throw new UnreadableWalletException.FutureVersion(); if (!walletProto.getNetworkIdentifier().equals(params.getId())) throw new UnreadableWalletException.WrongNetwork(); // Read the scrypt parameters that specify how encryption and decryption is performed. KeyChainGroup keyChainGroup; if (walletProto.hasEncryptionParameters()) { Protos.ScryptParameters encryptionParameters = walletProto.getEncryptionParameters(); final KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(encryptionParameters); keyChainGroup = KeyChainGroup.fromProtobufEncrypted(params, walletProto.getKeyList(), keyCrypter, keyChainFactory); } else { keyChainGroup = KeyChainGroup.fromProtobufUnencrypted(params, walletProto.getKeyList(), keyChainFactory); } Wallet wallet = factory.create(params, keyChainGroup); List