@Test public void deterministicUpgradeEncrypted() throws Exception { group = new KeyChainGroup(params); final ECKey key = new ECKey(); group.importKeys(key); final KeyCrypterScrypt crypter = new KeyCrypterScrypt(); final KeyParameter aesKey = crypter.deriveKey("abc"); assertTrue(group.isDeterministicUpgradeRequired()); group.encrypt(crypter, aesKey); assertTrue(group.isDeterministicUpgradeRequired()); try { group.upgradeToDeterministic(0, null); fail(); } catch (DeterministicUpgradeRequiresPassword e) { // Expected. } group.upgradeToDeterministic(0, aesKey); assertFalse(group.isDeterministicUpgradeRequired()); final DeterministicSeed deterministicSeed = group.getActiveKeyChain().getSeed(); assertNotNull(deterministicSeed); assertTrue(deterministicSeed.isEncrypted()); byte[] entropy = checkNotNull(group.getActiveKeyChain().toDecrypted(aesKey).getSeed()).getEntropyBytes(); // Check we used the right key: oldest non rotating. byte[] truncatedBytes = Arrays.copyOfRange(key.getSecretBytes(), 0, 16); assertArrayEquals(entropy, truncatedBytes); }
@Test public void deterministicUpgradeUnencrypted() throws Exception { // Check that a group that contains only random keys has its HD chain created using the private // key bytes of // the oldest random key, so upgrading the same wallet twice gives the same outcome. group = new KeyChainGroup(params); group.setLookaheadSize(LOOKAHEAD_SIZE); // Don't want slow tests. ECKey key1 = new ECKey(); Utils.rollMockClock(86400); ECKey key2 = new ECKey(); group.importKeys(key2, key1); List<Protos.Key> protobufs = group.serializeToProtobuf(); group.upgradeToDeterministic(0, null); assertFalse(group.isDeterministicUpgradeRequired()); DeterministicKey dkey1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicSeed seed1 = group.getActiveKeyChain().getSeed(); assertNotNull(seed1); group = KeyChainGroup.fromProtobufUnencrypted(params, protobufs); group.upgradeToDeterministic(0, null); // Should give same result as last time. DeterministicKey dkey2 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicSeed seed2 = group.getActiveKeyChain().getSeed(); assertEquals(seed1, seed2); assertEquals(dkey1, dkey2); // Check we used the right (oldest) key despite backwards import order. byte[] truncatedBytes = Arrays.copyOfRange(key1.getSecretBytes(), 0, 16); assertArrayEquals(seed1.getEntropyBytes(), truncatedBytes); }
@Test public void bloom() throws Exception { ECKey key1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); ECKey key2 = new ECKey(); BloomFilter filter = group.getBloomFilter( group.getBloomFilterElementCount(), 0.001, (long) (Math.random() * Long.MAX_VALUE)); assertTrue(filter.contains(key1.getPubKeyHash())); assertTrue(filter.contains(key1.getPubKey())); assertFalse(filter.contains(key2.getPubKey())); // Check that the filter contains the lookahead buffer and threshold zone. for (int i = 0; i < LOOKAHEAD_SIZE + group.getLookaheadThreshold(); i++) { ECKey k = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); assertTrue(filter.contains(k.getPubKeyHash())); } // We ran ahead of the lookahead buffer. assertFalse(filter.contains(group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).getPubKey())); group.importKeys(key2); filter = group.getBloomFilter( group.getBloomFilterElementCount(), 0.001, (long) (Math.random() * Long.MAX_VALUE)); assertTrue(filter.contains(key1.getPubKeyHash())); assertTrue(filter.contains(key1.getPubKey())); assertTrue(filter.contains(key2.getPubKey())); }
@Test public void testCreateMultiSigInputScript() throws AddressFormatException { // Setup transaction and signatures ECKey key1 = new DumpedPrivateKey(params, "cVLwRLTvz3BxDAWkvS3yzT9pUcTCup7kQnfT2smRjvmmm1wAP6QT") .getKey(); ECKey key2 = new DumpedPrivateKey(params, "cTine92s8GLpVqvebi8rYce3FrUYq78ZGQffBYCS1HmDPJdSTxUo") .getKey(); ECKey key3 = new DumpedPrivateKey(params, "cVHwXSPRZmL9adctwBwmn4oTZdZMbaCsR5XF6VznqMgcvt1FDDxg") .getKey(); Script multisigScript = ScriptBuilder.createMultiSigOutputScript(2, Arrays.asList(key1, key2, key3)); byte[] bytes = Hex.decode( "01000000013df681ff83b43b6585fa32dd0e12b0b502e6481e04ee52ff0fdaf55a16a4ef61000000006b483045022100a84acca7906c13c5895a1314c165d33621cdcf8696145080895cbf301119b7cf0220730ff511106aa0e0a8570ff00ee57d7a6f24e30f592a10cae1deffac9e13b990012102b8d567bcd6328fd48a429f9cf4b315b859a58fd28c5088ef3cb1d98125fc4e8dffffffff02364f1c00000000001976a91439a02793b418de8ec748dd75382656453dc99bcb88ac40420f000000000017a9145780b80be32e117f675d6e0ada13ba799bf248e98700000000"); Transaction transaction = new Transaction(params, bytes); TransactionOutput output = transaction.getOutput(1); Transaction spendTx = new Transaction(params); Address address = new Address(params, "n3CFiCmBXVt5d3HXKQ15EFZyhPz4yj5F3H"); Script outputScript = ScriptBuilder.createOutputScript(address); spendTx.addOutput(output.getValue(), outputScript); spendTx.addInput(output); Sha256Hash sighash = spendTx.hashForSignature(0, multisigScript, SigHash.ALL, false); ECKey.ECDSASignature party1Signature = key1.sign(sighash); ECKey.ECDSASignature party2Signature = key2.sign(sighash); TransactionSignature party1TransactionSignature = new TransactionSignature(party1Signature, SigHash.ALL, false); TransactionSignature party2TransactionSignature = new TransactionSignature(party2Signature, SigHash.ALL, false); // Create p2sh multisig input script Script inputScript = ScriptBuilder.createP2SHMultiSigInputScript( ImmutableList.of(party1TransactionSignature, party2TransactionSignature), multisigScript.getProgram()); // Assert that the input script contains 4 chunks assertTrue(inputScript.getChunks().size() == 4); // Assert that the input script created contains the original multisig // script as the last chunk ScriptChunk scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1); Assert.assertArrayEquals(scriptChunk.data, multisigScript.getProgram()); // Create regular multisig input script inputScript = ScriptBuilder.createMultiSigInputScript( ImmutableList.of(party1TransactionSignature, party2TransactionSignature)); // Assert that the input script only contains 3 chunks assertTrue(inputScript.getChunks().size() == 3); // Assert that the input script created does not end with the original // multisig script scriptChunk = inputScript.getChunks().get(inputScript.getChunks().size() - 1); Assert.assertThat(scriptChunk.data, IsNot.not(IsEqual.equalTo(multisigScript.getProgram()))); }
/** * 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. }
@Test public void testGetOpenTransactionOutputs() throws Exception { final int UNDOABLE_BLOCKS_STORED = 10; store = createStore(params, UNDOABLE_BLOCKS_STORED); chain = new FullPrunedBlockChain(params, store); // Check that we aren't accidentally leaving any references // to the full StoredUndoableBlock's lying around (ie memory leaks) ECKey outKey = new ECKey(); int height = 1; // Build some blocks on genesis block to create a spendable output Block rollingBlock = params .getGenesisBlock() .createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); Transaction transaction = rollingBlock.getTransactions().get(0); TransactionOutPoint spendableOutput = new TransactionOutPoint(params, 0, transaction.getHash()); byte[] spendableOutputScriptPubKey = transaction.getOutputs().get(0).getScriptBytes(); for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) { rollingBlock = rollingBlock.createNextBlockWithCoinbase( Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); } rollingBlock = rollingBlock.createNextBlock(null); // Create bitcoin spend of 1 BTC. ECKey toKey = new ECKey(); Coin amount = Coin.valueOf(100000000); Address address = new Address(params, toKey.getPubKeyHash()); Coin totalAmount = Coin.ZERO; Transaction t = new Transaction(params); t.addOutput(new TransactionOutput(params, t, amount, toKey)); t.addSignedInput(spendableOutput, new Script(spendableOutputScriptPubKey), outKey); rollingBlock.addTransaction(t); rollingBlock.solve(); chain.add(rollingBlock); totalAmount = totalAmount.add(amount); List<UTXO> outputs = store.getOpenTransactionOutputs(Lists.newArrayList(address)); assertNotNull(outputs); assertEquals("Wrong Number of Outputs", 1, outputs.size()); UTXO output = outputs.get(0); assertEquals("The address is not equal", address.toString(), output.getAddress()); assertEquals("The amount is not equal", totalAmount, output.getValue()); outputs = null; output = null; try { store.close(); } catch (Exception e) { } }
/** Test that if the block height is missing from coinbase of a version 2 block, it's rejected. */ @Test public void missingHeightFromCoinbase() throws Exception { final int UNDOABLE_BLOCKS_STORED = params.getMajorityEnforceBlockUpgrade() + 1; store = createStore(params, UNDOABLE_BLOCKS_STORED); try { chain = new FullPrunedBlockChain(params, store); ECKey outKey = new ECKey(); int height = 1; Block chainHead = params.getGenesisBlock(); // Build some blocks on genesis block to create a spendable output. // Put in just enough v1 blocks to stop the v2 blocks from forming a majority for (height = 1; height <= (params.getMajorityWindow() - params.getMajorityEnforceBlockUpgrade()); height++) { chainHead = chainHead.createNextBlockWithCoinbase( Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height); chain.add(chainHead); } // Fill the rest of the window in with v2 blocks for (; height < params.getMajorityWindow(); height++) { chainHead = chainHead.createNextBlockWithCoinbase( Block.BLOCK_VERSION_BIP34, outKey.getPubKey(), height); chain.add(chainHead); } // Throw a broken v2 block in before we have a supermajority to enable // enforcement, which should validate as-is chainHead = chainHead.createNextBlockWithCoinbase( Block.BLOCK_VERSION_BIP34, outKey.getPubKey(), height * 2); chain.add(chainHead); height++; // Trying to add a broken v2 block should now result in rejection as // we have a v2 supermajority thrown.expect(VerificationException.CoinbaseHeightMismatch.class); chainHead = chainHead.createNextBlockWithCoinbase( Block.BLOCK_VERSION_BIP34, outKey.getPubKey(), height * 2); chain.add(chainHead); } catch (final VerificationException ex) { throw (Exception) ex.getCause(); } finally { try { store.close(); } catch (Exception e) { // Catch and drop any exception so a break mid-test doesn't result // in a new exception being thrown and the original lost } } }
@Test public void encryptionWhilstEmpty() throws Exception { group = new KeyChainGroup(params); group.setLookaheadSize(5); KeyCrypterScrypt scrypt = new KeyCrypterScrypt(2); final KeyParameter aesKey = scrypt.deriveKey("password"); group.encrypt(scrypt, aesKey); assertTrue(group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS).isEncrypted()); final ECKey key = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); group.decrypt(aesKey); assertFalse(checkNotNull(group.findKeyFromPubKey(key.getPubKey())).isEncrypted()); }
@Test public void testKeys() throws Exception { for (int i = 0; i < 20; i++) { myKey = new ECKey(); myAddress = myKey.toAddress(params); myWallet = new Wallet(params); myWallet.importKey(myKey); Wallet wallet1 = roundTrip(myWallet); assertArrayEquals( myKey.getPubKey(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPubKey()); assertArrayEquals( myKey.getPrivKeyBytes(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes()); } }
@Test public void earliestKeyTime() throws Exception { long now = Utils.currentTimeSeconds(); // mock long yesterday = now - 86400; assertEquals(now, group.getEarliestKeyCreationTime()); Utils.rollMockClock(10000); group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); Utils.rollMockClock(10000); group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); // Check that all keys are assumed to be created at the same instant the seed is. assertEquals(now, group.getEarliestKeyCreationTime()); ECKey key = new ECKey(); key.setCreationTimeSeconds(yesterday); group.importKeys(key); assertEquals(yesterday, group.getEarliestKeyCreationTime()); }
@Test public void testSimplePayment() throws Exception { // Create a PaymentRequest and make sure the correct values are parsed by the PaymentSession. MockPaymentSession paymentSession = new MockPaymentSession(newSimplePaymentRequest()); assertEquals(paymentRequestMemo, paymentSession.getMemo()); assertEquals(nanoCoins, paymentSession.getValue()); assertEquals(simplePaymentUrl, paymentSession.getPaymentUrl()); assertTrue(new Date(time * 1000L).equals(paymentSession.getDate())); assertTrue(paymentSession.getSendRequest().tx.equals(tx)); assertFalse(paymentSession.isExpired()); // Send the payment and verify that the correct information is sent. // Add a dummy input to tx so it is considered valid. tx.addInput(new TransactionInput(params, tx, outputToMe.getScriptBytes())); ArrayList<Transaction> txns = new ArrayList<Transaction>(); txns.add(tx); Address refundAddr = new Address(params, serverKey.getPubKeyHash()); paymentSession.sendPayment(txns, refundAddr, paymentMemo); assertEquals(1, paymentSession.getPaymentLog().size()); assertEquals(simplePaymentUrl, paymentSession.getPaymentLog().get(0).getUrl().toString()); Protos.Payment payment = paymentSession.getPaymentLog().get(0).getPayment(); assertEquals(paymentMemo, payment.getMemo()); assertEquals(merchantData, payment.getMerchantData()); assertEquals(1, payment.getRefundToCount()); assertEquals(nanoCoins.longValue(), payment.getRefundTo(0).getAmount()); TransactionOutput refundOutput = new TransactionOutput(params, null, nanoCoins, refundAddr); ByteString refundScript = ByteString.copyFrom(refundOutput.getScriptBytes()); assertTrue(refundScript.equals(payment.getRefundTo(0).getScript())); }
@Before public void setUp() throws Exception { BriefLogFormatter.initVerbose(); Context ctx = new Context(params); myWatchedKey = new ECKey(); myWallet = new Wallet(params); myKey = new ECKey(); myKey.setCreationTimeSeconds(123456789L); myWallet.importKey(myKey); myAddress = myKey.toAddress(params); myWallet = new Wallet(params); myWallet.importKey(myKey); mScriptCreationTime = new Date().getTime() / 1000 - 1234; myWallet.addWatchedAddress(myWatchedKey.toAddress(params), mScriptCreationTime); myWallet.setDescription(WALLET_DESCRIPTION); }
@Test public void deterministicUpgradeRotating() throws Exception { group = new KeyChainGroup(params); group.setLookaheadSize(LOOKAHEAD_SIZE); // Don't want slow tests. long now = Utils.currentTimeSeconds(); ECKey key1 = new ECKey(); Utils.rollMockClock(86400); ECKey key2 = new ECKey(); Utils.rollMockClock(86400); ECKey key3 = new ECKey(); group.importKeys(key2, key1, key3); group.upgradeToDeterministic(now + 10, null); DeterministicSeed seed = group.getActiveKeyChain().getSeed(); assertNotNull(seed); // Check we used the right key: oldest non rotating. byte[] truncatedBytes = Arrays.copyOfRange(key2.getSecretBytes(), 0, 16); assertArrayEquals(seed.getEntropyBytes(), truncatedBytes); }
@Test public void skipScripts() throws Exception { store = createStore(params, 10); chain = new FullPrunedBlockChain(params, store); // Check that we aren't accidentally leaving any references // to the full StoredUndoableBlock's lying around (ie memory leaks) ECKey outKey = new ECKey(); int height = 1; // Build some blocks on genesis block to create a spendable output Block rollingBlock = params .getGenesisBlock() .createNextBlockWithCoinbase(Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); TransactionOutput spendableOutput = rollingBlock.getTransactions().get(0).getOutput(0); for (int i = 1; i < params.getSpendableCoinbaseDepth(); i++) { rollingBlock = rollingBlock.createNextBlockWithCoinbase( Block.BLOCK_VERSION_GENESIS, outKey.getPubKey(), height++); chain.add(rollingBlock); } rollingBlock = rollingBlock.createNextBlock(null); Transaction t = new Transaction(params); t.addOutput(new TransactionOutput(params, t, FIFTY_COINS, new byte[] {})); TransactionInput input = t.addInput(spendableOutput); // Invalid script. input.clearScriptBytes(); rollingBlock.addTransaction(t); rollingBlock.solve(); chain.setRunScripts(false); try { chain.add(rollingBlock); } catch (VerificationException e) { fail(); } try { store.close(); } catch (Exception e) { } }
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; }
private static void addKey() { ECKey key; long creationTimeSeconds = getCreationTimeSeconds(); if (options.has("privkey")) { String data = (String) options.valueOf("privkey"); if (data.startsWith("5J") || data.startsWith("5H") || data.startsWith("5K")) { DumpedPrivateKey dpk; try { dpk = new DumpedPrivateKey(params, data); } catch (AddressFormatException e) { System.err.println("Could not parse dumped private key " + data); return; } key = dpk.getKey(); } else { byte[] decode = Utils.parseAsHexOrBase58(data); if (decode == null) { System.err.println("Could not understand --privkey as either hex or base58: " + data); return; } key = new ECKey(new BigInteger(1, decode)); } if (options.has("pubkey")) { // Give the user a hint. System.out.println("You don't have to specify --pubkey when a private key is supplied."); } key.setCreationTimeSeconds(creationTimeSeconds); } else if (options.has("pubkey")) { byte[] pubkey = Utils.parseAsHexOrBase58((String) options.valueOf("pubkey")); key = new ECKey(null, pubkey); key.setCreationTimeSeconds(creationTimeSeconds); } else { // Freshly generated key. key = new ECKey(); if (creationTimeSeconds > 0) key.setCreationTimeSeconds(creationTimeSeconds); } if (wallet.findKeyFromPubKey(key.getPubKey()) != null) { System.err.println("That key already exists in this wallet."); return; } try { if (wallet.isEncrypted()) { if (password == null || !wallet.checkPassword(password)) { System.err.println("The password is incorrect."); return; } key = key.encrypt(wallet.getKeyCrypter(), wallet.getKeyCrypter().deriveKey(password)); } 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(key.toAddress(params) + " " + key); }
/** Constructs a key from its components. This is not normally something you should use. */ public DeterministicKey( ImmutableList<ChildNumber> childNumberPath, byte[] chainCode, BigInteger priv, @Nullable DeterministicKey parent) { super(priv, compressPoint(ECKey.publicPointFromPrivate(priv))); checkArgument(chainCode.length == 32); this.parent = parent; this.childNumberPath = checkNotNull(childNumberPath); this.chainCode = Arrays.copyOf(chainCode, chainCode.length); this.depth = this.childNumberPath.size(); this.parentFingerprint = (parent != null) ? parent.getFingerprint() : 0; }
@Override public CoinSelection select(Coin target, List<TransactionOutput> candidates) { try { LinkedList<TransactionOutput> gathered = Lists.newLinkedList(); Coin valueGathered = Coin.ZERO; for (TransactionOutput output : candidates) { if (ignorePending && !isConfirmed(output)) continue; // Find the key that controls output, assuming it's a regular pay-to-pubkey or // pay-to-address output. // We ignore any other kind of exotic output on the assumption we can't spend it ourselves. final Script scriptPubKey = output.getScriptPubKey(); ECKey controllingKey; if (scriptPubKey.isSentToRawPubKey()) { controllingKey = wallet.findKeyFromPubKey(scriptPubKey.getPubKey()); } else if (scriptPubKey.isSentToAddress()) { controllingKey = wallet.findKeyFromPubHash(scriptPubKey.getPubKeyHash()); } else { log.info("Skipping tx output {} because it's not of simple form.", output); continue; } checkNotNull( controllingKey, "Coin selector given output as candidate for which we lack the key"); if (controllingKey.getCreationTimeSeconds() >= unixTimeSeconds) continue; // It's older than the cutoff time so select. valueGathered = valueGathered.add(output.getValue()); gathered.push(output); if (gathered.size() >= MAX_SIMULTANEOUS_INPUTS) { log.warn( "Reached {} inputs, going further would yield a tx that is too large, stopping here.", gathered.size()); break; } } return new CoinSelection(valueGathered, gathered); } catch (ScriptException e) { throw new RuntimeException( e); // We should never have problems understanding scripts in our wallet. } }
@Test public void freshCurrentKeys() throws Exception { int numKeys = ((group.getLookaheadSize() + group.getLookaheadThreshold()) * 2) // * 2 because of internal/external + 1 // keys issued + 3 /* account key + int/ext parent keys */; assertEquals(numKeys, group.numKeys()); assertEquals(2 * numKeys, group.getBloomFilterElementCount()); ECKey r1 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); assertEquals(numKeys, group.numKeys()); assertEquals(2 * numKeys, group.getBloomFilterElementCount()); ECKey i1 = new ECKey(); group.importKeys(i1); numKeys++; assertEquals(numKeys, group.numKeys()); assertEquals(2 * numKeys, group.getBloomFilterElementCount()); ECKey r2 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); assertEquals(r1, r2); ECKey c1 = group.currentKey(KeyChain.KeyPurpose.CHANGE); assertNotEquals(r1, c1); ECKey r3 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); assertNotEquals(r1, r3); ECKey c2 = group.freshKey(KeyChain.KeyPurpose.CHANGE); assertNotEquals(r3, c2); // Current key has not moved and will not under marked as used. ECKey r4 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); assertEquals(r2, r4); ECKey c3 = group.currentKey(KeyChain.KeyPurpose.CHANGE); assertEquals(c1, c3); // Mark as used. Current key is now different. group.markPubKeyAsUsed(r4.getPubKey()); ECKey r5 = group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); assertNotEquals(r4, r5); }
/** * Constructs a key from its components, including its private key data and possibly-redundant * information about its parent key. Invoked when deserializing, but otherwise not something that * you normally should use. */ private DeterministicKey( ImmutableList<ChildNumber> childNumberPath, byte[] chainCode, BigInteger priv, @Nullable DeterministicKey parent, int depth, int parentFingerprint) { super(priv, compressPoint(ECKey.publicPointFromPrivate(priv))); checkArgument(chainCode.length == 32); this.parent = parent; this.childNumberPath = checkNotNull(childNumberPath); this.chainCode = Arrays.copyOf(chainCode, chainCode.length); this.depth = depth; this.parentFingerprint = ascertainParentFingerprint(parent, parentFingerprint); }
/** * Called when the client provides the refund transaction. The refund transaction must have one * input from the multisig contract (that we don't have yet) and one output that the client * creates to themselves. This object will later be modified when we start getting paid. * * @param refundTx The refund transaction, this object will be mutated when payment is * incremented. * @param clientMultiSigPubKey The client's pubkey which is required for the multisig output * @return Our signature that makes the refund transaction valid * @throws VerificationException If the transaction isnt valid or did not meet the requirements of * a refund transaction. */ public synchronized byte[] provideRefundTransaction( Transaction refundTx, byte[] clientMultiSigPubKey) throws VerificationException { checkNotNull(refundTx); checkNotNull(clientMultiSigPubKey); checkState(state == State.WAITING_FOR_REFUND_TRANSACTION); log.info("Provided with refund transaction: {}", refundTx); // Do a few very basic syntax sanity checks. refundTx.verify(); // Verify that the refund transaction has a single input (that we can fill to sign the multisig // output). if (refundTx.getInputs().size() != 1) throw new VerificationException("Refund transaction does not have exactly one input"); // Verify that the refund transaction has a time lock on it and a sequence number of zero. if (refundTx.getInput(0).getSequenceNumber() != 0) throw new VerificationException("Refund transaction's input's sequence number is non-0"); if (refundTx.getLockTime() < minExpireTime) throw new VerificationException("Refund transaction has a lock time too soon"); // Verify the transaction has one output (we don't care about its contents, its up to the // client) // Note that because we sign with SIGHASH_NONE|SIGHASH_ANYOENCANPAY the client can later add // more outputs and // inputs, but we will need only one output later to create the paying transactions if (refundTx.getOutputs().size() != 1) throw new VerificationException("Refund transaction does not have exactly one output"); refundTransactionUnlockTimeSecs = refundTx.getLockTime(); // Sign the refund tx with the scriptPubKey and return the signature. We don't have the spending // transaction // so do the steps individually. clientKey = ECKey.fromPublicOnly(clientMultiSigPubKey); Script multisigPubKey = ScriptBuilder.createMultiSigOutputScript(2, ImmutableList.of(clientKey, serverKey)); // We are really only signing the fact that the transaction has a proper lock time and don't // care about anything // else, so we sign SIGHASH_NONE and SIGHASH_ANYONECANPAY. TransactionSignature sig = refundTx.calculateSignature(0, serverKey, multisigPubKey, Transaction.SigHash.NONE, true); log.info("Signed refund transaction."); this.clientOutput = refundTx.getOutput(0); state = State.WAITING_FOR_MULTISIG_CONTRACT; return sig.encodeToBitcoin(); }
@Test public void testRoundTripNormalWallet() throws Exception { Wallet wallet1 = roundTrip(myWallet); assertEquals(0, wallet1.getTransactions(true).size()); assertEquals(Coin.ZERO, wallet1.getBalance()); assertArrayEquals( myKey.getPubKey(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPubKey()); assertArrayEquals( myKey.getPrivKeyBytes(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes()); assertEquals( myKey.getCreationTimeSeconds(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getCreationTimeSeconds()); }
@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 = params .getDefaultSerializer() .makeTransaction(storedState.getRefundTransaction().toByteArray()); refundTransaction.getConfidence().setSource(TransactionConfidence.Source.SELF); ECKey myKey = (storedState.getMyKey().isEmpty()) ? containingWallet.findKeyFromPubKey(storedState.getMyPublicKey().toByteArray()) : ECKey.fromPrivate(storedState.getMyKey().toByteArray()); StoredClientChannel channel = new StoredClientChannel( Sha256Hash.wrap(storedState.getId().toByteArray()), params .getDefaultSerializer() .makeTransaction(storedState.getContractTransaction().toByteArray()), refundTransaction, myKey, Coin.valueOf(storedState.getValueToMe()), Coin.valueOf(storedState.getRefundFees()), false); if (storedState.hasCloseTransactionHash()) { Sha256Hash closeTxHash = Sha256Hash.wrap(storedState.getCloseTransactionHash().toByteArray()); channel.close = containingWallet.getTransaction(closeTxHash); } putChannel(channel, false); } } finally { lock.unlock(); } }
PaymentChannelServerState( StoredServerChannel storedServerChannel, Wallet wallet, TransactionBroadcaster broadcaster) throws VerificationException { synchronized (storedServerChannel) { this.wallet = checkNotNull(wallet); this.broadcaster = checkNotNull(broadcaster); this.multisigContract = checkNotNull(storedServerChannel.contract); this.multisigScript = multisigContract.getOutput(0).getScriptPubKey(); this.clientKey = ECKey.fromPublicOnly(multisigScript.getChunks().get(1).data); this.clientOutput = checkNotNull(storedServerChannel.clientOutput); this.refundTransactionUnlockTimeSecs = storedServerChannel.refundTransactionUnlockTimeSecs; this.serverKey = checkNotNull(storedServerChannel.myKey); this.totalValue = multisigContract.getOutput(0).getValue(); this.bestValueToMe = checkNotNull(storedServerChannel.bestValueToMe); this.bestValueSignature = storedServerChannel.bestValueSignature; checkArgument(bestValueToMe.equals(Coin.ZERO) || bestValueSignature != null); this.storedServerChannel = storedServerChannel; storedServerChannel.state = this; this.state = State.READY; } }
public void setUp(BlockStore blockStore) throws Exception { BriefLogFormatter.init(); unitTestParams = UnitTestParams.get(); Wallet.SendRequest.DEFAULT_FEE_PER_KB = BigInteger.ZERO; this.blockStore = blockStore; wallet = new Wallet(unitTestParams); key = new ECKey(); address = key.toAddress(unitTestParams); wallet.addKey(key); blockChain = new BlockChain(unitTestParams, wallet, blockStore); startPeerServers(); if (clientType == ClientType.NIO_CLIENT_MANAGER || clientType == ClientType.BLOCKING_CLIENT_MANAGER) { channels.startAsync(); channels.awaitRunning(); } socketAddress = new InetSocketAddress("127.0.0.1", 1111); }
@Test public void coinbaseTxns() throws Exception { // Covers issue 420 where the outpoint index of a coinbase tx input was being mis-serialized. Block b = params .getGenesisBlock() .createNextBlockWithCoinbase( Block.BLOCK_VERSION_GENESIS, myKey.getPubKey(), FIFTY_COINS, Block.BLOCK_HEIGHT_GENESIS); Transaction coinbase = b.getTransactions().get(0); assertTrue(coinbase.isCoinBase()); BlockChain chain = new BlockChain(params, myWallet, new MemoryBlockStore(params)); assertTrue(chain.add(b)); // Wallet now has a coinbase tx in it. assertEquals(1, myWallet.getTransactions(true).size()); assertTrue(myWallet.getTransaction(coinbase.getHash()).isCoinBase()); Wallet wallet2 = roundTrip(myWallet); assertEquals(1, wallet2.getTransactions(true).size()); assertTrue(wallet2.getTransaction(coinbase.getHash()).isCoinBase()); }
@Test public void empty() throws Exception { // Check the base case of a wallet with one key and no transactions. Wallet wallet1 = roundTrip(myWallet); assertEquals(0, wallet1.getTransactions(true).size()); assertEquals(Coin.ZERO, wallet1.getBalance()); assertArrayEquals( myKey.getPubKey(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPubKey()); assertArrayEquals( myKey.getPrivKeyBytes(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes()); assertEquals( myKey.getCreationTimeSeconds(), wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getCreationTimeSeconds()); assertEquals(mScriptCreationTime, wallet1.getWatchedScripts().get(0).getCreationTimeSeconds()); assertEquals(1, wallet1.getWatchedScripts().size()); assertEquals( ScriptBuilder.createOutputScript(myWatchedKey.toAddress(params)), wallet1.getWatchedScripts().get(0)); assertEquals(WALLET_DESCRIPTION, wallet1.getDescription()); }
/** * Called when the client provides us with a new signature and wishes to increment total payment * by size. Verifies the provided signature and only updates values if everything checks out. If * the new refundSize is not the lowest we have seen, it is simply ignored. * * @param refundSize How many satoshis of the original contract are refunded to the client (the * rest are ours) * @param signatureBytes The new signature spending the multi-sig contract to a new payment * transaction * @throws VerificationException If the signature does not verify or size is out of range (incl * being rejected by the network as dust). * @return true if there is more value left on the channel, false if it is now fully used up. */ public synchronized boolean incrementPayment(Coin refundSize, byte[] signatureBytes) throws VerificationException, ValueOutOfRangeException, InsufficientMoneyException { checkState(state == State.READY); checkNotNull(refundSize); checkNotNull(signatureBytes); TransactionSignature signature = TransactionSignature.decodeFromBitcoin(signatureBytes, true); // We allow snapping to zero for the payment amount because it's treated specially later, but // not less than // the dust level because that would prevent the transaction from being relayed/mined. final boolean fullyUsedUp = refundSize.equals(Coin.ZERO); if (refundSize.compareTo(clientOutput.getMinNonDustValue()) < 0 && !fullyUsedUp) throw new ValueOutOfRangeException( "Attempt to refund negative value or value too small to be accepted by the network"); Coin newValueToMe = totalValue.subtract(refundSize); if (newValueToMe.signum() < 0) throw new ValueOutOfRangeException("Attempt to refund more than the contract allows."); if (newValueToMe.compareTo(bestValueToMe) < 0) throw new ValueOutOfRangeException("Attempt to roll back payment on the channel."); // Get the wallet's copy of the multisigContract (ie with confidence information), if this is // null, the wallet // was not connected to the peergroup when the contract was broadcast (which may cause issues // down the road, and // disables our double-spend check next) Transaction walletContract = wallet.getTransaction(multisigContract.getHash()); checkNotNull( walletContract, "Wallet did not contain multisig contract {} after state was marked READY", multisigContract.getHash()); // Note that we check for DEAD state here, but this test is essentially useless in production // because we will // miss most double-spends due to bloom filtering right now anyway. This will eventually fixed // by network-wide // double-spend notifications, so we just wait instead of attempting to add all dependant // outpoints to our bloom // filters (and probably missing lots of edge-cases). if (walletContract.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.DEAD) { close(); throw new VerificationException("Multisig contract was double-spent"); } Transaction.SigHash mode; // If the client doesn't want anything back, they shouldn't sign any outputs at all. if (fullyUsedUp) mode = Transaction.SigHash.NONE; else mode = Transaction.SigHash.SINGLE; if (signature.sigHashMode() != mode || !signature.anyoneCanPay()) throw new VerificationException( "New payment signature was not signed with the right SIGHASH flags."); Wallet.SendRequest req = makeUnsignedChannelContract(newValueToMe); // Now check the signature is correct. // Note that the client must sign with SIGHASH_{SINGLE/NONE} | SIGHASH_ANYONECANPAY to allow us // to add additional // inputs (in case we need to add significant fee, or something...) and any outputs we want to // pay to. Sha256Hash sighash = req.tx.hashForSignature(0, multisigScript, mode, true); if (!clientKey.verify(sighash, signature)) throw new VerificationException("Signature does not verify on tx\n" + req.tx); bestValueToMe = newValueToMe; bestValueSignature = signatureBytes; updateChannelInWallet(); return !fullyUsedUp; }
/** Returns an ECKey created from this encoded private key. */ public ECKey getKey() { final ECKey key = ECKey.fromPrivate(bytes); return compressed ? key : key.decompress(); }
public Address genAddress() { ECKey key = new ECKey(); localWallet.addKey(key); return key.toAddress(Craftcoinish.network); }