private void loadWalletFromProtobuf() { if (walletFile.exists()) { final long start = System.currentTimeMillis(); FileInputStream walletStream = null; try { walletStream = new FileInputStream(walletFile); wallet = new WalletProtobufSerializer().readWallet(walletStream); log.info( "wallet loaded from: '" + walletFile + "', took " + (System.currentTimeMillis() - start) + "ms"); } catch (final FileNotFoundException x) { log.error("problem loading wallet", x); Toast.makeText(WalletApplication.this, x.getClass().getName(), Toast.LENGTH_LONG).show(); wallet = restoreWalletFromBackup(); } catch (final UnreadableWalletException x) { log.error("problem loading wallet", x); Toast.makeText(WalletApplication.this, x.getClass().getName(), Toast.LENGTH_LONG).show(); wallet = restoreWalletFromBackup(); } finally { if (walletStream != null) { try { walletStream.close(); } catch (final IOException x) { // swallow } } } if (!wallet.isConsistent()) { Toast.makeText(this, "inconsistent wallet: " + walletFile, Toast.LENGTH_LONG).show(); wallet = restoreWalletFromBackup(); } if (!wallet.getParams().equals(Constants.NETWORK_PARAMETERS)) throw new Error("bad wallet network parameters: " + wallet.getParams().getId()); } else { wallet = new Wallet(Constants.NETWORK_PARAMETERS); log.info("new wallet created"); } // this check is needed so encrypted wallets won't get their private keys removed accidently for (final ECKey key : wallet.getKeys()) if (key.getPrivKeyBytes() == null) throw new Error( "found read-only key, but wallet is likely an encrypted wallet from the future"); }
private synchronized Transaction makeUnsignedChannelContract(Coin valueToMe) throws ValueOutOfRangeException { Transaction tx = new Transaction(wallet.getParams()); tx.addInput(multisigContract.getOutput(0)); // Our output always comes first. // TODO: We should drop myKey in favor of output key + multisig key separation // (as its always obvious who the client is based on T2 output order) tx.addOutput(valueToMe, myKey.toAddress(wallet.getParams())); return tx; }
@Override public void deserializeWalletExtension(Wallet containingWallet, byte[] data) throws Exception { lock.lock(); try { checkState(this.containingWallet == null || this.containingWallet == containingWallet); this.containingWallet = containingWallet; NetworkParameters params = containingWallet.getParams(); ClientState.StoredClientPaymentChannels states = ClientState.StoredClientPaymentChannels.parseFrom(data); for (ClientState.StoredClientPaymentChannel storedState : states.getChannelsList()) { Transaction refundTransaction = new Transaction(params, storedState.getRefundTransaction().toByteArray()); refundTransaction.getConfidence().setSource(TransactionConfidence.Source.SELF); StoredClientChannel channel = new StoredClientChannel( new Sha256Hash(storedState.getId().toByteArray()), new Transaction(params, storedState.getContractTransaction().toByteArray()), refundTransaction, new ECKey(new BigInteger(1, storedState.getMyKey().toByteArray()), null, true), BigInteger.valueOf(storedState.getValueToMe()), BigInteger.valueOf(storedState.getRefundFees()), false); if (storedState.hasCloseTransactionHash()) channel.close = containingWallet.getTransaction(new Sha256Hash(storedState.toByteArray())); putChannel(channel, false); } } finally { lock.unlock(); } }
/** * Creates the initial multisig contract and incomplete refund transaction which can be requested * at the appropriate time using {@link PaymentChannelClientState#getIncompleteRefundTransaction} * and {@link PaymentChannelClientState#getMultisigContract()}. The way the contract is crafted * can be adjusted by overriding {@link * PaymentChannelClientState#editContractSendRequest(com.google.bitcoin.core.Wallet.SendRequest)}. * By default unconfirmed coins are allowed to be used, as for micropayments the risk should be * relatively low. * * @throws ValueOutOfRangeException if the value being used is too small to be accepted by the * network * @throws InsufficientMoneyException if the wallet doesn't contain enough balance to initiate */ public synchronized void initiate() throws ValueOutOfRangeException, InsufficientMoneyException { final NetworkParameters params = wallet.getParams(); Transaction template = new Transaction(params); // We always place the client key before the server key because, if either side wants some // privacy, they can // use a fresh key for the the multisig contract and nowhere else List<ECKey> keys = Lists.newArrayList(myKey, serverMultisigKey); // There is also probably a change output, but we don't bother shuffling them as it's obvious // from the // format which one is the change. If we start obfuscating the change output better in future // this may // be worth revisiting. TransactionOutput multisigOutput = template.addOutput(totalValue, ScriptBuilder.createMultiSigOutputScript(2, keys)); if (multisigOutput.getMinNonDustValue().compareTo(totalValue) > 0) throw new ValueOutOfRangeException("totalValue too small to use"); Wallet.SendRequest req = Wallet.SendRequest.forTx(template); req.coinSelector = AllowUnconfirmedCoinSelector.get(); editContractSendRequest(req); req.shuffleOutputs = false; // TODO: Fix things so shuffling is usable. wallet.completeTx(req); Coin multisigFee = req.tx.getFee(); multisigContract = req.tx; // Build a refund transaction that protects us in the case of a bad server that's just trying to // cause havoc // by locking up peoples money (perhaps as a precursor to a ransom attempt). We time lock it so // the server // has an assurance that we cannot take back our money by claiming a refund before the channel // closes - this // relies on the fact that since Bitcoin 0.8 time locked transactions are non-final. This will // need to change // in future as it breaks the intended design of timelocking/tx replacement, but for now it // simplifies this // specific protocol somewhat. refundTx = new Transaction(params); refundTx .addInput(multisigOutput) .setSequenceNumber(0); // Allow replacement when it's eventually reactivated. refundTx.setLockTime(expiryTime); if (totalValue.compareTo(Coin.CENT) < 0) { // Must pay min fee. final Coin valueAfterFee = totalValue.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE); if (Transaction.MIN_NONDUST_OUTPUT.compareTo(valueAfterFee) > 0) throw new ValueOutOfRangeException("totalValue too small to use"); refundTx.addOutput(valueAfterFee, myKey.toAddress(params)); refundFees = multisigFee.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE); } else { refundTx.addOutput(totalValue, myKey.toAddress(params)); refundFees = multisigFee; } refundTx.getConfidence().setSource(TransactionConfidence.Source.SELF); log.info( "initiated channel with multi-sig contract {}, refund {}", multisigContract.getHashAsString(), refundTx.getHashAsString()); state = State.INITIATED; // Client should now call getIncompleteRefundTransaction() and send it to the server. }
/** * 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. * * @throws IOException if there is a problem reading the stream. * @throws IllegalArgumentException if the wallet is corrupt. */ public void readWallet(Protos.Wallet walletProto, Wallet wallet) throws IOException { // TODO: This method should throw more specific exception types than IllegalArgumentException. // Read the scrypt parameters that specify how encryption and decryption is performed. if (walletProto.hasEncryptionParameters()) { Protos.ScryptParameters encryptionParameters = walletProto.getEncryptionParameters(); wallet.setKeyCrypter(new KeyCrypterScrypt(encryptionParameters)); } if (walletProto.hasDescription()) { wallet.setDescription(walletProto.getDescription()); } // Read all keys for (Protos.Key keyProto : walletProto.getKeyList()) { 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 = 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; final KeyCrypter keyCrypter = wallet.getKeyCrypter(); 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, 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()); } loadExtensions(wallet, walletProto); if (walletProto.hasVersion()) { wallet.setVersion(walletProto.getVersion()); } // Make sure the object can be re-used to read another wallet without corruption. txMap.clear(); }