3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-14 11:15:51 +00:00

Married wallets: multisig threshold could be specified

That allows to create multisigs like 3-of-3. If not specified, majority of
keys will be required (2-of-3, 3-of-5 and so on)
This commit is contained in:
Kosta Korenkov 2014-08-24 21:04:35 +07:00 committed by Mike Hearn
parent 96107b8b91
commit 5910a7f25e
9 changed files with 352 additions and 67 deletions

View File

@ -293,6 +293,21 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
return params;
}
/**
* Returns the number of signatures required to spend from this wallet. For a normal non-married wallet this will
* always be 1. For a married wallet this will be the N from N-of-M CHECKMULTISIG scripts used in this wallet.
* This value is either directly specified during the marriage (see {@link #addFollowingAccountKeys(java.util.List, int)})
* or, if not specified, calculated implicitly as a simple majority of keys.
*/
public int getSigsRequiredToSpend() {
lock.lock();
try {
return keychain.getSigsRequiredToSpend();
} finally {
lock.unlock();
}
}
/**
* <p>Adds given transaction signer to the list of signers. It will be added to the end of the signers list, so if
* this wallet already has some signers added, given signer will be executed after all of them.</p>
@ -615,9 +630,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
/**
* Makes given account keys follow the account key of the active keychain. After that you will be able
* to get P2SH addresses to receive coins to.
* This method should be called only once before key rotation, otherwise it will throw an IllegalStateException.
* <p>Alias for <code>addFollowingAccountKeys(followingAccountKeys, (followingAccountKeys.size() + 1) / 2 + 1)</code></p>
* <p>Creates married wallet requiring majority of keys to spend (2-of-3, 3-of-5 and so on)</p>
* <p>IMPORTANT: As of Bitcoin Core 0.9 all multisig transactions which require more than 3 public keys are
* non-standard and such spends won't be processed by peers with default settings, essentially making such
* transactions almost nonspendable</p>
*/
public void addFollowingAccountKeys(List<DeterministicKey> followingAccountKeys) {
lock.lock();
@ -628,6 +645,25 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
}
/**
* Makes given account keys follow the account key of the active keychain. After that you will be able
* to get P2SH addresses to receive coins to. Given threshold value specifies how many signatures required to
* spend transactions for this married wallet. This value should not exceed total number of keys involved
* (one followed key plus number of following keys).</p>
* <p>IMPORTANT: As of Bitcoin Core 0.9 all multisig transactions which require more than 3 public keys are
* non-standard and such spends won't be processed by peers with default settings, essentially making such
* transactions almost nonspendable</p>
* This method should be called only once before key rotation, otherwise it will throw an IllegalStateException.
*/
public void addFollowingAccountKeys(List<DeterministicKey> followingAccountKeys, int threshold) {
lock.lock();
try {
keychain.addFollowingAccountKeys(followingAccountKeys, threshold);
} finally {
lock.unlock();
}
}
/** See {@link com.google.bitcoin.wallet.DeterministicKeyChain#setLookaheadSize(int)} for more info on this. */
public void setKeychainLookaheadSize(int lookaheadSize) {
lock.lock();

View File

@ -205,6 +205,8 @@ public class WalletProtobufSerializer {
walletBuilder.addTransactionSigners(protoSigner);
}
walletBuilder.setSigsRequiredToSpend(wallet.getSigsRequiredToSpend());
// Populate the wallet version.
walletBuilder.setVersion(wallet.getVersion());
@ -408,14 +410,16 @@ public class WalletProtobufSerializer {
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(), keyCrypter);
chain = KeyChainGroup.fromProtobufEncrypted(params, walletProto.getKeyList(), sigsRequiredToSpend, keyCrypter);
} else {
chain = KeyChainGroup.fromProtobufUnencrypted(params, walletProto.getKeyList());
chain = KeyChainGroup.fromProtobufUnencrypted(params, walletProto.getKeyList(), sigsRequiredToSpend);
}
Wallet wallet = factory.create(params, chain);

View File

@ -0,0 +1,50 @@
/**
* Copyright 2014 Kosta Korenkov
*
* 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 com.google.bitcoin.testing;
import com.google.bitcoin.core.Sha256Hash;
import com.google.bitcoin.crypto.ChildNumber;
import com.google.bitcoin.crypto.DeterministicKey;
import com.google.bitcoin.signers.CustomTransactionSigner;
import com.google.bitcoin.wallet.DeterministicKeyChain;
import com.google.common.collect.ImmutableList;
import java.util.List;
/**
* <p>Transaction signer which uses provided keychain to get signing keys from. It relies on previous signer to provide
* derivation path to be used to get signing key and, once gets the key, just signs given transaction immediately.</p>
* It should not be used in test scenarios involving serialization as it doesn't have proper serialize/deserialize
* implementation.
*/
public class KeyChainTransactionSigner extends CustomTransactionSigner {
private DeterministicKeyChain keyChain;
public KeyChainTransactionSigner() {
}
public KeyChainTransactionSigner(DeterministicKeyChain keyChain) {
this.keyChain = keyChain;
}
@Override
protected SignatureAndKey getSignature(Sha256Hash sighash, List<ChildNumber> derivationPath) {
ImmutableList<ChildNumber> keyPath = ImmutableList.copyOf(derivationPath);
DeterministicKey key = keyChain.getKeyByPath(keyPath, true);
return new SignatureAndKey(key.sign(sighash), key.getPubOnly());
}
}

View File

@ -26,6 +26,7 @@ import com.google.bitcoin.script.ScriptBuilder;
import com.google.bitcoin.store.UnreadableWalletException;
import com.google.bitcoin.utils.ListenerRegistration;
import com.google.bitcoin.utils.Threading;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
@ -74,6 +75,10 @@ public class KeyChainGroup implements KeyBag {
// The map keys are the watching keys of the followed chains and values are the following chains
private Multimap<DeterministicKey, DeterministicKeyChain> followingKeychains;
// holds a number of signatures required to spend. It's the N from N-of-M CHECKMULTISIG script for P2SH transactions
// and always 1 for other transaction types
private int sigsRequiredToSpend;
// The map holds P2SH redeem script and corresponding ECKeys issued by this KeyChainGroup (including lookahead)
// mapped to redeem script hashes.
private LinkedHashMap<ByteString, RedeemData> marriedKeysRedeemData;
@ -85,12 +90,12 @@ public class KeyChainGroup implements KeyBag {
/** Creates a keychain group with no basic chain, and a single, lazily created HD chain. */
public KeyChainGroup(NetworkParameters params) {
this(params, null, new ArrayList<DeterministicKeyChain>(1), null, null, null);
this(params, null, new ArrayList<DeterministicKeyChain>(1), null, null, 1, null);
}
/** Creates a keychain group with no basic chain, and an HD chain initialized from the given seed. */
public KeyChainGroup(NetworkParameters params, DeterministicSeed seed) {
this(params, null, ImmutableList.of(new DeterministicKeyChain(seed)), null, null, null);
this(params, null, ImmutableList.of(new DeterministicKeyChain(seed)), null, null, 1, null);
}
/**
@ -98,7 +103,7 @@ public class KeyChainGroup implements KeyBag {
* This HAS to be an account key as returned by {@link DeterministicKeyChain#getWatchingKey()}.
*/
public KeyChainGroup(NetworkParameters params, DeterministicKey watchKey) {
this(params, null, ImmutableList.of(DeterministicKeyChain.watch(watchKey)), null, null, null);
this(params, null, ImmutableList.of(DeterministicKeyChain.watch(watchKey)), null, null, 1, null);
}
/**
@ -107,28 +112,49 @@ public class KeyChainGroup implements KeyBag {
* This HAS to be an account key as returned by {@link DeterministicKeyChain#getWatchingKey()}.
*/
public KeyChainGroup(NetworkParameters params, DeterministicKey watchKey, long creationTimeSecondsSecs) {
this(params, null, ImmutableList.of(DeterministicKeyChain.watch(watchKey, creationTimeSecondsSecs)), null, null, null);
this(params, null, ImmutableList.of(DeterministicKeyChain.watch(watchKey, creationTimeSecondsSecs)), null, null, 1, null);
}
/**
* Creates a keychain group with no basic chain, with an HD chain initialized from the given seed and being followed
* by given list of watch keys. Watch keys have to be account keys.
*/
public KeyChainGroup(NetworkParameters params, DeterministicSeed seed, List<DeterministicKey> followingAccountKeys) {
public KeyChainGroup(NetworkParameters params, DeterministicSeed seed, List<DeterministicKey> followingAccountKeys, int sigsRequiredToSpend) {
this(params, seed);
addFollowingAccountKeys(followingAccountKeys);
addFollowingAccountKeys(followingAccountKeys, sigsRequiredToSpend);
}
/**
* Makes given account keys follow the account key of the active keychain. After that active keychain will be
* treated as married and you will be able to get P2SH addresses to receive coins to.
* This method will throw an IllegalStateException, if active keychain is already married or already has leaf keys
* issued. In future this behaviour may be replaced with key rotation
* <p>Alias for <code>addFollowingAccountKeys(followingAccountKeys, (followingAccountKeys.size() + 1) / 2 + 1)</code></p>
* <p>Creates married keychain requiring majority of keys to spend (2-of-3, 3-of-5 and so on)</p>
* <p>IMPORTANT: As of Bitcoin Core 0.9 all multisig transactions which require more than 3 public keys are non-standard
* and such spends won't be processed by peers with default settings, essentially making such transactions almost
* nonspendable</p>
*/
public void addFollowingAccountKeys(List<DeterministicKey> followingAccountKeys) {
addFollowingAccountKeys(followingAccountKeys, (followingAccountKeys.size() + 1) / 2 + 1);
}
/**
* <p>Makes given account keys follow the account key of the active keychain. After that active keychain will be
* treated as married and you will be able to get P2SH addresses to receive coins to. Given sigsRequiredToSpend value
* specifies how many signatures required to spend transactions for this married keychain. This value should not exceed
* total number of keys involved (one followed key plus number of following keys), otherwise IllegalArgumentException
* will be thrown.</p>
* <p>IMPORTANT: As of Bitcoin Core 0.9 all multisig transactions which require more than 3 public keys are non-standard
* and such spends won't be processed by peers with default settings, essentially making such transactions almost
* nonspendable</p>
* <p>This method will throw an IllegalStateException, if active keychain is already married or already has leaf keys
* issued. In future this behaviour may be replaced with key rotation.</p>
*/
public void addFollowingAccountKeys(List<DeterministicKey> followingAccountKeys, int sigsRequiredToSpend) {
checkArgument(sigsRequiredToSpend <= followingAccountKeys.size() + 1, "Multisig threshold can't exceed total number of keys");
checkState(!isMarried(), "KeyChainGroup is married already");
checkState(getActiveKeyChain().numLeafKeysIssued() == 0, "Active keychain already has keys in use");
this.sigsRequiredToSpend = sigsRequiredToSpend;
DeterministicKey accountKey = getActiveKeyChain().getWatchingKey();
for (DeterministicKey key : followingAccountKeys) {
checkArgument(key.getPath().size() == 1, "Following keys have to be account keys");
@ -142,7 +168,9 @@ public class KeyChainGroup implements KeyBag {
}
// Used for deserialization.
private KeyChainGroup(NetworkParameters params, @Nullable BasicKeyChain basicKeyChain, List<DeterministicKeyChain> chains, @Nullable EnumMap<KeyChain.KeyPurpose, DeterministicKey> currentKeys, Multimap<DeterministicKey, DeterministicKeyChain> followingKeychains, @Nullable KeyCrypter crypter) {
private KeyChainGroup(NetworkParameters params, @Nullable BasicKeyChain basicKeyChain, List<DeterministicKeyChain> chains,
@Nullable EnumMap<KeyChain.KeyPurpose, DeterministicKey> currentKeys, Multimap<DeterministicKey,
DeterministicKeyChain> followingKeychains, int sigsRequiredToSpend, @Nullable KeyCrypter crypter) {
this.params = params;
this.basic = basicKeyChain == null ? new BasicKeyChain() : basicKeyChain;
this.chains = new ArrayList<DeterministicKeyChain>(checkNotNull(chains));
@ -155,6 +183,7 @@ public class KeyChainGroup implements KeyBag {
if (followingKeychains != null) {
this.followingKeychains.putAll(followingKeychains);
}
this.sigsRequiredToSpend = sigsRequiredToSpend;
marriedKeysRedeemData = new LinkedHashMap<ByteString, RedeemData>();
maybeLookaheadScripts();
@ -660,7 +689,7 @@ public class KeyChainGroup implements KeyBag {
}
private Script makeRedeemScript(List<ECKey> marriedKeys) {
return ScriptBuilder.createRedeemScript((marriedKeys.size() / 2) + 1, marriedKeys);
return ScriptBuilder.createRedeemScript(sigsRequiredToSpend, marriedKeys);
}
/** Adds a listener for events that are run when keys are added, on the user thread. */
@ -703,17 +732,21 @@ public class KeyChainGroup implements KeyBag {
return result;
}
public static KeyChainGroup fromProtobufUnencrypted(NetworkParameters params, List<Protos.Key> keys) throws UnreadableWalletException {
public static KeyChainGroup fromProtobufUnencrypted(NetworkParameters params, List<Protos.Key> keys, int sigsRequiredToSpend) throws UnreadableWalletException {
checkArgument(sigsRequiredToSpend > 0);
BasicKeyChain basicKeyChain = BasicKeyChain.fromProtobufUnencrypted(keys);
List<DeterministicKeyChain> chains = DeterministicKeyChain.fromProtobuf(keys, null);
EnumMap<KeyChain.KeyPurpose, DeterministicKey> currentKeys = null;
if (!chains.isEmpty())
currentKeys = createCurrentKeysMap(chains);
Multimap<DeterministicKey, DeterministicKeyChain> followingKeychains = extractFollowingKeychains(chains);
return new KeyChainGroup(params, basicKeyChain, chains, currentKeys, followingKeychains, null);
if (sigsRequiredToSpend < 2 && followingKeychains.size() > 0)
throw new IllegalArgumentException("Married KeyChainGroup requires multiple signatures to spend");
return new KeyChainGroup(params, basicKeyChain, chains, currentKeys, followingKeychains, sigsRequiredToSpend, null);
}
public static KeyChainGroup fromProtobufEncrypted(NetworkParameters params, List<Protos.Key> keys, KeyCrypter crypter) throws UnreadableWalletException {
public static KeyChainGroup fromProtobufEncrypted(NetworkParameters params, List<Protos.Key> keys, int sigsRequiredToSpend, KeyCrypter crypter) throws UnreadableWalletException {
checkArgument(sigsRequiredToSpend > 0);
checkNotNull(crypter);
BasicKeyChain basicKeyChain = BasicKeyChain.fromProtobufEncrypted(keys, crypter);
List<DeterministicKeyChain> chains = DeterministicKeyChain.fromProtobuf(keys, crypter);
@ -721,7 +754,9 @@ public class KeyChainGroup implements KeyBag {
if (!chains.isEmpty())
currentKeys = createCurrentKeysMap(chains);
Multimap<DeterministicKey, DeterministicKeyChain> followingKeychains = extractFollowingKeychains(chains);
return new KeyChainGroup(params, basicKeyChain, chains, currentKeys, followingKeychains, crypter);
if (sigsRequiredToSpend < 2 && followingKeychains.size() > 0)
throw new IllegalArgumentException("Married KeyChainGroup requires multiple signatures to spend");
return new KeyChainGroup(params, basicKeyChain, chains, currentKeys, followingKeychains, sigsRequiredToSpend, crypter);
}
/**
@ -912,4 +947,12 @@ public class KeyChainGroup implements KeyBag {
public List<DeterministicKeyChain> getDeterministicKeyChains() {
return new ArrayList<DeterministicKeyChain>(chains);
}
/**
* Returns the number of signatures required to spend transactions for this KeyChainGroup. It's the N from
* N-of-M CHECKMULTISIG script for P2SH transactions and always 1 for other transaction types.
*/
public int getSigsRequiredToSpend() {
return sigsRequiredToSpend;
}
}

View File

@ -13903,6 +13903,26 @@ public final class Protos {
*/
org.bitcoinj.wallet.Protos.TransactionSignerOrBuilder getTransactionSignersOrBuilder(
int index);
// optional uint32 sigsRequiredToSpend = 18 [default = 1];
/**
* <code>optional uint32 sigsRequiredToSpend = 18 [default = 1];</code>
*
* <pre>
* Number of signatures required to spend. This field is needed only for married wallets to reconstruct KeyChainGroup
* and represents the N value from N-of-M CHECKMULTISIG script. For regular single wallets it will always be 1.
* </pre>
*/
boolean hasSigsRequiredToSpend();
/**
* <code>optional uint32 sigsRequiredToSpend = 18 [default = 1];</code>
*
* <pre>
* Number of signatures required to spend. This field is needed only for married wallets to reconstruct KeyChainGroup
* and represents the N value from N-of-M CHECKMULTISIG script. For regular single wallets it will always be 1.
* </pre>
*/
int getSigsRequiredToSpend();
}
/**
* Protobuf type {@code wallet.Wallet}
@ -14066,6 +14086,11 @@ public final class Protos {
transactionSigners_.add(input.readMessage(org.bitcoinj.wallet.Protos.TransactionSigner.PARSER, extensionRegistry));
break;
}
case 144: {
bitField0_ |= 0x00000200;
sigsRequiredToSpend_ = input.readUInt32();
break;
}
}
}
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
@ -14733,6 +14758,32 @@ public final class Protos {
return transactionSigners_.get(index);
}
// optional uint32 sigsRequiredToSpend = 18 [default = 1];
public static final int SIGSREQUIREDTOSPEND_FIELD_NUMBER = 18;
private int sigsRequiredToSpend_;
/**
* <code>optional uint32 sigsRequiredToSpend = 18 [default = 1];</code>
*
* <pre>
* Number of signatures required to spend. This field is needed only for married wallets to reconstruct KeyChainGroup
* and represents the N value from N-of-M CHECKMULTISIG script. For regular single wallets it will always be 1.
* </pre>
*/
public boolean hasSigsRequiredToSpend() {
return ((bitField0_ & 0x00000200) == 0x00000200);
}
/**
* <code>optional uint32 sigsRequiredToSpend = 18 [default = 1];</code>
*
* <pre>
* Number of signatures required to spend. This field is needed only for married wallets to reconstruct KeyChainGroup
* and represents the N value from N-of-M CHECKMULTISIG script. For regular single wallets it will always be 1.
* </pre>
*/
public int getSigsRequiredToSpend() {
return sigsRequiredToSpend_;
}
private void initFields() {
networkIdentifier_ = "";
lastSeenBlockHash_ = com.google.protobuf.ByteString.EMPTY;
@ -14749,6 +14800,7 @@ public final class Protos {
keyRotationTime_ = 0L;
tags_ = java.util.Collections.emptyList();
transactionSigners_ = java.util.Collections.emptyList();
sigsRequiredToSpend_ = 1;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@ -14853,6 +14905,9 @@ public final class Protos {
for (int i = 0; i < transactionSigners_.size(); i++) {
output.writeMessage(17, transactionSigners_.get(i));
}
if (((bitField0_ & 0x00000200) == 0x00000200)) {
output.writeUInt32(18, sigsRequiredToSpend_);
}
getUnknownFields().writeTo(output);
}
@ -14922,6 +14977,10 @@ public final class Protos {
size += com.google.protobuf.CodedOutputStream
.computeMessageSize(17, transactionSigners_.get(i));
}
if (((bitField0_ & 0x00000200) == 0x00000200)) {
size += com.google.protobuf.CodedOutputStream
.computeUInt32Size(18, sigsRequiredToSpend_);
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@ -15107,6 +15166,8 @@ public final class Protos {
} else {
transactionSignersBuilder_.clear();
}
sigsRequiredToSpend_ = 1;
bitField0_ = (bitField0_ & ~0x00008000);
return this;
}
@ -15229,6 +15290,10 @@ public final class Protos {
} else {
result.transactionSigners_ = transactionSignersBuilder_.build();
}
if (((from_bitField0_ & 0x00008000) == 0x00008000)) {
to_bitField0_ |= 0x00000200;
}
result.sigsRequiredToSpend_ = sigsRequiredToSpend_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@ -15432,6 +15497,9 @@ public final class Protos {
}
}
}
if (other.hasSigsRequiredToSpend()) {
setSigsRequiredToSpend(other.getSigsRequiredToSpend());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
@ -17610,6 +17678,59 @@ public final class Protos {
return transactionSignersBuilder_;
}
// optional uint32 sigsRequiredToSpend = 18 [default = 1];
private int sigsRequiredToSpend_ = 1;
/**
* <code>optional uint32 sigsRequiredToSpend = 18 [default = 1];</code>
*
* <pre>
* Number of signatures required to spend. This field is needed only for married wallets to reconstruct KeyChainGroup
* and represents the N value from N-of-M CHECKMULTISIG script. For regular single wallets it will always be 1.
* </pre>
*/
public boolean hasSigsRequiredToSpend() {
return ((bitField0_ & 0x00008000) == 0x00008000);
}
/**
* <code>optional uint32 sigsRequiredToSpend = 18 [default = 1];</code>
*
* <pre>
* Number of signatures required to spend. This field is needed only for married wallets to reconstruct KeyChainGroup
* and represents the N value from N-of-M CHECKMULTISIG script. For regular single wallets it will always be 1.
* </pre>
*/
public int getSigsRequiredToSpend() {
return sigsRequiredToSpend_;
}
/**
* <code>optional uint32 sigsRequiredToSpend = 18 [default = 1];</code>
*
* <pre>
* Number of signatures required to spend. This field is needed only for married wallets to reconstruct KeyChainGroup
* and represents the N value from N-of-M CHECKMULTISIG script. For regular single wallets it will always be 1.
* </pre>
*/
public Builder setSigsRequiredToSpend(int value) {
bitField0_ |= 0x00008000;
sigsRequiredToSpend_ = value;
onChanged();
return this;
}
/**
* <code>optional uint32 sigsRequiredToSpend = 18 [default = 1];</code>
*
* <pre>
* Number of signatures required to spend. This field is needed only for married wallets to reconstruct KeyChainGroup
* and represents the N value from N-of-M CHECKMULTISIG script. For regular single wallets it will always be 1.
* </pre>
*/
public Builder clearSigsRequiredToSpend() {
bitField0_ = (bitField0_ & ~0x00008000);
sigsRequiredToSpend_ = 1;
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:wallet.Wallet)
}
@ -18538,7 +18659,7 @@ public final class Protos {
"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\" \n\003Tag\022\013\n\003tag\030\001 \002(\t\022\014\n" +
"\004data\030\002 \002(\014\"5\n\021TransactionSigner\022\022\n\nclas" +
"s_name\030\001 \002(\t\022\014\n\004data\030\002 \001(\014\"\351\004\n\006Wallet\022\032\n" +
"s_name\030\001 \002(\t\022\014\n\004data\030\002 \001(\014\"\211\005\n\006Wallet\022\032\n" +
"\022network_identifier\030\001 \002(\t\022\034\n\024last_seen_b" +
"lock_hash\030\002 \001(\014\022\036\n\026last_seen_block_heigh" +
"t\030\014 \001(\r\022!\n\031last_seen_block_time_secs\030\016 \001",
@ -18552,12 +18673,12 @@ public final class Protos {
"llet.Extension\022\023\n\013description\030\013 \001(\t\022\031\n\021k" +
"ey_rotation_time\030\r \001(\004\022\031\n\004tags\030\020 \003(\0132\013.w" +
"allet.Tag\0226\n\023transaction_signers\030\021 \003(\0132\031",
".wallet.TransactionSigner\";\n\016EncryptionT" +
"ype\022\017\n\013UNENCRYPTED\020\001\022\030\n\024ENCRYPTED_SCRYPT" +
"_AES\020\002\"R\n\014ExchangeRate\022\022\n\ncoin_value\030\001 \002" +
"(\003\022\022\n\nfiat_value\030\002 \002(\003\022\032\n\022fiat_currency_" +
"code\030\003 \002(\tB\035\n\023org.bitcoinj.walletB\006Proto" +
"s"
".wallet.TransactionSigner\022\036\n\023sigsRequire" +
"dToSpend\030\022 \001(\r:\0011\";\n\016EncryptionType\022\017\n\013U" +
"NENCRYPTED\020\001\022\030\n\024ENCRYPTED_SCRYPT_AES\020\002\"R" +
"\n\014ExchangeRate\022\022\n\ncoin_value\030\001 \002(\003\022\022\n\nfi" +
"at_value\030\002 \002(\003\022\032\n\022fiat_currency_code\030\003 \002" +
"(\tB\035\n\023org.bitcoinj.walletB\006Protos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@ -18647,7 +18768,7 @@ public final class Protos {
internal_static_wallet_Wallet_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_wallet_Wallet_descriptor,
new java.lang.String[] { "NetworkIdentifier", "LastSeenBlockHash", "LastSeenBlockHeight", "LastSeenBlockTimeSecs", "Key", "Transaction", "WatchedScript", "EncryptionType", "EncryptionParameters", "Version", "Extension", "Description", "KeyRotationTime", "Tags", "TransactionSigners", });
new java.lang.String[] { "NetworkIdentifier", "LastSeenBlockHash", "LastSeenBlockHeight", "LastSeenBlockTimeSecs", "Key", "Transaction", "WatchedScript", "EncryptionType", "EncryptionParameters", "Version", "Extension", "Description", "KeyRotationTime", "Tags", "TransactionSigners", "SigsRequiredToSpend", });
internal_static_wallet_ExchangeRate_descriptor =
getDescriptor().getMessageTypes().get(14);
internal_static_wallet_ExchangeRate_fieldAccessorTable = new

View File

@ -19,15 +19,11 @@ package com.google.bitcoin.core;
import com.google.bitcoin.core.Wallet.SendRequest;
import com.google.bitcoin.crypto.*;
import com.google.bitcoin.signers.CustomTransactionSigner;
import com.google.bitcoin.signers.TransactionSigner;
import com.google.bitcoin.store.BlockStoreException;
import com.google.bitcoin.store.MemoryBlockStore;
import com.google.bitcoin.store.WalletProtobufSerializer;
import com.google.bitcoin.testing.FakeTxBuilder;
import com.google.bitcoin.testing.MockTransactionBroadcaster;
import com.google.bitcoin.testing.NopTransactionSigner;
import com.google.bitcoin.testing.TestWithWallet;
import com.google.bitcoin.testing.*;
import com.google.bitcoin.utils.ExchangeRate;
import com.google.bitcoin.utils.Fiat;
import com.google.bitcoin.utils.Threading;
@ -96,31 +92,26 @@ public class WalletTest extends TestWithWallet {
super.tearDown();
}
private void createMarriedWalletWithSigner() throws BlockStoreException {
createMarriedWallet(true);
private void createMarriedWallet(int threshold, int numKeys) throws BlockStoreException {
createMarriedWallet(threshold, numKeys, true);
}
private void createMarriedWallet(boolean addSigners) throws BlockStoreException {
private void createMarriedWallet(int threshold, int numKeys, boolean addSigners) throws BlockStoreException {
wallet = new Wallet(params);
blockStore = new MemoryBlockStore(params);
chain = new BlockChain(params, wallet, blockStore);
final DeterministicKeyChain keyChain = new DeterministicKeyChain(new SecureRandom());
DeterministicKey partnerKey = DeterministicKey.deserializeB58(null, keyChain.getWatchingKey().serializePubB58());
if (addSigners) {
CustomTransactionSigner signer = new CustomTransactionSigner() {
@Override
protected SignatureAndKey getSignature(Sha256Hash sighash, List<ChildNumber> derivationPath) {
ImmutableList<ChildNumber> keyPath = ImmutableList.copyOf(derivationPath);
DeterministicKey key = keyChain.getKeyByPath(keyPath, true);
return new SignatureAndKey(key.sign(sighash), key.getPubOnly());
}
};
wallet.addTransactionSigner(signer);
List<DeterministicKey> followingKeys = Lists.newArrayList();
for (int i = 0; i < numKeys - 1; i++) {
final DeterministicKeyChain keyChain = new DeterministicKeyChain(new SecureRandom());
DeterministicKey partnerKey = DeterministicKey.deserializeB58(null, keyChain.getWatchingKey().serializePubB58());
followingKeys.add(partnerKey);
if (addSigners && i < threshold)
wallet.addTransactionSigner(new KeyChainTransactionSigner(keyChain));
}
wallet.addFollowingAccountKeys(ImmutableList.of(partnerKey));
wallet.addFollowingAccountKeys(followingKeys, threshold);
}
@Test
@ -152,10 +143,22 @@ public class WalletTest extends TestWithWallet {
@Test
public void basicSpendingFromP2SH() throws Exception {
createMarriedWalletWithSigner();
Address destination = new ECKey().toAddress(params);
createMarriedWallet(2, 2);
myAddress = wallet.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
basicSpendingCommon(wallet, myAddress, destination, false);
basicSpendingCommon(wallet, myAddress, new ECKey().toAddress(params), false);
createMarriedWallet(2, 3);
myAddress = wallet.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
basicSpendingCommon(wallet, myAddress, new ECKey().toAddress(params), false);
createMarriedWallet(3, 3);
myAddress = wallet.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
basicSpendingCommon(wallet, myAddress, new ECKey().toAddress(params), false);
}
@Test (expected = IllegalArgumentException.class)
public void thresholdShouldNotExceedNumberOfKeys() throws Exception {
createMarriedWallet(3, 2);
}
@Test
@ -1236,7 +1239,7 @@ public class WalletTest extends TestWithWallet {
@Test
public void marriedKeychainBloomFilter() throws Exception {
createMarriedWalletWithSigner();
createMarriedWallet(2, 2);
Address address = wallet.currentReceiveAddress();
assertTrue(wallet.getBloomFilter(0.001).contains(address.getHash160()));
@ -2451,7 +2454,7 @@ public class WalletTest extends TestWithWallet {
@Test (expected = TransactionSigner.MissingSignatureException.class)
public void completeTxPartiallySignedMarriedThrowsByDefault() throws Exception {
createMarriedWallet(false);
createMarriedWallet(2, 2, false);
myAddress = wallet.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
sendMoneyToWallet(wallet, COIN, myAddress, AbstractBlockChain.NewBlockType.BEST_CHAIN);
@ -2461,7 +2464,7 @@ public class WalletTest extends TestWithWallet {
public void completeTxPartiallySignedMarried(Wallet.MissingSigsMode missSigMode, byte[] expectedSig) throws Exception {
// create married wallet without signer
createMarriedWallet(false);
createMarriedWallet(2, 2, false);
myAddress = wallet.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
sendMoneyToWallet(wallet, COIN, myAddress, AbstractBlockChain.NewBlockType.BEST_CHAIN);

View File

@ -20,12 +20,16 @@ package com.google.bitcoin.store;
import com.google.bitcoin.core.*;
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
import com.google.bitcoin.crypto.DeterministicKey;
import com.google.bitcoin.params.MainNetParams;
import com.google.bitcoin.params.UnitTestParams;
import com.google.bitcoin.script.ScriptBuilder;
import com.google.bitcoin.testing.FakeTxBuilder;
import com.google.bitcoin.utils.BriefLogFormatter;
import com.google.bitcoin.utils.Threading;
import com.google.bitcoin.wallet.DeterministicKeyChain;
import com.google.bitcoin.wallet.KeyChain;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.ByteString;
import org.bitcoinj.wallet.Protos;
import org.junit.Before;
@ -35,6 +39,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
@ -258,7 +263,6 @@ public class WalletProtobufSerializerTest {
private static Wallet roundTrip(Wallet wallet) throws Exception {
ByteArrayOutputStream output = new ByteArrayOutputStream();
//System.out.println(WalletProtobufSerializer.walletToText(wallet));
new WalletProtobufSerializer().writeWallet(wallet, output);
ByteArrayInputStream test = new ByteArrayInputStream(output.toByteArray());
assertTrue(WalletProtobufSerializer.isWallet(test));
@ -279,6 +283,23 @@ public class WalletProtobufSerializerTest {
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getCreationTimeSeconds());
}
@Test
public void testRoundTripMarriedWallet() throws Exception {
// create 2-of-2 married wallet
myWallet = new Wallet(params);
final DeterministicKeyChain keyChain = new DeterministicKeyChain(new SecureRandom());
DeterministicKey partnerKey = DeterministicKey.deserializeB58(null, keyChain.getWatchingKey().serializePubB58());
myWallet.addFollowingAccountKeys(ImmutableList.of(partnerKey), 2);
myAddress = myWallet.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
Wallet wallet1 = roundTrip(myWallet);
assertEquals(0, wallet1.getTransactions(true).size());
assertEquals(Coin.ZERO, wallet1.getBalance());
assertEquals(2, wallet1.getSigsRequiredToSpend());
assertEquals(myAddress, wallet1.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS));
}
@Test
public void coinbaseTxns() throws Exception {
// Covers issue 420 where the outpoint index of a coinbase tx input was being mis-serialized.

View File

@ -58,7 +58,7 @@ public class KeyChainGroupTest {
private KeyChainGroup createMarriedKeyChainGroup() {
byte[] entropy = Sha256Hash.create("don't use a seed like this in real life".getBytes()).getBytes();
DeterministicSeed seed = new DeterministicSeed(entropy, "", MnemonicCode.BIP39_STANDARDISATION_TIME_SECS);
KeyChainGroup group = new KeyChainGroup(params, seed, ImmutableList.of(watchingAccountKey));
KeyChainGroup group = new KeyChainGroup(params, seed, ImmutableList.of(watchingAccountKey), 2);
group.setLookaheadSize(LOOKAHEAD_SIZE);
group.getActiveKeyChain();
return group;
@ -400,7 +400,7 @@ public class KeyChainGroupTest {
@Test
public void serialization() throws Exception {
assertEquals(INITIAL_KEYS + 1 /* for the seed */, group.serializeToProtobuf().size());
group = KeyChainGroup.fromProtobufUnencrypted(params, group.serializeToProtobuf());
group = KeyChainGroup.fromProtobufUnencrypted(params, group.serializeToProtobuf(), 1);
group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicKey key2 = group.freshKey(KeyChain.KeyPurpose.CHANGE);
@ -411,13 +411,13 @@ public class KeyChainGroupTest {
List<Protos.Key> protoKeys2 = group.serializeToProtobuf();
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 2, protoKeys2.size());
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys1);
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys1, 1);
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 1, protoKeys1.size());
assertTrue(group.hasKey(key1));
assertTrue(group.hasKey(key2));
assertEquals(key2, group.currentKey(KeyChain.KeyPurpose.CHANGE));
assertEquals(key1, group.currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS));
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys2);
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys2, 1);
assertEquals(INITIAL_KEYS + ((LOOKAHEAD_SIZE + 1) * 2) + 1 /* for the seed */ + 2, protoKeys2.size());
assertTrue(group.hasKey(key1));
assertTrue(group.hasKey(key2));
@ -426,7 +426,7 @@ public class KeyChainGroupTest {
final KeyParameter aesKey = scrypt.deriveKey("password");
group.encrypt(scrypt, aesKey);
List<Protos.Key> protoKeys3 = group.serializeToProtobuf();
group = KeyChainGroup.fromProtobufEncrypted(params, protoKeys3, scrypt);
group = KeyChainGroup.fromProtobufEncrypted(params, protoKeys3, 1, scrypt);
assertTrue(group.isEncrypted());
assertTrue(group.checkPassword("password"));
group.decrypt(aesKey);
@ -443,7 +443,7 @@ public class KeyChainGroupTest {
group.getBloomFilterElementCount(); // Force lookahead.
List<Protos.Key> protoKeys1 = group.serializeToProtobuf();
assertEquals(3 + (group.getLookaheadSize() + group.getLookaheadThreshold() + 1) * 2, protoKeys1.size());
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys1);
group = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys1, 1);
assertEquals(3 + (group.getLookaheadSize() + group.getLookaheadThreshold() + 1) * 2, group.serializeToProtobuf().size());
}
@ -452,10 +452,12 @@ public class KeyChainGroupTest {
group = createMarriedKeyChainGroup();
Address address1 = group.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertTrue(group.isMarried());
assertEquals(2, group.getSigsRequiredToSpend());
List<Protos.Key> protoKeys = group.serializeToProtobuf();
KeyChainGroup group2 = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys);
KeyChainGroup group2 = KeyChainGroup.fromProtobufUnencrypted(params, protoKeys, 2);
assertTrue(group2.isMarried());
assertEquals(2, group.getSigsRequiredToSpend());
Address address2 = group2.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
assertEquals(address1, address2);
}
@ -517,7 +519,7 @@ public class KeyChainGroupTest {
DeterministicSeed seed1 = group.getActiveKeyChain().getSeed();
assertNotNull(seed1);
group = KeyChainGroup.fromProtobufUnencrypted(params, protobufs);
group = KeyChainGroup.fromProtobufUnencrypted(params, protobufs, 1);
group.upgradeToDeterministic(0, null); // Should give same result as last time.
DeterministicKey dkey2 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
DeterministicSeed seed2 = group.getActiveKeyChain().getSeed();

View File

@ -105,6 +105,7 @@ message Key {
// Either the private EC key bytes (without any ASN.1 wrapping), or the deterministic root seed.
// If the secret is encrypted, or this is a "watching entry" then this is missing.
optional bytes secret_bytes = 2;
// If the secret data is encrypted, then secret_bytes is missing and this field is set.
optional EncryptedData encrypted_data = 6;
@ -371,7 +372,11 @@ message Wallet {
// transaction signers added to the wallet
repeated TransactionSigner transaction_signers = 17;
// Next tag: 18
// Number of signatures required to spend. This field is needed only for married wallets to reconstruct KeyChainGroup
// and represents the N value from N-of-M CHECKMULTISIG script. For regular single wallets it will always be 1.
optional uint32 sigsRequiredToSpend = 18 [default = 1];
// Next tag: 19
}
/** An exchange rate between Bitcoin and some fiat currency. */