All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bitcoinj.wallet.MarriedKeyChain Maven / Gradle / Ivy

There is a newer version: 21.1.2
Show newest version
/*
 * Copyright by the original author or authors.
 * 
 * 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 org.bitcoinj.wallet;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.protobuf.ByteString;

import org.bitcoinj.core.BloomFilter;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypter;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bouncycastle.crypto.params.KeyParameter;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Nullable;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Lists.newArrayList;

/**
 * 

A 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 Dash Core 0.9 all bare (non-P2SH) 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 marriedKeysRedeemData = new LinkedHashMap<>(); private List followingKeyChains; /** Builds a {@link MarriedKeyChain} */ public static class Builder> extends DeterministicKeyChain.Builder { private List followingKeys; private int threshold; protected Builder() { } public T followingKeys(List followingKeys) { this.followingKeys = followingKeys; return self(); } public T followingKeys(DeterministicKey followingKey, DeterministicKey ...followingKeys) { this.followingKeys = Lists.asList(followingKey, followingKeys); return self(); } /** *

Threshold, or {@code (followingKeys.size() + 1) / 2 + 1)} (majority) if unspecified.

*

IMPORTANT: As of Dash 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(); } @Override public MarriedKeyChain build() { checkNotNull(followingKeys, "followingKeys must be provided"); if (threshold == 0) threshold = (followingKeys.size() + 1) / 2 + 1; if (accountPath == null) accountPath = ACCOUNT_ZERO_PATH; MarriedKeyChain chain; if (random != null) chain = new MarriedKeyChain(new DeterministicSeed(random, bits, getPassphrase()), null, outputScriptType, accountPath); else if (entropy != null) chain = new MarriedKeyChain(new DeterministicSeed(entropy, getPassphrase(), creationTimeSecs), null, outputScriptType, accountPath); else if (seed != null) chain = new MarriedKeyChain(seed, null, outputScriptType, accountPath); else if (watchingKey != null) chain = new MarriedKeyChain(watchingKey, outputScriptType); else throw new IllegalStateException(); chain.addFollowingAccountKeys(followingKeys, threshold); return chain; } } public static Builder builder() { return new Builder(); } /** * This constructor is not stable across releases! If you need a stable API, use {@link #builder()} to use a * {@link Builder}. */ protected MarriedKeyChain(DeterministicKey accountKey, Script.ScriptType outputScriptType) { super(accountKey, false, true, outputScriptType); } /** * This constructor is not stable across releases! If you need a stable API, use {@link #builder()} to use a * {@link Builder}. */ protected MarriedKeyChain(DeterministicSeed seed, KeyCrypter crypter, Script.ScriptType outputScriptType, ImmutableList accountPath) { super(seed, crypter, outputScriptType, accountPath); } void setFollowingKeyChains(List followingKeyChains) { checkArgument(!followingKeyChains.isEmpty()); this.followingKeyChains = followingKeyChains; } @Override public boolean isMarried() { return true; } /** Create a new married key and return the matching output script */ @Override public Script freshOutputScript(KeyPurpose purpose) { DeterministicKey followedKey = getKey(purpose); ImmutableList.Builder keys = ImmutableList.builder().add(followedKey); for (DeterministicKeyChain keyChain : followingKeyChains) { DeterministicKey followingKey = keyChain.getKey(purpose); checkState(followedKey.getChildNumber().equals(followingKey.getChildNumber()), "Following keychains should be in sync"); keys.add(followingKey); } List marriedKeys = keys.build(); Script redeemScript = ScriptBuilder.createRedeemScript(sigsRequiredToSpend, marriedKeys); return ScriptBuilder.createP2SHOutputScript(redeemScript); } private List getMarriedKeysWithFollowed(DeterministicKey followedKey) { ImmutableList.Builder keys = ImmutableList.builder(); for (DeterministicKeyChain keyChain : followingKeyChains) { keyChain.maybeLookAhead(); keys.add(keyChain.getKeyByPath(followedKey.getPath())); } keys.add(followedKey); return keys.build(); } /** Get the redeem data for a key in this married chain */ @Override public RedeemData getRedeemData(DeterministicKey followedKey) { List marriedKeys = getMarriedKeysWithFollowed(followedKey); Script redeemScript = ScriptBuilder.createRedeemScript(sigsRequiredToSpend, marriedKeys); return RedeemData.of(marriedKeys, redeemScript); } private void addFollowingAccountKeys(List followingAccountKeys, int sigsRequiredToSpend) { checkArgument(sigsRequiredToSpend <= followingAccountKeys.size() + 1, "Multisig threshold can't exceed total number of keys"); checkState(numLeafKeysIssued() == 0, "Active keychain already has keys in use"); checkState(followingKeyChains == null); List followingKeyChains = Lists.newArrayList(); for (DeterministicKey key : followingAccountKeys) { checkArgument(key.getPath().size() == getAccountPath().size(), "Following keys have to be account keys"); DeterministicKeyChain chain = DeterministicKeyChain.builder().watchAndFollow(key) .outputScriptType(getOutputScriptType()).build(); if (lookaheadSize >= 0) chain.setLookaheadSize(lookaheadSize); if (lookaheadThreshold >= 0) chain.setLookaheadThreshold(lookaheadThreshold); followingKeyChains.add(chain); } this.sigsRequiredToSpend = sigsRequiredToSpend; this.followingKeyChains = followingKeyChains; } @Override public void setLookaheadSize(int lookaheadSize) { lock.lock(); try { super.setLookaheadSize(lookaheadSize); if (followingKeyChains != null) { for (DeterministicKeyChain followingChain : followingKeyChains) { followingChain.setLookaheadSize(lookaheadSize); } } } finally { lock.unlock(); } } @Override public List serializeToProtobuf() { List result = newArrayList(); lock.lock(); try { for (DeterministicKeyChain chain : followingKeyChains) { result.addAll(chain.serializeMyselfToProtobuf()); } result.addAll(serializeMyselfToProtobuf()); } finally { lock.unlock(); } return result; } @Override protected void formatAddresses(boolean includeLookahead, boolean includePrivateKeys, @Nullable KeyParameter aesKey, NetworkParameters params, StringBuilder builder) { for (DeterministicKeyChain followingChain : followingKeyChains) builder.append("Following chain: ").append(followingChain.getWatchingKey().serializePubB58(params)) .append('\n'); builder.append('\n'); for (RedeemData redeemData : marriedKeysRedeemData.values()) formatScript(ScriptBuilder.createP2SHOutputScript(redeemData.redeemScript), builder, params); } private void formatScript(Script script, StringBuilder builder, NetworkParameters params) { builder.append(" addr:"); builder.append(script.getToAddress(params)); builder.append(" hash160:"); builder.append(Utils.HEX.encode(script.getPubKeyHash())); if (script.getCreationTimeSeconds() > 0) builder.append(" creationTimeSeconds:").append(script.getCreationTimeSeconds()); builder.append('\n'); } @Override public void maybeLookAheadScripts() { super.maybeLookAheadScripts(); int numLeafKeys = getLeafKeys().size(); checkState(marriedKeysRedeemData.size() <= numLeafKeys, "Number of scripts is greater than number of leaf keys"); if (marriedKeysRedeemData.size() == numLeafKeys) return; maybeLookAhead(); for (DeterministicKey followedKey : getLeafKeys()) { RedeemData redeemData = getRedeemData(followedKey); Script scriptPubKey = ScriptBuilder.createP2SHOutputScript(redeemData.redeemScript); marriedKeysRedeemData.put(ByteString.copyFrom(scriptPubKey.getPubKeyHash()), redeemData); } } @Nullable @Override public RedeemData findRedeemDataByScriptHash(ByteString bytes) { return marriedKeysRedeemData.get(bytes); } @Override public BloomFilter getFilter(int size, double falsePositiveRate, long tweak) { lock.lock(); BloomFilter filter; try { filter = new BloomFilter(size, falsePositiveRate, tweak); for (Map.Entry entry : marriedKeysRedeemData.entrySet()) { filter.insert(entry.getKey().toByteArray()); filter.insert(entry.getValue().redeemScript.getProgram()); } } finally { lock.unlock(); } return filter; } @Override public int numBloomFilterEntries() { maybeLookAhead(); return getLeafKeys().size() * 2; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy