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: 0.17-beta1
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.protobuf.ByteString;
import org.bitcoinj.base.ScriptType;
import org.bitcoinj.crypto.AesKey;
import org.bitcoinj.base.internal.ByteUtils;
import org.bitcoinj.core.BloomFilter;
import org.bitcoinj.crypto.ECKey;
import org.bitcoinj.core.NetworkParameters;
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 javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.bitcoinj.base.internal.Preconditions.checkArgument;
import static org.bitcoinj.base.internal.Preconditions.checkState;

/**
 * 

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 Bitcoin 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 followingKey(DeterministicKey followingKey) { this.followingKeys = Collections.singletonList(followingKey); return self(); } public T followingKeys(List followingKeys) { this.followingKeys = followingKeys; return self(); } /** * @deprecated Merge the elements and call {@link #followingKeys(List)} */ @Deprecated public T followingKeys(DeterministicKey followingKey, DeterministicKey ...followingKeys) { List tempList = new ArrayList<>(); tempList.add(followingKey); tempList.addAll(Arrays.asList(followingKeys)); this.followingKeys = tempList; return self(); } /** *

Threshold, or {@code (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(); } @Override public MarriedKeyChain build() { Objects.requireNonNull(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(DeterministicSeed.ofRandom(random, bits, getPassphrase()), null, outputScriptType, accountPath); else if (entropy != null) chain = new MarriedKeyChain(DeterministicSeed.ofEntropy(entropy, getPassphrase(), creationTime), 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, 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, ScriptType outputScriptType, List 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); List keys = new ArrayList<>(); keys.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 = Collections.unmodifiableList(keys); Script redeemScript = ScriptBuilder.createRedeemScript(sigsRequiredToSpend, marriedKeys); return ScriptBuilder.createP2SHOutputScript(redeemScript); } private List getMarriedKeysWithFollowed(DeterministicKey followedKey) { List keys = new ArrayList<>(); for (DeterministicKeyChain keyChain : followingKeyChains) { keyChain.maybeLookAhead(); keys.add(keyChain.getKeyByPath(followedKey.getPath())); } keys.add(followedKey); return Collections.unmodifiableList(keys); } /** 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 = new ArrayList<>(); 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(); } } /** * Serialize to list of keys * @return a list of keys (treat as unmodifiable list, will change in future release) */ @Override public List serializeToProtobuf() { lock.lock(); try { Stream followingStream = followingKeyChains.stream() .flatMap(chain -> chain.serializeMyselfToProtobuf().stream()); return Stream.concat(followingStream, serializeMyselfToProtobuf().stream()) .collect(Collectors.toList()); } finally { lock.unlock(); } } @Override protected void formatAddresses(boolean includeLookahead, boolean includePrivateKeys, @Nullable AesKey aesKey, NetworkParameters params, StringBuilder builder) { for (DeterministicKeyChain followingChain : followingKeyChains) builder.append("Following chain: ").append(followingChain.getWatchingKey().serializePubB58(params.network())) .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(ByteUtils.formatHex(script.getPubKeyHash())); script.creationTime().ifPresent(creationTime -> builder.append(" creationTimeSeconds:").append(creationTime)); 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, int 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