/** * Parses a wallet from the given stream, using the provided Wallet instance to load data into. * This is primarily used when you want to register extensions. Data in the proto will be added * into the wallet where applicable and overwrite where not. * * <p>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) 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, null, walletProto); } 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); } }
private void loadExtensions( Wallet wallet, WalletExtension[] extensionsList, Protos.Wallet walletProto) throws UnreadableWalletException { final Map<String, WalletExtension> extensions = new HashMap<String, WalletExtension>(); for (WalletExtension e : extensionsList) extensions.put(e.getWalletExtensionID(), e); // The Wallet object, if subclassed, might have added some extensions to itself already. In that // case, don't // expect them to be passed in, just fetch them here and don't re-add. extensions.putAll(wallet.getExtensions()); for (Protos.Extension extProto : walletProto.getExtensionList()) { String id = extProto.getId(); WalletExtension extension = extensions.get(id); if (extension == null) { if (extProto.getMandatory()) { if (requireMandatoryExtensions) throw new UnreadableWalletException("Unknown mandatory extension in wallet: " + id); else log.error("Unknown extension in wallet {}, ignoring", id); } } else { log.info("Loading wallet extension {}", id); try { extension.deserializeWalletExtension(wallet, extProto.getData().toByteArray()); wallet.addOrGetExistingExtension(extension); } catch (Exception e) { if (extProto.getMandatory() && requireMandatoryExtensions) throw new UnreadableWalletException( "Could not parse mandatory extension in wallet: " + id); else log.error("Error whilst reading extension {}, ignoring", id, e); } } } }
/** * Returns the loaded protocol buffer from the given byte stream. You normally want {@link * Wallet#loadFromFile(java.io.File)} instead - this method is designed for low level work * involving the wallet file format itself. */ public static Protos.Wallet parseToProto(InputStream input) throws IOException { return Protos.Wallet.parseFrom(input); }
/** * 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. * * <p>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 { if (walletProto.getVersion() > 1) throw new UnreadableWalletException.FutureVersion(); if (!walletProto.getNetworkIdentifier().equals(params.getId())) throw new UnreadableWalletException.WrongNetwork(); int sigsRequiredToSpend = walletProto.getSigsRequiredToSpend(); // Read the scrypt parameters that specify how encryption and decryption is performed. KeyChainGroup chain; if (walletProto.hasEncryptionParameters()) { Protos.ScryptParameters encryptionParameters = walletProto.getEncryptionParameters(); final KeyCrypterScrypt keyCrypter = new KeyCrypterScrypt(encryptionParameters); chain = KeyChainGroup.fromProtobufEncrypted( params, walletProto.getKeyList(), sigsRequiredToSpend, keyCrypter); } else { chain = KeyChainGroup.fromProtobufUnencrypted( params, walletProto.getKeyList(), sigsRequiredToSpend); } Wallet wallet = factory.create(params, chain); List<Script> scripts = Lists.newArrayList(); for (Protos.Script protoScript : walletProto.getWatchedScriptList()) { try { Script script = new Script( protoScript.getProgram().toByteArray(), protoScript.getCreationTimestamp() / 1000); scripts.add(script); } catch (ScriptException e) { throw new UnreadableWalletException("Unparseable script in wallet"); } } wallet.addWatchedScripts(scripts); if (walletProto.hasDescription()) { wallet.setDescription(walletProto.getDescription()); } // Read all transactions and insert into the txMap. for (Protos.Transaction txProto : walletProto.getTransactionList()) { readTransaction(txProto, wallet.getParams()); } // Update transaction outputs to point to inputs that spend them for (Protos.Transaction txProto : walletProto.getTransactionList()) { WalletTransaction wtx = connectTransactionOutputs(txProto); wallet.addWalletTransaction(wtx); } // Update the lastBlockSeenHash. if (!walletProto.hasLastSeenBlockHash()) { wallet.setLastBlockSeenHash(null); } else { wallet.setLastBlockSeenHash(byteStringToHash(walletProto.getLastSeenBlockHash())); } if (!walletProto.hasLastSeenBlockHeight()) { wallet.setLastBlockSeenHeight(-1); } else { wallet.setLastBlockSeenHeight(walletProto.getLastSeenBlockHeight()); } // Will default to zero if not present. wallet.setLastBlockSeenTimeSecs(walletProto.getLastSeenBlockTimeSecs()); if (walletProto.hasKeyRotationTime()) { wallet.setKeyRotationTime(new Date(walletProto.getKeyRotationTime() * 1000)); } loadExtensions(wallet, extensions != null ? extensions : new WalletExtension[0], walletProto); for (Protos.Tag tag : walletProto.getTagsList()) { wallet.setTag(tag.getTag(), tag.getData()); } for (Protos.TransactionSigner signerProto : walletProto.getTransactionSignersList()) { try { Class signerClass = Class.forName(signerProto.getClassName()); TransactionSigner signer = (TransactionSigner) signerClass.newInstance(); signer.deserialize(signerProto.getData().toByteArray()); wallet.addTransactionSigner(signer); } catch (Exception e) { throw new UnreadableWalletException( "Unable to deserialize TransactionSigner instance: " + signerProto.getClassName(), e); } } if (walletProto.hasVersion()) { wallet.setVersion(walletProto.getVersion()); } // Make sure the object can be re-used to read another wallet without corruption. txMap.clear(); return wallet; }
/** * 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.serializeKeychainToProtobuf()); 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<String, ByteString> entry : wallet.getTags().entrySet()) { Protos.Tag.Builder tag = Protos.Tag.newBuilder().setTag(entry.getKey()).setData(entry.getValue()); walletBuilder.addTags(tag); } for (TransactionSigner signer : wallet.getTransactionSigners()) { // do not serialize LocalTransactionSigner as it's being added implicitly if (signer instanceof LocalTransactionSigner) continue; Protos.TransactionSigner.Builder protoSigner = Protos.TransactionSigner.newBuilder(); protoSigner.setClassName(signer.getClass().getName()); protoSigner.setData(ByteString.copyFrom(signer.serialize())); walletBuilder.addTransactionSigners(protoSigner); } walletBuilder.setSigsRequiredToSpend(wallet.getSigsRequiredToSpend()); // Populate the wallet version. walletBuilder.setVersion(wallet.getVersion()); return walletBuilder.build(); }
/** * Formats the given wallet (transactions and keys) to the given output stream in protocol buffer * format. * * <p>Equivalent to <tt>walletToProto(wallet).writeTo(output);</tt> */ public void writeWallet(Wallet wallet, OutputStream output) throws IOException { Protos.Wallet walletProto = walletToProto(wallet); walletProto.writeTo(output); }