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

com.google.bitcoin.crypto.HDKeyDerivation Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2013 Matija Mazi.
 *
 * 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.crypto;

import com.google.bitcoin.core.ECKey;
import com.google.common.collect.ImmutableList;
import org.spongycastle.crypto.macs.HMac;
import org.spongycastle.math.ec.ECPoint;

import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;

/**
 * Implementation of the (public derivation version) deterministic wallet child key generation algorithm.
 */
public final class HDKeyDerivation {

    private HDKeyDerivation() { }

    private static final HMac MASTER_HMAC_SHA512 = HDUtils.createHmacSha512Digest("Bitcoin seed".getBytes());

    /**
     * Generates a new deterministic key from the given seed, which can be any arbitrary byte array. However resist
     * the temptation to use a string as the seed - any key derived from a password is likely to be weak and easily
     * broken by attackers (this is not theoretical, people have had money stolen that way). This method checks
     * that the given seed is at least 64 bits long.
     *
     * @throws HDDerivationException if generated master key is invalid (private key 0 or >= n).
     * @throws IllegalArgumentException if the seed is less than 8 bytes and could be brute forced.
     */
    public static DeterministicKey createMasterPrivateKey(byte[] seed) throws HDDerivationException {
        checkArgument(seed.length > 8, "Seed is too short and could be brute forced");
        // Calculate I = HMAC-SHA512(key="Bitcoin seed", msg=S)
        byte[] i = HDUtils.hmacSha512(MASTER_HMAC_SHA512, seed);
        // Split I into two 32-byte sequences, Il and Ir.
        // Use Il as master secret key, and Ir as master chain code.
        checkState(i.length == 64, i.length);
        byte[] il = Arrays.copyOfRange(i, 0, 32);
        byte[] ir = Arrays.copyOfRange(i, 32, 64);
        Arrays.fill(i, (byte)0);
        DeterministicKey masterPrivKey = createMasterPrivKeyFromBytes(il, ir);
        Arrays.fill(il, (byte)0);
        Arrays.fill(ir, (byte)0);
        return masterPrivKey;
    }

    /**
     * @throws HDDerivationException if privKeyBytes is invalid (0 or >= n).
     */
    public static DeterministicKey createMasterPrivKeyFromBytes(
            byte[] privKeyBytes, byte[] chainCode) throws HDDerivationException {
        BigInteger privateKeyFieldElt = new BigInteger(1, privKeyBytes);
        assertNonZero(privateKeyFieldElt, "Generated master key is invalid.");
        assertLessThanN(privateKeyFieldElt, "Generated master key is invalid.");
        return new DeterministicKey(ImmutableList.of(), chainCode, null, privateKeyFieldElt, null);
    }

    public static DeterministicKey createMasterPubKeyFromBytes(byte[] pubKeyBytes, byte[] chainCode) {
        return new DeterministicKey(ImmutableList.of(), chainCode, ECKey.CURVE.getCurve().decodePoint(pubKeyBytes), null, null);
    }

    /**
     * @param childNumber the "extended" child number, ie. with the 0x80000000 bit specifying private/public derivation.
     */
    public static DeterministicKey deriveChildKey(DeterministicKey parent, int childNumber) {
        return deriveChildKey(parent, new ChildNumber(childNumber));
    }

    /**
     * @throws HDDerivationException if private derivation is attempted for a public-only parent key, or
     * if the resulting derived key is invalid (eg. private key == 0).
     */
    public static DeterministicKey deriveChildKey(DeterministicKey parent, ChildNumber childNumber)
            throws HDDerivationException {

        RawKeyBytes rawKey = deriveChildKeyBytes(parent, childNumber);
        return new DeterministicKey(
                HDUtils.append(parent.getChildNumberPath(), childNumber),
                rawKey.chainCode,
                parent.hasPrivate() ? null : ECKey.CURVE.getCurve().decodePoint(rawKey.keyBytes),
                parent.hasPrivate() ? new BigInteger(1, rawKey.keyBytes) : null,
                parent);
    }

    private static RawKeyBytes deriveChildKeyBytes(DeterministicKey parent, ChildNumber childNumber)
            throws HDDerivationException {

        byte[] parentPublicKey = HDUtils.getBytes(parent.getPubPoint());
        assert parentPublicKey.length == 33 : parentPublicKey.length;
        ByteBuffer data = ByteBuffer.allocate(37);
        if (childNumber.isPrivateDerivation()) {
            data.put(parent.getPrivKeyBytes33());
        } else {
            data.put(parentPublicKey);
        }
        data.putInt(childNumber.getI());
        byte[] i = HDUtils.hmacSha512(parent.getChainCode(), data.array());
        assert i.length == 64 : i.length;
        byte[] il = Arrays.copyOfRange(i, 0, 32);
        byte[] chainCode = Arrays.copyOfRange(i, 32, 64);
        BigInteger ilInt = new BigInteger(1, il);
        assertLessThanN(ilInt, "Illegal derived key: I_L >= n");
        byte[] keyBytes;
        final BigInteger privAsFieldElement = parent.getPrivAsFieldElement();
        if (privAsFieldElement != null) {
            BigInteger ki = privAsFieldElement.add(ilInt).mod(ECKey.CURVE.getN());
            assertNonZero(ki, "Illegal derived key: derived private key equals 0.");
            keyBytes = ki.toByteArray();
        } else {
            checkArgument(!childNumber.isPrivateDerivation(), "Can't use private derivation with public keys only.");
            ECPoint Ki = ECKey.CURVE.getG().multiply(ilInt).add(parent.getPubPoint());
            checkArgument(!Ki.equals(ECKey.CURVE.getCurve().getInfinity()),
                    "Illegal derived key: derived public key equals infinity.");
            keyBytes = HDUtils.toCompressed(Ki.getEncoded());
        }
        return new RawKeyBytes(keyBytes, chainCode);
    }

    private static void assertNonZero(BigInteger integer, String errorMessage) {
        checkArgument(!integer.equals(BigInteger.ZERO), errorMessage);
    }

    private static void assertLessThanN(BigInteger integer, String errorMessage) {
        checkArgument(integer.compareTo(ECKey.CURVE.getN()) < 0, errorMessage);
    }

    private static class RawKeyBytes {
        private final byte[] keyBytes, chainCode;

        private RawKeyBytes(byte[] keyBytes, byte[] chainCode) {
            this.keyBytes = keyBytes;
            this.chainCode = chainCode;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy