From 22f0600afe4a10da8b52c82269842b4187fd4363 Mon Sep 17 00:00:00 2001
From: Devrandom Alias for Creates married wallet requiring majority of keys to spend (2-of-3, 3-of-5 and so on) 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 nonspendableaddFollowingAccountKeys(followingAccountKeys, (followingAccountKeys.size() + 1) / 2 + 1)
+ * MarriedKeyChain chain = MarriedKeyChain.builder()
+ * .random(new SecureRandom())
+ * .followingKeys(followingKeys)
+ * .threshold(2).build();
+ * wallet.addAndActivateHDChain(chain);
+ *
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
- * This method should be called only once before key rotation, otherwise it will throw an IllegalStateException. - */ - public void addFollowingAccountKeys(ListWatch key has to be an account key.
*/ - private DeterministicKeyChain(DeterministicKey watchKey, boolean isFollowing) { + protected DeterministicKeyChain(DeterministicKey watchKey, boolean isFollowing) { this(watchKey, Utils.currentTimeSeconds()); this.isFollowing = isFollowing; } @@ -581,49 +680,61 @@ public class DeterministicKeyChain implements EncryptableKeyChain { // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + protected void beforeSerializeToProtobuf(ListAlias for addFollowingAccountKeys(followingAccountKeys, (followingAccountKeys.size() + 1) / 2 + 1)
Creates married keychain requiring majority of keys to spend (2-of-3, 3-of-5 and so on)
- *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
- */ - public void addFollowingAccountKeys(ListMakes 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.
- *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
- *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.
- */ - public void addFollowingAccountKeys(ListA multi-signature keychain using synchronized HD keys (a.k.a HDM)
+ *This keychain keeps track of following keychains that follow the account key of this keychain. + * You can get P2SH addresses to receive coins to from this chain. The threshold - sigsRequiredToSpend + * 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.
+ *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
+ *This method will throw an IllegalStateException, if the keychain is already married or already has leaf keys + * issued.
+ */ +public class MarriedKeyChain extends DeterministicKeyChain { + // The map holds P2SH redeem script and corresponding ECKeys issued by this KeyChainGroup (including lookahead) + // mapped to redeem script hashes. + private LinkedHashMap(followingKeys.size() + 1) / 2 + 1)
(majority) if unspecified.
+ * 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
+ */ + public T threshold(int threshold) { + this.threshold = threshold; + return self(); + } + + public MarriedKeyChain build() { + checkState(random != null || entropy != null || seed != null, "Must provide either entropy or random"); + checkNotNull(followingKeys, "followingKeys must be provided"); + MarriedKeyChain chain; + if (threshold == 0) + threshold = (followingKeys.size() + 1) / 2 + 1; + if (random != null) { + chain = new MarriedKeyChain(random, bits, passphrase, seedCreationTimeSecs); + } else if (entropy != null) { + chain = new MarriedKeyChain(entropy, passphrase, seedCreationTimeSecs); + } else { + chain = new MarriedKeyChain(seed); + } + chain.addFollowingAccountKeys(followingKeys, threshold); + return chain; + } + } + + public static Builder> builder() { + return new Builder(); + } + + // Protobuf deserialization constructors + MarriedKeyChain(DeterministicKey accountKey) { + super(accountKey, false); + } + + MarriedKeyChain(DeterministicSeed seed, KeyCrypter crypter) { + super(seed, crypter); + } + + // Builder constructors + private MarriedKeyChain(SecureRandom random, int bits, String passphrase, long seedCreationTimeSecs) { + super(random, bits, passphrase, seedCreationTimeSecs); + } + + private MarriedKeyChain(byte[] entropy, String passphrase, long seedCreationTimeSecs) { + super(entropy, passphrase, seedCreationTimeSecs); + } + + private MarriedKeyChain(DeterministicSeed seed) { + super(seed); + } + + void setFollowingKeyChains(Listoptional uint32 sigsRequiredToSpend = 6 [default = 1];
+ *
+ * + * Number of signatures required to spend. This field is needed only for married keychains to reconstruct KeyChain + * and represents the N value from N-of-M CHECKMULTISIG script. For regular single keychains it will always be 1. + *+ */ + boolean hasSigsRequiredToSpend(); + /** + *
optional uint32 sigsRequiredToSpend = 6 [default = 1];
+ *
+ * + * Number of signatures required to spend. This field is needed only for married keychains to reconstruct KeyChain + * and represents the N value from N-of-M CHECKMULTISIG script. For regular single keychains it will always be 1. + *+ */ + int getSigsRequiredToSpend(); } /** * Protobuf type {@code wallet.DeterministicKey} @@ -1367,6 +1387,11 @@ public final class Protos { isFollowing_ = input.readBool(); break; } + case 48: { + bitField0_ |= 0x00000010; + sigsRequiredToSpend_ = input.readUInt32(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -1554,12 +1579,39 @@ public final class Protos { return isFollowing_; } + // optional uint32 sigsRequiredToSpend = 6 [default = 1]; + public static final int SIGSREQUIREDTOSPEND_FIELD_NUMBER = 6; + private int sigsRequiredToSpend_; + /** + *
optional uint32 sigsRequiredToSpend = 6 [default = 1];
+ *
+ * + * Number of signatures required to spend. This field is needed only for married keychains to reconstruct KeyChain + * and represents the N value from N-of-M CHECKMULTISIG script. For regular single keychains it will always be 1. + *+ */ + public boolean hasSigsRequiredToSpend() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + *
optional uint32 sigsRequiredToSpend = 6 [default = 1];
+ *
+ * + * Number of signatures required to spend. This field is needed only for married keychains to reconstruct KeyChain + * and represents the N value from N-of-M CHECKMULTISIG script. For regular single keychains it will always be 1. + *+ */ + public int getSigsRequiredToSpend() { + return sigsRequiredToSpend_; + } + private void initFields() { chainCode_ = com.google.protobuf.ByteString.EMPTY; path_ = java.util.Collections.emptyList(); issuedSubkeys_ = 0; lookaheadSize_ = 0; isFollowing_ = false; + sigsRequiredToSpend_ = 1; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -1592,6 +1644,9 @@ public final class Protos { if (((bitField0_ & 0x00000008) == 0x00000008)) { output.writeBool(5, isFollowing_); } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeUInt32(6, sigsRequiredToSpend_); + } getUnknownFields().writeTo(output); } @@ -1626,6 +1681,10 @@ public final class Protos { size += com.google.protobuf.CodedOutputStream .computeBoolSize(5, isFollowing_); } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(6, sigsRequiredToSpend_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -1757,6 +1816,8 @@ public final class Protos { bitField0_ = (bitField0_ & ~0x00000008); isFollowing_ = false; bitField0_ = (bitField0_ & ~0x00000010); + sigsRequiredToSpend_ = 1; + bitField0_ = (bitField0_ & ~0x00000020); return this; } @@ -1806,6 +1867,10 @@ public final class Protos { to_bitField0_ |= 0x00000008; } result.isFollowing_ = isFollowing_; + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000010; + } + result.sigsRequiredToSpend_ = sigsRequiredToSpend_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -1844,6 +1909,9 @@ public final class Protos { if (other.hasIsFollowing()) { setIsFollowing(other.getIsFollowing()); } + if (other.hasSigsRequiredToSpend()) { + setSigsRequiredToSpend(other.getSigsRequiredToSpend()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -2195,6 +2263,59 @@ public final class Protos { return this; } + // optional uint32 sigsRequiredToSpend = 6 [default = 1]; + private int sigsRequiredToSpend_ = 1; + /** + *
optional uint32 sigsRequiredToSpend = 6 [default = 1];
+ *
+ * + * Number of signatures required to spend. This field is needed only for married keychains to reconstruct KeyChain + * and represents the N value from N-of-M CHECKMULTISIG script. For regular single keychains it will always be 1. + *+ */ + public boolean hasSigsRequiredToSpend() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + *
optional uint32 sigsRequiredToSpend = 6 [default = 1];
+ *
+ * + * Number of signatures required to spend. This field is needed only for married keychains to reconstruct KeyChain + * and represents the N value from N-of-M CHECKMULTISIG script. For regular single keychains it will always be 1. + *+ */ + public int getSigsRequiredToSpend() { + return sigsRequiredToSpend_; + } + /** + *
optional uint32 sigsRequiredToSpend = 6 [default = 1];
+ *
+ * + * Number of signatures required to spend. This field is needed only for married keychains to reconstruct KeyChain + * and represents the N value from N-of-M CHECKMULTISIG script. For regular single keychains it will always be 1. + *+ */ + public Builder setSigsRequiredToSpend(int value) { + bitField0_ |= 0x00000020; + sigsRequiredToSpend_ = value; + onChanged(); + return this; + } + /** + *
optional uint32 sigsRequiredToSpend = 6 [default = 1];
+ *
+ * + * Number of signatures required to spend. This field is needed only for married keychains to reconstruct KeyChain + * and represents the N value from N-of-M CHECKMULTISIG script. For regular single keychains it will always be 1. + *+ */ + public Builder clearSigsRequiredToSpend() { + bitField0_ = (bitField0_ & ~0x00000020); + sigsRequiredToSpend_ = 1; + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:wallet.DeterministicKey) } @@ -14291,26 +14412,6 @@ public final class Protos { */ org.bitcoinj.wallet.Protos.TransactionSignerOrBuilder getTransactionSignersOrBuilder( int index); - - // optional uint32 sigsRequiredToSpend = 18 [default = 1]; - /** - *
optional uint32 sigsRequiredToSpend = 18 [default = 1];
- *
- * - * 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. - *- */ - boolean hasSigsRequiredToSpend(); - /** - *
optional uint32 sigsRequiredToSpend = 18 [default = 1];
- *
- * - * 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. - *- */ - int getSigsRequiredToSpend(); } /** * Protobuf type {@code wallet.Wallet} @@ -14474,11 +14575,6 @@ 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) { @@ -15146,32 +15242,6 @@ 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_; - /** - *
optional uint32 sigsRequiredToSpend = 18 [default = 1];
- *
- * - * 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. - *- */ - public boolean hasSigsRequiredToSpend() { - return ((bitField0_ & 0x00000200) == 0x00000200); - } - /** - *
optional uint32 sigsRequiredToSpend = 18 [default = 1];
- *
- * - * 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. - *- */ - public int getSigsRequiredToSpend() { - return sigsRequiredToSpend_; - } - private void initFields() { networkIdentifier_ = ""; lastSeenBlockHash_ = com.google.protobuf.ByteString.EMPTY; @@ -15188,7 +15258,6 @@ 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() { @@ -15293,9 +15362,6 @@ 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); } @@ -15365,10 +15431,6 @@ 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; @@ -15554,8 +15616,6 @@ public final class Protos { } else { transactionSignersBuilder_.clear(); } - sigsRequiredToSpend_ = 1; - bitField0_ = (bitField0_ & ~0x00008000); return this; } @@ -15678,10 +15738,6 @@ 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; @@ -15885,9 +15941,6 @@ public final class Protos { } } } - if (other.hasSigsRequiredToSpend()) { - setSigsRequiredToSpend(other.getSigsRequiredToSpend()); - } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -18066,59 +18119,6 @@ public final class Protos { return transactionSignersBuilder_; } - // optional uint32 sigsRequiredToSpend = 18 [default = 1]; - private int sigsRequiredToSpend_ = 1; - /** - *
optional uint32 sigsRequiredToSpend = 18 [default = 1];
- *
- * - * 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. - *- */ - public boolean hasSigsRequiredToSpend() { - return ((bitField0_ & 0x00008000) == 0x00008000); - } - /** - *
optional uint32 sigsRequiredToSpend = 18 [default = 1];
- *
- * - * 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. - *- */ - public int getSigsRequiredToSpend() { - return sigsRequiredToSpend_; - } - /** - *
optional uint32 sigsRequiredToSpend = 18 [default = 1];
- *
- * - * 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. - *- */ - public Builder setSigsRequiredToSpend(int value) { - bitField0_ |= 0x00008000; - sigsRequiredToSpend_ = value; - onChanged(); - return this; - } - /** - *
optional uint32 sigsRequiredToSpend = 18 [default = 1];
- *
- * - * 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. - *- */ - public Builder clearSigsRequiredToSpend() { - bitField0_ = (bitField0_ & ~0x00008000); - sigsRequiredToSpend_ = 1; - onChanged(); - return this; - } - // @@protoc_insertion_point(builder_scope:wallet.Wallet) } @@ -18995,81 +18995,81 @@ public final class Protos { "\nip_address\030\001 \002(\014\022\014\n\004port\030\002 \002(\r\022\020\n\010servi" + "ces\030\003 \002(\004\"M\n\rEncryptedData\022\035\n\025initialisa" + "tion_vector\030\001 \002(\014\022\035\n\025encrypted_private_k" + - "ey\030\002 \002(\014\"y\n\020DeterministicKey\022\022\n\nchain_co" + - "de\030\001 \002(\014\022\014\n\004path\030\002 \003(\r\022\026\n\016issued_subkeys" + - "\030\003 \001(\r\022\026\n\016lookahead_size\030\004 \001(\r\022\023\n\013isFoll" + - "owing\030\005 \001(\010\"\232\003\n\003Key\022\036\n\004type\030\001 \002(\0162\020.wall" + - "et.Key.Type\022\024\n\014secret_bytes\030\002 \001(\014\022-\n\016enc" + - "rypted_data\030\006 \001(\0132\025.wallet.EncryptedData", - "\022\022\n\npublic_key\030\003 \001(\014\022\r\n\005label\030\004 \001(\t\022\032\n\022c" + - "reation_timestamp\030\005 \001(\003\0223\n\021deterministic" + - "_key\030\007 \001(\0132\030.wallet.DeterministicKey\022\032\n\022" + - "deterministic_seed\030\010 \001(\014\022;\n\034encrypted_de" + - "terministic_seed\030\t \001(\0132\025.wallet.Encrypte" + - "dData\"a\n\004Type\022\014\n\010ORIGINAL\020\001\022\030\n\024ENCRYPTED" + - "_SCRYPT_AES\020\002\022\032\n\026DETERMINISTIC_MNEMONIC\020" + - "\003\022\025\n\021DETERMINISTIC_KEY\020\004\"5\n\006Script\022\017\n\007pr" + - "ogram\030\001 \002(\014\022\032\n\022creation_timestamp\030\002 \002(\003\"" + - "\222\001\n\020TransactionInput\022\"\n\032transaction_out_", - "point_hash\030\001 \002(\014\022#\n\033transaction_out_poin" + - "t_index\030\002 \002(\r\022\024\n\014script_bytes\030\003 \002(\014\022\020\n\010s" + - "equence\030\004 \001(\r\022\r\n\005value\030\005 \001(\003\"\177\n\021Transact" + - "ionOutput\022\r\n\005value\030\001 \002(\003\022\024\n\014script_bytes" + - "\030\002 \002(\014\022!\n\031spent_by_transaction_hash\030\003 \001(" + - "\014\022\"\n\032spent_by_transaction_index\030\004 \001(\005\"\211\003" + - "\n\025TransactionConfidence\0220\n\004type\030\001 \001(\0162\"." + - "wallet.TransactionConfidence.Type\022\032\n\022app" + - "eared_at_height\030\002 \001(\005\022\036\n\026overriding_tran" + - "saction\030\003 \001(\014\022\r\n\005depth\030\004 \001(\005\022)\n\014broadcas", - "t_by\030\006 \003(\0132\023.wallet.PeerAddress\0224\n\006sourc" + - "e\030\007 \001(\0162$.wallet.TransactionConfidence.S" + - "ource\"O\n\004Type\022\013\n\007UNKNOWN\020\000\022\014\n\010BUILDING\020\001" + - "\022\013\n\007PENDING\020\002\022\025\n\021NOT_IN_BEST_CHAIN\020\003\022\010\n\004" + - "DEAD\020\004\"A\n\006Source\022\022\n\016SOURCE_UNKNOWN\020\000\022\022\n\016" + - "SOURCE_NETWORK\020\001\022\017\n\013SOURCE_SELF\020\002\"\264\005\n\013Tr" + - "ansaction\022\017\n\007version\030\001 \002(\005\022\014\n\004hash\030\002 \002(\014" + - "\022&\n\004pool\030\003 \001(\0162\030.wallet.Transaction.Pool" + - "\022\021\n\tlock_time\030\004 \001(\r\022\022\n\nupdated_at\030\005 \001(\003\022" + - "3\n\021transaction_input\030\006 \003(\0132\030.wallet.Tran", - "sactionInput\0225\n\022transaction_output\030\007 \003(\013" + - "2\031.wallet.TransactionOutput\022\022\n\nblock_has" + - "h\030\010 \003(\014\022 \n\030block_relativity_offsets\030\013 \003(" + - "\005\0221\n\nconfidence\030\t \001(\0132\035.wallet.Transacti" + - "onConfidence\0225\n\007purpose\030\n \001(\0162\033.wallet.T" + - "ransaction.Purpose:\007UNKNOWN\022+\n\rexchange_" + - "rate\030\014 \001(\0132\024.wallet.ExchangeRate\022\014\n\004memo" + - "\030\r \001(\t\"Y\n\004Pool\022\013\n\007UNSPENT\020\004\022\t\n\005SPENT\020\005\022\014" + - "\n\010INACTIVE\020\002\022\010\n\004DEAD\020\n\022\013\n\007PENDING\020\020\022\024\n\020P" + - "ENDING_INACTIVE\020\022\"\224\001\n\007Purpose\022\013\n\007UNKNOWN", - "\020\000\022\020\n\014USER_PAYMENT\020\001\022\020\n\014KEY_ROTATION\020\002\022\034" + - "\n\030ASSURANCE_CONTRACT_CLAIM\020\003\022\035\n\031ASSURANC" + - "E_CONTRACT_PLEDGE\020\004\022\033\n\027ASSURANCE_CONTRAC" + - "T_STUB\020\005\"N\n\020ScryptParameters\022\014\n\004salt\030\001 \002" + - "(\014\022\020\n\001n\030\002 \001(\003:\00516384\022\014\n\001r\030\003 \001(\005:\0018\022\014\n\001p\030" + - "\004 \001(\005:\0011\"8\n\tExtension\022\n\n\002id\030\001 \002(\t\022\014\n\004dat" + - "a\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\021TransactionSigne" + - "r\022\022\n\nclass_name\030\001 \002(\t\022\014\n\004data\030\002 \001(\014\"\211\005\n\006" + - "Wallet\022\032\n\022network_identifier\030\001 \002(\t\022\034\n\024la", - "st_seen_block_hash\030\002 \001(\014\022\036\n\026last_seen_bl" + - "ock_height\030\014 \001(\r\022!\n\031last_seen_block_time" + - "_secs\030\016 \001(\003\022\030\n\003key\030\003 \003(\0132\013.wallet.Key\022(\n" + - "\013transaction\030\004 \003(\0132\023.wallet.Transaction\022" + - "&\n\016watched_script\030\017 \003(\0132\016.wallet.Script\022" + - "C\n\017encryption_type\030\005 \001(\0162\035.wallet.Wallet" + - ".EncryptionType:\013UNENCRYPTED\0227\n\025encrypti" + - "on_parameters\030\006 \001(\0132\030.wallet.ScryptParam" + - "eters\022\022\n\007version\030\007 \001(\005:\0011\022$\n\textension\030\n" + - " \003(\0132\021.wallet.Extension\022\023\n\013description\030\013", - " \001(\t\022\031\n\021key_rotation_time\030\r \001(\004\022\031\n\004tags\030" + - "\020 \003(\0132\013.wallet.Tag\0226\n\023transaction_signer" + - "s\030\021 \003(\0132\031.wallet.TransactionSigner\022\036\n\023si" + - "gsRequiredToSpend\030\022 \001(\r:\0011\";\n\016Encryption" + - "Type\022\017\n\013UNENCRYPTED\020\001\022\030\n\024ENCRYPTED_SCRYP" + - "T_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\006Prot" + - "os" + "ey\030\002 \002(\014\"\231\001\n\020DeterministicKey\022\022\n\nchain_c" + + "ode\030\001 \002(\014\022\014\n\004path\030\002 \003(\r\022\026\n\016issued_subkey" + + "s\030\003 \001(\r\022\026\n\016lookahead_size\030\004 \001(\r\022\023\n\013isFol" + + "lowing\030\005 \001(\010\022\036\n\023sigsRequiredToSpend\030\006 \001(" + + "\r:\0011\"\232\003\n\003Key\022\036\n\004type\030\001 \002(\0162\020.wallet.Key." + + "Type\022\024\n\014secret_bytes\030\002 \001(\014\022-\n\016encrypted_", + "data\030\006 \001(\0132\025.wallet.EncryptedData\022\022\n\npub" + + "lic_key\030\003 \001(\014\022\r\n\005label\030\004 \001(\t\022\032\n\022creation" + + "_timestamp\030\005 \001(\003\0223\n\021deterministic_key\030\007 " + + "\001(\0132\030.wallet.DeterministicKey\022\032\n\022determi" + + "nistic_seed\030\010 \001(\014\022;\n\034encrypted_determini" + + "stic_seed\030\t \001(\0132\025.wallet.EncryptedData\"a" + + "\n\004Type\022\014\n\010ORIGINAL\020\001\022\030\n\024ENCRYPTED_SCRYPT" + + "_AES\020\002\022\032\n\026DETERMINISTIC_MNEMONIC\020\003\022\025\n\021DE" + + "TERMINISTIC_KEY\020\004\"5\n\006Script\022\017\n\007program\030\001" + + " \002(\014\022\032\n\022creation_timestamp\030\002 \002(\003\"\222\001\n\020Tra", + "nsactionInput\022\"\n\032transaction_out_point_h" + + "ash\030\001 \002(\014\022#\n\033transaction_out_point_index" + + "\030\002 \002(\r\022\024\n\014script_bytes\030\003 \002(\014\022\020\n\010sequence" + + "\030\004 \001(\r\022\r\n\005value\030\005 \001(\003\"\177\n\021TransactionOutp" + + "ut\022\r\n\005value\030\001 \002(\003\022\024\n\014script_bytes\030\002 \002(\014\022" + + "!\n\031spent_by_transaction_hash\030\003 \001(\014\022\"\n\032sp" + + "ent_by_transaction_index\030\004 \001(\005\"\211\003\n\025Trans" + + "actionConfidence\0220\n\004type\030\001 \001(\0162\".wallet." + + "TransactionConfidence.Type\022\032\n\022appeared_a" + + "t_height\030\002 \001(\005\022\036\n\026overriding_transaction", + "\030\003 \001(\014\022\r\n\005depth\030\004 \001(\005\022)\n\014broadcast_by\030\006 " + + "\003(\0132\023.wallet.PeerAddress\0224\n\006source\030\007 \001(\016" + + "2$.wallet.TransactionConfidence.Source\"O" + + "\n\004Type\022\013\n\007UNKNOWN\020\000\022\014\n\010BUILDING\020\001\022\013\n\007PEN" + + "DING\020\002\022\025\n\021NOT_IN_BEST_CHAIN\020\003\022\010\n\004DEAD\020\004\"" + + "A\n\006Source\022\022\n\016SOURCE_UNKNOWN\020\000\022\022\n\016SOURCE_" + + "NETWORK\020\001\022\017\n\013SOURCE_SELF\020\002\"\264\005\n\013Transacti" + + "on\022\017\n\007version\030\001 \002(\005\022\014\n\004hash\030\002 \002(\014\022&\n\004poo" + + "l\030\003 \001(\0162\030.wallet.Transaction.Pool\022\021\n\tloc" + + "k_time\030\004 \001(\r\022\022\n\nupdated_at\030\005 \001(\003\0223\n\021tran", + "saction_input\030\006 \003(\0132\030.wallet.Transaction" + + "Input\0225\n\022transaction_output\030\007 \003(\0132\031.wall" + + "et.TransactionOutput\022\022\n\nblock_hash\030\010 \003(\014" + + "\022 \n\030block_relativity_offsets\030\013 \003(\005\0221\n\nco" + + "nfidence\030\t \001(\0132\035.wallet.TransactionConfi" + + "dence\0225\n\007purpose\030\n \001(\0162\033.wallet.Transact" + + "ion.Purpose:\007UNKNOWN\022+\n\rexchange_rate\030\014 " + + "\001(\0132\024.wallet.ExchangeRate\022\014\n\004memo\030\r \001(\t\"" + + "Y\n\004Pool\022\013\n\007UNSPENT\020\004\022\t\n\005SPENT\020\005\022\014\n\010INACT" + + "IVE\020\002\022\010\n\004DEAD\020\n\022\013\n\007PENDING\020\020\022\024\n\020PENDING_", + "INACTIVE\020\022\"\224\001\n\007Purpose\022\013\n\007UNKNOWN\020\000\022\020\n\014U" + + "SER_PAYMENT\020\001\022\020\n\014KEY_ROTATION\020\002\022\034\n\030ASSUR" + + "ANCE_CONTRACT_CLAIM\020\003\022\035\n\031ASSURANCE_CONTR" + + "ACT_PLEDGE\020\004\022\033\n\027ASSURANCE_CONTRACT_STUB\020" + + "\005\"N\n\020ScryptParameters\022\014\n\004salt\030\001 \002(\014\022\020\n\001n" + + "\030\002 \001(\003:\00516384\022\014\n\001r\030\003 \001(\005:\0018\022\014\n\001p\030\004 \001(\005:\001" + + "1\"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\ncl" + + "ass_name\030\001 \002(\t\022\014\n\004data\030\002 \001(\014\"\351\004\n\006Wallet\022", + "\032\n\022network_identifier\030\001 \002(\t\022\034\n\024last_seen" + + "_block_hash\030\002 \001(\014\022\036\n\026last_seen_block_hei" + + "ght\030\014 \001(\r\022!\n\031last_seen_block_time_secs\030\016" + + " \001(\003\022\030\n\003key\030\003 \003(\0132\013.wallet.Key\022(\n\013transa" + + "ction\030\004 \003(\0132\023.wallet.Transaction\022&\n\016watc" + + "hed_script\030\017 \003(\0132\016.wallet.Script\022C\n\017encr" + + "yption_type\030\005 \001(\0162\035.wallet.Wallet.Encryp" + + "tionType:\013UNENCRYPTED\0227\n\025encryption_para" + + "meters\030\006 \001(\0132\030.wallet.ScryptParameters\022\022" + + "\n\007version\030\007 \001(\005:\0011\022$\n\textension\030\n \003(\0132\021.", + "wallet.Extension\022\023\n\013description\030\013 \001(\t\022\031\n" + + "\021key_rotation_time\030\r \001(\004\022\031\n\004tags\030\020 \003(\0132\013" + + ".wallet.Tag\0226\n\023transaction_signers\030\021 \003(\013" + + "2\031.wallet.TransactionSigner\";\n\016Encryptio" + + "nType\022\017\n\013UNENCRYPTED\020\001\022\030\n\024ENCRYPTED_SCRY" + + "PT_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_currenc" + + "y_code\030\003 \002(\tB\035\n\023org.bitcoinj.walletB\006Pro" + + "tos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -19093,7 +19093,7 @@ public final class Protos { internal_static_wallet_DeterministicKey_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_wallet_DeterministicKey_descriptor, - new java.lang.String[] { "ChainCode", "Path", "IssuedSubkeys", "LookaheadSize", "IsFollowing", }); + new java.lang.String[] { "ChainCode", "Path", "IssuedSubkeys", "LookaheadSize", "IsFollowing", "SigsRequiredToSpend", }); internal_static_wallet_Key_descriptor = getDescriptor().getMessageTypes().get(3); internal_static_wallet_Key_fieldAccessorTable = new @@ -19159,7 +19159,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", "SigsRequiredToSpend", }); + new java.lang.String[] { "NetworkIdentifier", "LastSeenBlockHash", "LastSeenBlockHeight", "LastSeenBlockTimeSecs", "Key", "Transaction", "WatchedScript", "EncryptionType", "EncryptionParameters", "Version", "Extension", "Description", "KeyRotationTime", "Tags", "TransactionSigners", }); internal_static_wallet_ExchangeRate_descriptor = getDescriptor().getMessageTypes().get(14); internal_static_wallet_ExchangeRate_fieldAccessorTable = new diff --git a/core/src/test/java/org/bitcoinj/core/WalletTest.java b/core/src/test/java/org/bitcoinj/core/WalletTest.java index 04a6b77f..c3c82ae4 100644 --- a/core/src/test/java/org/bitcoinj/core/WalletTest.java +++ b/core/src/test/java/org/bitcoinj/core/WalletTest.java @@ -111,7 +111,11 @@ public class WalletTest extends TestWithWallet { wallet.addTransactionSigner(new KeyChainTransactionSigner(keyChain)); } - wallet.addFollowingAccountKeys(followingKeys, threshold); + MarriedKeyChain chain = MarriedKeyChain.builder() + .random(new SecureRandom()) + .followingKeys(followingKeys) + .threshold(threshold).build(); + wallet.addAndActivateHDChain(chain); } @Test @@ -2652,7 +2656,11 @@ public class WalletTest extends TestWithWallet { } }; wallet.addTransactionSigner(signer); - wallet.addFollowingAccountKeys(ImmutableList.of(partnerKey)); + MarriedKeyChain chain = MarriedKeyChain.builder() + .random(new SecureRandom()) + .followingKeys(partnerKey) + .build(); + wallet.addAndActivateHDChain(chain); myAddress = wallet.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS); sendMoneyToWallet(wallet, COIN, myAddress, AbstractBlockChain.NewBlockType.BEST_CHAIN); diff --git a/core/src/test/java/org/bitcoinj/store/WalletProtobufSerializerTest.java b/core/src/test/java/org/bitcoinj/store/WalletProtobufSerializerTest.java index bed91ac5..67c300cc 100644 --- a/core/src/test/java/org/bitcoinj/store/WalletProtobufSerializerTest.java +++ b/core/src/test/java/org/bitcoinj/store/WalletProtobufSerializerTest.java @@ -31,6 +31,8 @@ import org.bitcoinj.wallet.DeterministicKeyChain; import org.bitcoinj.wallet.KeyChain; import com.google.common.collect.ImmutableList; import com.google.protobuf.ByteString; + +import org.bitcoinj.wallet.MarriedKeyChain; import org.bitcoinj.wallet.Protos; import org.junit.Before; import org.junit.Test; @@ -287,16 +289,20 @@ public class WalletProtobufSerializerTest { 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()); + final DeterministicKeyChain partnerChain = new DeterministicKeyChain(new SecureRandom()); + DeterministicKey partnerKey = DeterministicKey.deserializeB58(null, partnerChain.getWatchingKey().serializePubB58()); + MarriedKeyChain chain = MarriedKeyChain.builder() + .random(new SecureRandom()) + .followingKeys(partnerKey) + .threshold(2).build(); + myWallet.addAndActivateHDChain(chain); - 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(2, wallet1.getActiveKeychain().getSigsRequiredToSpend()); assertEquals(myAddress, wallet1.currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS)); } diff --git a/core/src/test/java/org/bitcoinj/wallet/KeyChainGroupTest.java b/core/src/test/java/org/bitcoinj/wallet/KeyChainGroupTest.java index 4e0520d7..8629d731 100644 --- a/core/src/test/java/org/bitcoinj/wallet/KeyChainGroupTest.java +++ b/core/src/test/java/org/bitcoinj/wallet/KeyChainGroupTest.java @@ -56,14 +56,24 @@ 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), 2); + KeyChainGroup group = new KeyChainGroup(params); + DeterministicKeyChain chain = createMarriedKeyChain(); + group.addAndActivateHDChain(chain); group.setLookaheadSize(LOOKAHEAD_SIZE); group.getActiveKeyChain(); return group; } + private MarriedKeyChain createMarriedKeyChain() { + 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); + MarriedKeyChain chain = MarriedKeyChain.builder() + .seed(seed) + .followingKeys(watchingAccountKey) + .threshold(2).build(); + return chain; + } + @Test public void freshCurrentKeys() throws Exception { int numKeys = ((group.getLookaheadSize() + group.getLookaheadThreshold()) * 2) // * 2 because of internal/external @@ -400,7 +410,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(), 1); + group = KeyChainGroup.fromProtobufUnencrypted(params, group.serializeToProtobuf()); group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicKey key1 = group.freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicKey key2 = group.freshKey(KeyChain.KeyPurpose.CHANGE); @@ -411,13 +421,13 @@ public class KeyChainGroupTest { List