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:
parent
96107b8b91
commit
5910a7f25e
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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. */
|
||||
|
Loading…
x
Reference in New Issue
Block a user