mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-01 07:42:17 +00:00
Add support classes for hierarchical deterministic wallets (BIP 32)
Code from Matija Mazi. HD wallets allow you to derive keys from a single root key, giving various useful features: - Make a backup once and it's good forever (for your keys only of course) - You can break off parts of the tree and give it to other people, they can then generate new keys to send you money without any involvement by you (better privacy+security for watching wallets) - You can delegate sub-trees to other people as a form of access control.
This commit is contained in:
parent
00f4c82342
commit
c3fd83e511
@ -0,0 +1,60 @@
|
||||
package com.google.bitcoin.crypto.hd;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
*
|
||||
* This is just a wrapper for the i (child number) as per BIP 32 with a boolean getter for the first bit and a getter
|
||||
* for the actual 0-based child number.
|
||||
*
|
||||
* This class is immutable.
|
||||
*/
|
||||
public class ChildNumber {
|
||||
public static final int PRIV_BIT = 0x80000000;
|
||||
|
||||
/** Integer i as per BIP 32 spec, including the MSB denoting derivation type (0 = public, 1 = private) **/
|
||||
private final int i;
|
||||
|
||||
public ChildNumber(int childNumber, boolean isPrivate) {
|
||||
if (hasPrivateBit(childNumber)) {
|
||||
throw new IllegalArgumentException("Most significant bit is reserved and shouldn't be set: " + childNumber);
|
||||
}
|
||||
i = isPrivate ? (childNumber | PRIV_BIT) : childNumber;
|
||||
}
|
||||
|
||||
public ChildNumber(int i) {
|
||||
this.i = i;
|
||||
}
|
||||
|
||||
public int getI() {
|
||||
return i;
|
||||
}
|
||||
|
||||
public boolean isPrivateDerivation() {
|
||||
return hasPrivateBit(i);
|
||||
}
|
||||
|
||||
private static boolean hasPrivateBit(int a) {
|
||||
return (a & PRIV_BIT) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the child number without the private/public derivation bit set.
|
||||
*/
|
||||
public int getChildNumber() {
|
||||
return i & (~PRIV_BIT);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%d%s", getChildNumber(), isPrivateDerivation() ? "'" : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return this == o || !(o == null || getClass() != o.getClass()) && i == ((ChildNumber) o).i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return i;
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package com.google.bitcoin.crypto.hd;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
*
|
||||
* A DeterministicHierarchy calculates and keeps a whole tree (hierarchy) of keys originating from a single
|
||||
* root key.
|
||||
*/
|
||||
public class DeterministicHierarchy implements Serializable {
|
||||
|
||||
/**
|
||||
* Child derivation may fail (although with extremely low probability); in such case it is re-attempted.
|
||||
* This is the maximum number of re-attempts (to avoid an infinite loop in case of bugs etc.).
|
||||
*/
|
||||
private static final int MAX_CHILD_DERIVATION_ATTEMPTS = 100;
|
||||
|
||||
private final Map<ImmutableList<ChildNumber>, ExtendedHierarchicKey> keys = Maps.newHashMap();
|
||||
private final ImmutableList<ChildNumber> rootPath;
|
||||
private final Map<ImmutableList<ChildNumber>, ChildNumber> lastPrivDerivedNumbers = Maps.newHashMap();
|
||||
private final Map<ImmutableList<ChildNumber>, ChildNumber> lastPubDerivedNumbers = Maps.newHashMap();
|
||||
|
||||
public DeterministicHierarchy(ExtendedHierarchicKey rootKey) {
|
||||
putKey(rootKey);
|
||||
rootPath = rootKey.getChildNumberPath();
|
||||
}
|
||||
|
||||
private void putKey(ExtendedHierarchicKey key) {
|
||||
keys.put(key.getChildNumberPath(), key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path the path to the key
|
||||
* @param relativePath whether the path is relative to the root path
|
||||
* @param create whether the key corresponding to path should be created (with any necessary ancestors) if it doesn't exist already
|
||||
* @return next newly created key using the child derivation funtcion
|
||||
*/
|
||||
public ExtendedHierarchicKey get(List<ChildNumber> path, boolean relativePath, boolean create) {
|
||||
ImmutableList<ChildNumber> absolutePath = relativePath
|
||||
? ImmutableList.<ChildNumber>builder().addAll(rootPath).addAll(path).build()
|
||||
: ImmutableList.copyOf(path);
|
||||
if (!keys.containsKey(absolutePath)) {
|
||||
Preconditions.checkArgument(create, "No key found for {} path {}.", relativePath ? "relative" : "absolute", path);
|
||||
Preconditions.checkArgument(absolutePath.size() > 0, "Can't derive the master key: nothing to derive from.");
|
||||
ExtendedHierarchicKey parent = get(absolutePath.subList(0, absolutePath.size() - 1), relativePath, create);
|
||||
putKey(HDKeyDerivation.deriveChildKey(parent, absolutePath.get(absolutePath.size() - 1)));
|
||||
}
|
||||
return keys.get(absolutePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param parentPath the path to the parent
|
||||
* @param relativePath whether the path is relative to the root path
|
||||
* @param createParent whether the parent corresponding to path should be created (with any necessary ancestors) if it doesn't exist already
|
||||
* @param privateDerivation whether to use private or public derivation
|
||||
* @return next newly created key using the child derivation funtcion
|
||||
*/
|
||||
public ExtendedHierarchicKey deriveNextChild(ImmutableList<ChildNumber> parentPath, boolean relativePath, boolean createParent, boolean privateDerivation) {
|
||||
ExtendedHierarchicKey parent = get(parentPath, relativePath, createParent);
|
||||
int nAttempts = 0;
|
||||
while (nAttempts++ < MAX_CHILD_DERIVATION_ATTEMPTS) {
|
||||
try {
|
||||
ChildNumber createChildNumber = getNextChildNumberToDerive(parent.getChildNumberPath(), privateDerivation);
|
||||
return deriveChild(parent, createChildNumber);
|
||||
} catch (HDDerivationException ignore) { }
|
||||
}
|
||||
throw new HDDerivationException("Maximum number of child derivation attempts reached, this is probably an indication of a bug.");
|
||||
}
|
||||
|
||||
private ChildNumber getNextChildNumberToDerive(ImmutableList<ChildNumber> path, boolean privateDerivation) {
|
||||
Map<ImmutableList<ChildNumber>, ChildNumber> lastDerivedNumbers = getLastDerivedNumbers(privateDerivation);
|
||||
ChildNumber lastChildNumber = lastDerivedNumbers.get(path);
|
||||
ChildNumber nextChildNumber = new ChildNumber(lastChildNumber != null ? lastChildNumber.getChildNumber() + 1 : 0, privateDerivation);
|
||||
lastDerivedNumbers.put(path, nextChildNumber);
|
||||
return nextChildNumber;
|
||||
}
|
||||
|
||||
public ExtendedHierarchicKey deriveChild(List<ChildNumber> parentPath, boolean relativePath, boolean createParent, ChildNumber createChildNumber) {
|
||||
return deriveChild(get(parentPath, relativePath, createParent), createChildNumber);
|
||||
}
|
||||
|
||||
private ExtendedHierarchicKey deriveChild(ExtendedHierarchicKey parent, ChildNumber createChildNumber) {
|
||||
ExtendedHierarchicKey childKey = HDKeyDerivation.deriveChildKey(parent, createChildNumber);
|
||||
putKey(childKey);
|
||||
return childKey;
|
||||
}
|
||||
|
||||
public ExtendedHierarchicKey getRootKey() {
|
||||
return get(rootPath, false, false);
|
||||
}
|
||||
|
||||
private Map<ImmutableList<ChildNumber>, ChildNumber> getLastDerivedNumbers(boolean privateDerivation) {
|
||||
return privateDerivation ? lastPrivDerivedNumbers : lastPubDerivedNumbers;
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
package com.google.bitcoin.crypto.hd;
|
||||
|
||||
import com.google.bitcoin.core.Base58;
|
||||
import com.google.bitcoin.core.ECKey;
|
||||
import com.google.bitcoin.core.Utils;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
*
|
||||
* Extended key as per BIP 32 is a pair (key, chaincode).
|
||||
*/
|
||||
public class ExtendedHierarchicKey implements Serializable {
|
||||
public static final ChildNumber MASTER_CHILD_NUMBER = new ChildNumber(0);
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Joiner PATH_JOINER = Joiner.on("/");
|
||||
|
||||
private final ExtendedHierarchicKey parent;
|
||||
private ECPoint publicAsPoint;
|
||||
private final BigInteger privateAsFieldElement;
|
||||
private final ImmutableList<ChildNumber> childNumberPath;
|
||||
|
||||
/** 32 bytes */
|
||||
private byte[] chainCode;
|
||||
|
||||
ExtendedHierarchicKey(ImmutableList<ChildNumber> childNumberPath, byte[] chainCode, ECPoint publicAsPoint, BigInteger privateKeyFieldElt, ExtendedHierarchicKey parent) {
|
||||
assert chainCode.length == 32 : chainCode.length;
|
||||
this.parent = parent;
|
||||
this.childNumberPath = childNumberPath;
|
||||
this.chainCode = Arrays.copyOf(chainCode, chainCode.length);
|
||||
this.publicAsPoint = publicAsPoint == null ? null : HDUtils.compressedCopy(publicAsPoint);
|
||||
this.privateAsFieldElement = privateKeyFieldElt;
|
||||
}
|
||||
|
||||
public ImmutableList<ChildNumber> getChildNumberPath() {
|
||||
return childNumberPath;
|
||||
}
|
||||
|
||||
private int getDepth() {
|
||||
return childNumberPath.size();
|
||||
}
|
||||
|
||||
public ChildNumber getChildNumber() {
|
||||
return getDepth() == 0 ? MASTER_CHILD_NUMBER : childNumberPath.get(childNumberPath.size() - 1);
|
||||
}
|
||||
|
||||
byte[] getChainCode() {
|
||||
return chainCode;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return PATH_JOINER.join(Iterables.concat(Collections.singleton("M"), getChildNumberPath()));
|
||||
}
|
||||
|
||||
public byte[] getIdentifier() {
|
||||
return Utils.sha256hash160(getPubKeyBytes());
|
||||
}
|
||||
|
||||
ECPoint getPubPoint() {
|
||||
if (publicAsPoint == null && privateAsFieldElement != null) {
|
||||
publicAsPoint = HDUtils.getEcParams().getG().multiply(privateAsFieldElement);
|
||||
}
|
||||
return HDUtils.compressedCopy(publicAsPoint);
|
||||
}
|
||||
|
||||
public byte[] getPubKeyBytes() {
|
||||
return getPubPoint().getEncoded();
|
||||
}
|
||||
|
||||
public byte[] getFingerprint() {
|
||||
// todo: why is this different than armory's fingerprint? BIP 32: "The first 32 bits of the identifier are called the fingerprint."
|
||||
return Arrays.copyOfRange(getIdentifier(), 0, 4);
|
||||
}
|
||||
|
||||
public BigInteger getPrivAsFieldElement() {
|
||||
return privateAsFieldElement;
|
||||
}
|
||||
|
||||
public ExtendedHierarchicKey getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public byte[] getPrivKeyBytes() {
|
||||
return privateAsFieldElement == null ? null : privateAsFieldElement.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return private key bytes, padded with zeros to 33 bytes.
|
||||
*/
|
||||
public byte[] getPrivKeyBytes33() {
|
||||
byte[] bytes33 = new byte[33];
|
||||
byte[] priv = getPrivKeyBytes();
|
||||
System.arraycopy(priv, 0, bytes33, 33 - priv.length, priv.length);
|
||||
return bytes33;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The same key with the private part removed. May return the same instance.
|
||||
*/
|
||||
public ExtendedHierarchicKey getPubOnly() {
|
||||
return hasPrivate() ? new ExtendedHierarchicKey(getChildNumberPath(), getChainCode(), getPubPoint(), null, getParent() == null ? null : getParent().getPubOnly()) : this;
|
||||
}
|
||||
|
||||
public boolean hasPrivate() {
|
||||
return privateAsFieldElement != null;
|
||||
}
|
||||
|
||||
public ECKey toECKey() {
|
||||
return new ECKey(getPrivKeyBytes(), getPubKeyBytes());
|
||||
}
|
||||
|
||||
public String serializePubB58() {
|
||||
return toBase58(serialize(true));
|
||||
}
|
||||
|
||||
public String serializePrivB58() {
|
||||
return toBase58(serialize(false));
|
||||
}
|
||||
|
||||
static String toBase58(byte[] ser) {
|
||||
return Base58.encode(addChecksum(ser));
|
||||
}
|
||||
|
||||
static byte[] addChecksum(byte[] input) {
|
||||
int inputLength = input.length;
|
||||
byte[] checksummed = new byte[inputLength + 4];
|
||||
System.arraycopy(input, 0, checksummed, 0, inputLength);
|
||||
byte[] checksum = Utils.doubleDigest(input);
|
||||
System.arraycopy(checksum, 0, checksummed, inputLength, 4);
|
||||
return checksummed;
|
||||
}
|
||||
|
||||
public byte[] serializePublic() {
|
||||
return serialize(true);
|
||||
}
|
||||
|
||||
public byte[] serializePrivate() {
|
||||
return serialize(false);
|
||||
}
|
||||
|
||||
private byte[] serialize(boolean pub) {
|
||||
ByteBuffer ser = ByteBuffer.allocate(78);
|
||||
ser.putInt(pub ? 0x0488B21E : 0x0488ADE4);
|
||||
ser.put((byte) getDepth());
|
||||
if (parent == null) {
|
||||
ser.putInt(0);
|
||||
} else {
|
||||
ser.put(parent.getFingerprint());
|
||||
}
|
||||
ser.putInt(getChildNumber().getI());
|
||||
ser.put(getChainCode());
|
||||
ser.put(pub ? getPubKeyBytes() : getPrivKeyBytes33());
|
||||
assert ser.position() == 78;
|
||||
|
||||
return ser.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MessageFormat.format("ExtendedHierarchicKey[pub: {0}]", new String(Hex.encode(getPubKeyBytes())));
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package com.google.bitcoin.crypto.hd;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
* @created 6/21/13 4:03 PM
|
||||
*/
|
||||
public class HDDerivationException extends RuntimeException {
|
||||
public HDDerivationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
package com.google.bitcoin.crypto.hd;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
*
|
||||
* Implementation of the (public derivation version) child key generation algorithm as a utility class.
|
||||
*/
|
||||
public final class HDKeyDerivation {
|
||||
|
||||
private HDKeyDerivation() { }
|
||||
|
||||
private static final HMac MASTER_HMAC_SHA256 = HDUtils.createHmacSha256Digest("Bitcoin seed".getBytes());
|
||||
|
||||
/**
|
||||
* @throws HDDerivationException if generated master key is invalid (private key 0 or >= n).
|
||||
*/
|
||||
public static ExtendedHierarchicKey createMasterPrivateKey(byte[] seed) throws HDDerivationException {
|
||||
// Calculate I = HMAC-SHA512(key="Bitcoin seed", msg=S)
|
||||
byte[] i = HDUtils.hmacSha256(MASTER_HMAC_SHA256, seed);
|
||||
// Split I into two 32-byte sequences, Il and Ir.
|
||||
// Use Il as master secret key, and Ir as master chain code.
|
||||
assert i.length == 64 : i.length;
|
||||
byte[] il = Arrays.copyOfRange(i, 0, 32);
|
||||
byte[] ir = Arrays.copyOfRange(i, 32, 64);
|
||||
Arrays.fill(i, (byte)0);
|
||||
ExtendedHierarchicKey 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).
|
||||
*/
|
||||
static ExtendedHierarchicKey createMasterPrivKeyFromBytes(byte[] privKeyBytes, byte[] chainCode) throws HDDerivationException {
|
||||
BigInteger privateKeyFieldElt = HDUtils.toBigInteger(privKeyBytes);
|
||||
assertNonZero(privateKeyFieldElt, "Generated master key is invalid.");
|
||||
assertLessThanN(privateKeyFieldElt, "Generated master key is invalid.");
|
||||
return new ExtendedHierarchicKey(ImmutableList.<ChildNumber>of(), chainCode, null, privateKeyFieldElt, null);
|
||||
}
|
||||
|
||||
public static ExtendedHierarchicKey createMasterPubKeyFromBytes(byte[] pubKeyBytes, byte[] chainCode) {
|
||||
return new ExtendedHierarchicKey(ImmutableList.<ChildNumber>of(), chainCode, HDUtils.getCurve().decodePoint(pubKeyBytes), null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param childNumber the "extended" child number, ie. with the 0x80000000 bit specifying private/public derivation.
|
||||
*/
|
||||
public static ExtendedHierarchicKey deriveChildKey(ExtendedHierarchicKey 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 ExtendedHierarchicKey deriveChildKey(ExtendedHierarchicKey parent, ChildNumber childNumber)
|
||||
throws HDDerivationException {
|
||||
|
||||
RawKeyBytes rawKey = deriveChildKeyBytes(parent, childNumber);
|
||||
return new ExtendedHierarchicKey(
|
||||
HDUtils.append(parent.getChildNumberPath(), childNumber),
|
||||
rawKey.chainCode,
|
||||
parent.hasPrivate() ? null : HDUtils.getCurve().decodePoint(rawKey.keyBytes),
|
||||
parent.hasPrivate() ? HDUtils.toBigInteger(rawKey.keyBytes) : null,
|
||||
parent);
|
||||
}
|
||||
|
||||
private static RawKeyBytes deriveChildKeyBytes(ExtendedHierarchicKey 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.hmacSha256(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 = HDUtils.toBigInteger(il);
|
||||
assertLessThanN(ilInt, "Illegal derived key: I_L >= n");
|
||||
byte[] keyBytes;
|
||||
if (parent.hasPrivate()) {
|
||||
BigInteger ki = parent.getPrivAsFieldElement().add(ilInt).mod(HDUtils.getEcParams().getN());
|
||||
assertNonZero(ki, "Illegal derived key: derived private key equals 0.");
|
||||
keyBytes = ki.toByteArray();
|
||||
} else {
|
||||
assertArgument(!childNumber.isPrivateDerivation(), "Can't use private derivation with public keys only.");
|
||||
ECPoint Ki = HDUtils.getEcParams().getG().multiply(ilInt).add(parent.getPubPoint());
|
||||
assertArgument(!Ki.equals(HDUtils.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) {
|
||||
assertArgument(!integer.equals(BigInteger.ZERO), errorMessage);
|
||||
}
|
||||
|
||||
private static void assertLessThanN(BigInteger integer, String errorMessage) {
|
||||
assertArgument(integer.compareTo(HDUtils.getEcParams().getN()) < 0, errorMessage);
|
||||
}
|
||||
|
||||
private static void assertArgument(boolean assertion, String errorMessage) {
|
||||
if (!assertion) {
|
||||
throw new HDDerivationException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static class RawKeyBytes {
|
||||
private final byte[] keyBytes, chainCode;
|
||||
|
||||
private RawKeyBytes(byte[] keyBytes, byte[] chainCode) {
|
||||
this.keyBytes = keyBytes;
|
||||
this.chainCode = chainCode;
|
||||
}
|
||||
}
|
||||
}
|
90
core/src/main/java/com/google/bitcoin/crypto/hd/HDUtils.java
Normal file
90
core/src/main/java/com/google/bitcoin/crypto/hd/HDUtils.java
Normal file
@ -0,0 +1,90 @@
|
||||
package com.google.bitcoin.crypto.hd;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.spongycastle.asn1.sec.SECNamedCurves;
|
||||
import org.spongycastle.asn1.x9.X9ECParameters;
|
||||
import org.spongycastle.crypto.digests.SHA512Digest;
|
||||
import org.spongycastle.crypto.macs.HMac;
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.math.ec.ECCurve;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
*
|
||||
* Static utilities used in BIP 32 Hierarchical Deterministic Wallets (HDW).
|
||||
*/
|
||||
public final class HDUtils {
|
||||
|
||||
private HDUtils() { }
|
||||
|
||||
private static final ECDomainParameters ecParams;
|
||||
|
||||
static {
|
||||
// All clients must agree on the curve to use by agreement. Bitcoin uses secp256k1.
|
||||
X9ECParameters params = SECNamedCurves.getByName("secp256k1");
|
||||
ecParams = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH());
|
||||
}
|
||||
|
||||
static HMac createHmacSha256Digest(byte[] key) {
|
||||
SHA512Digest digest = new SHA512Digest();
|
||||
HMac hMac = new HMac(digest);
|
||||
hMac.init(new KeyParameter(key));
|
||||
return hMac;
|
||||
}
|
||||
|
||||
static byte[] hmacSha256(HMac hmacSha256, byte[] input) {
|
||||
hmacSha256.reset();
|
||||
hmacSha256.update(input, 0, input.length);
|
||||
byte[] out = new byte[64];
|
||||
hmacSha256.doFinal(out, 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
public static byte[] hmacSha256(byte[] key, byte[] data) {
|
||||
return hmacSha256(createHmacSha256Digest(key), data);
|
||||
}
|
||||
|
||||
static BigInteger toBigInteger(byte[] bytes) {
|
||||
return new BigInteger(1, bytes);
|
||||
}
|
||||
|
||||
static ECPoint compressedCopy(ECPoint pubKPoint) {
|
||||
return getCurve().createPoint(pubKPoint.getX().toBigInteger(), pubKPoint.getY().toBigInteger(), true);
|
||||
}
|
||||
|
||||
static ECCurve getCurve() {
|
||||
return getEcParams().getCurve();
|
||||
}
|
||||
|
||||
static ECPoint toUncompressed(ECPoint pubKPoint) {
|
||||
return getCurve().createPoint(pubKPoint.getX().toBigInteger(), pubKPoint.getY().toBigInteger(), false);
|
||||
}
|
||||
|
||||
static byte[] toCompressed(byte[] uncompressedPoint) {
|
||||
return compressedCopy(getCurve().decodePoint(uncompressedPoint)).getEncoded();
|
||||
}
|
||||
|
||||
static byte[] longTo4ByteArray(long n) {
|
||||
byte[] bytes = Arrays.copyOfRange(ByteBuffer.allocate(8).putLong(n).array(), 4, 8);
|
||||
assert bytes.length == 4 : bytes.length;
|
||||
return bytes;
|
||||
}
|
||||
|
||||
static ECDomainParameters getEcParams() {
|
||||
return ecParams;
|
||||
}
|
||||
|
||||
static byte[] getBytes(ECPoint pubKPoint) {
|
||||
return compressedCopy(pubKPoint).getEncoded();
|
||||
}
|
||||
|
||||
static ImmutableList<ChildNumber> append(ImmutableList<ChildNumber> path, ChildNumber childNumber) {
|
||||
return ImmutableList.<ChildNumber>builder().addAll(path).add(childNumber).build();
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.google.bitcoin.crypto.hd.wallet;
|
||||
|
||||
import com.google.bitcoin.core.ECKey;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
*
|
||||
* The default WalletKeyGenerator implementation, creating random receiving keys and always returning the second
|
||||
* existing keychain key (or first if only one).
|
||||
*/
|
||||
public class DefaultKeyGenerator implements WalletKeyGenerator, Serializable {
|
||||
|
||||
@Override
|
||||
public ECKey nextReceivingKey() {
|
||||
return new ECKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECKey nextChangeKey(ArrayList<ECKey> keychain) {
|
||||
// For now let's just pick the second key in our keychain. In future we might want to do something else to
|
||||
// give the user better privacy here, eg in incognito mode.
|
||||
// The second key is chosen rather than the first because, by default, a wallet is created with a
|
||||
// single key. If the user imports say a blockchain.info backup they typically want change to go
|
||||
// to one of the imported keys
|
||||
checkState(keychain.size() > 0, "Can't send value without an address to use for receiving change");
|
||||
ECKey change = keychain.get(0);
|
||||
|
||||
if (keychain.size() > 1) {
|
||||
change = keychain.get(1);
|
||||
}
|
||||
return change;
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package com.google.bitcoin.crypto.hd.wallet;
|
||||
|
||||
import com.google.bitcoin.core.ECKey;
|
||||
import com.google.bitcoin.crypto.hd.ChildNumber;
|
||||
import com.google.bitcoin.crypto.hd.DeterministicHierarchy;
|
||||
import com.google.bitcoin.crypto.hd.ExtendedHierarchicKey;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
*
|
||||
* A WalletKeyGenerator, used by Wallet, that uses BIP32 for key generation: internal deterministic chain for change
|
||||
* keys and external chain for receiving keys.
|
||||
*/
|
||||
public class DeterministicKeyGenerator implements WalletKeyGenerator, Serializable {
|
||||
private static final Logger log = LoggerFactory.getLogger(DeterministicKeyGenerator.class);
|
||||
|
||||
public static final boolean PRIVATE_DERIVATION = true;
|
||||
|
||||
public static final ChildNumber EXTERNAL_CHAIN = new ChildNumber(0, PRIVATE_DERIVATION);
|
||||
public static final ChildNumber INTERNAL_CHAIN = new ChildNumber(1, PRIVATE_DERIVATION);
|
||||
|
||||
// private final NetworkParameters params;
|
||||
private final DeterministicHierarchy hierarchy;
|
||||
private final ImmutableList<ChildNumber> externalChainRootPath;
|
||||
private final ImmutableList<ChildNumber> internalChainRootPath;
|
||||
|
||||
public DeterministicKeyGenerator(ExtendedHierarchicKey rootKey) {
|
||||
log.debug("DeterministicKeyGenerator.DeterministicKeyGenerator");
|
||||
long start = System.currentTimeMillis();
|
||||
// this.params = params;
|
||||
hierarchy = new DeterministicHierarchy(rootKey);
|
||||
externalChainRootPath = hierarchy.deriveChild(rootKey.getChildNumberPath(), false, false, EXTERNAL_CHAIN).getChildNumberPath();
|
||||
internalChainRootPath = hierarchy.deriveChild(rootKey.getChildNumberPath(), false, false, INTERNAL_CHAIN).getChildNumberPath();
|
||||
log.debug("DKG constructor took {}", System.currentTimeMillis() - start);
|
||||
}
|
||||
|
||||
public ExtendedHierarchicKey nextExternal() {
|
||||
log.debug("DeterministicKeyGenerator.nextExternal");
|
||||
return hierarchy.deriveNextChild(externalChainRootPath, false, false, PRIVATE_DERIVATION);
|
||||
}
|
||||
|
||||
public ExtendedHierarchicKey nextInternal() {
|
||||
log.debug("DeterministicKeyGenerator.nextInternal");
|
||||
return hierarchy.deriveNextChild(internalChainRootPath, false, false, PRIVATE_DERIVATION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECKey nextReceivingKey() {
|
||||
return nextExternal().toECKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ECKey nextChangeKey(ArrayList<ECKey> keychain) {
|
||||
return nextInternal().toECKey();
|
||||
}
|
||||
|
||||
/*
|
||||
private Address nextExternalAddress() {
|
||||
return nextExternal().toECKey().toAddress(getParams());
|
||||
}
|
||||
|
||||
private Address nextInternalAddress() {
|
||||
return nextInternal().toECKey().toAddress(getParams());
|
||||
}
|
||||
|
||||
synchronized Address getChangeAddress() {
|
||||
return nextInternalAddress();
|
||||
}
|
||||
|
||||
public ExtendedHierarchicKey getRootKey() {
|
||||
return hierarchy.getRootKey();
|
||||
}
|
||||
|
||||
public NetworkParameters getParams() {
|
||||
return params;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
package com.google.bitcoin.crypto.hd.wallet;
|
||||
|
||||
import com.google.bitcoin.core.ECKey;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
*
|
||||
* Used by {@link com.google.bitcoin.core.Wallet} to generate receiving and change keys.
|
||||
*/
|
||||
public interface WalletKeyGenerator {
|
||||
ECKey nextReceivingKey();
|
||||
|
||||
ECKey nextChangeKey(ArrayList<ECKey> keychain);
|
||||
}
|
166
core/src/test/java/com/google/bitcoin/crypto/hd/BIP32Test.java
Normal file
166
core/src/test/java/com/google/bitcoin/crypto/hd/BIP32Test.java
Normal file
@ -0,0 +1,166 @@
|
||||
package com.google.bitcoin.crypto.hd;
|
||||
|
||||
import com.google.bitcoin.core.AddressFormatException;
|
||||
import com.google.bitcoin.core.Base58;
|
||||
import com.google.common.base.Functions;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.Iterables;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
* @created 6/19/13 2:25 PM
|
||||
*
|
||||
* A test with test vectors as per BIP 32 spec: https://en.bitcoin.it/wiki/BIP_0032#Test_Vectors
|
||||
*/
|
||||
public class BIP32Test {
|
||||
private static final Logger log = LoggerFactory.getLogger(BIP32Test.class);
|
||||
|
||||
HDWTestVector[] tvs = new HDWTestVector[]{
|
||||
new HDWTestVector(
|
||||
"000102030405060708090a0b0c0d0e0f",
|
||||
"xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
|
||||
"xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
|
||||
Arrays.asList(
|
||||
new HDWTestVector.DerivedTestCase(
|
||||
"Test1 m/0'",
|
||||
new ChildNumber[]{new ChildNumber(0, true)},
|
||||
"xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
|
||||
"xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"
|
||||
),
|
||||
new HDWTestVector.DerivedTestCase(
|
||||
"Test1 m/0'/1",
|
||||
new ChildNumber[]{new ChildNumber(0, true), new ChildNumber(1, false)},
|
||||
"xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
|
||||
"xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"
|
||||
),
|
||||
new HDWTestVector.DerivedTestCase(
|
||||
"Test1 m/0'/1/2'",
|
||||
new ChildNumber[]{new ChildNumber(0, true), new ChildNumber(1, false), new ChildNumber(2, true)},
|
||||
"xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
|
||||
"xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5"
|
||||
),
|
||||
new HDWTestVector.DerivedTestCase(
|
||||
"Test1 m/0'/1/2'/2",
|
||||
new ChildNumber[]{new ChildNumber(0, true), new ChildNumber(1, false), new ChildNumber(2, true), new ChildNumber(2, false)},
|
||||
"xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
|
||||
"xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV"
|
||||
),
|
||||
new HDWTestVector.DerivedTestCase(
|
||||
"Test1 m/0'/1/2'/2/1000000000",
|
||||
new ChildNumber[]{new ChildNumber(0, true), new ChildNumber(1, false), new ChildNumber(2, true), new ChildNumber(2, false), new ChildNumber(1000000000, false)},
|
||||
"xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76",
|
||||
"xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"
|
||||
)
|
||||
)
|
||||
),
|
||||
new HDWTestVector(
|
||||
"fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
|
||||
"xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
|
||||
"xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB",
|
||||
Arrays.asList(
|
||||
new HDWTestVector.DerivedTestCase(
|
||||
"Test2 m/0",
|
||||
new ChildNumber[]{new ChildNumber(0, false)},
|
||||
"xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
|
||||
"xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"
|
||||
),
|
||||
new HDWTestVector.DerivedTestCase(
|
||||
"Test2 m/0/2147483647'",
|
||||
new ChildNumber[]{new ChildNumber(0, false), new ChildNumber(2147483647, true)},
|
||||
"xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
|
||||
"xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a"
|
||||
),
|
||||
new HDWTestVector.DerivedTestCase(
|
||||
"Test2 m/0/2147483647'/1",
|
||||
new ChildNumber[]{new ChildNumber(0, false), new ChildNumber(2147483647, true), new ChildNumber(1, false)},
|
||||
"xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
|
||||
"xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon"
|
||||
),
|
||||
new HDWTestVector.DerivedTestCase(
|
||||
"Test2 m/0/2147483647'/1/2147483646'",
|
||||
new ChildNumber[]{new ChildNumber(0, false), new ChildNumber(2147483647, true), new ChildNumber(1, false), new ChildNumber(2147483646, true)},
|
||||
"xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
|
||||
"xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"
|
||||
),
|
||||
new HDWTestVector.DerivedTestCase(
|
||||
"Test2 m/0/2147483647'/1/2147483646'/2",
|
||||
new ChildNumber[]{new ChildNumber(0, false), new ChildNumber(2147483647, true), new ChildNumber(1, false), new ChildNumber(2147483646, true), new ChildNumber(2, false)},
|
||||
"xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j",
|
||||
"xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"
|
||||
)
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
@Test
|
||||
public void testVector1() throws Exception {
|
||||
testVector(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVector2() throws Exception {
|
||||
testVector(1);
|
||||
}
|
||||
|
||||
private void testVector(int testCase) throws AddressFormatException {
|
||||
log.info("======= Test vector {}", testCase);
|
||||
HDWTestVector tv = tvs[testCase];
|
||||
ExtendedHierarchicKey masterPrivateKey = HDKeyDerivation.createMasterPrivateKey(Hex.decode(tv.seed));
|
||||
Assert.assertEquals(testEncode(tv.priv), testEncode(masterPrivateKey.serializePrivB58()));
|
||||
Assert.assertEquals(testEncode(tv.pub), testEncode(masterPrivateKey.serializePubB58()));
|
||||
DeterministicHierarchy dh = new DeterministicHierarchy(masterPrivateKey);
|
||||
for (int i = 0; i < tv.derived.size(); i++) {
|
||||
HDWTestVector.DerivedTestCase tc = tv.derived.get(i);
|
||||
log.info("{}", tc.name);
|
||||
Assert.assertEquals(tc.name, String.format("Test%d %s", testCase + 1, tc.getPathDescription()));
|
||||
int depth = tc.path.length - 1;
|
||||
ExtendedHierarchicKey ehkey = dh.deriveChild(Arrays.asList(tc.path).subList(0, depth), false, true, tc.path[depth]);
|
||||
Assert.assertEquals(testEncode(tc.priv), testEncode(ehkey.serializePrivB58()));
|
||||
Assert.assertEquals(testEncode(tc.pub), testEncode(ehkey.serializePubB58()));
|
||||
}
|
||||
}
|
||||
|
||||
private String testEncode(String what) throws AddressFormatException {
|
||||
return new String(Hex.encode(Base58.decodeChecked(what)));
|
||||
}
|
||||
|
||||
static class HDWTestVector {
|
||||
final String seed;
|
||||
final String priv;
|
||||
final String pub;
|
||||
final List<DerivedTestCase> derived;
|
||||
|
||||
HDWTestVector(String seed, String priv, String pub, List<DerivedTestCase> derived) {
|
||||
this.seed = seed;
|
||||
this.priv = priv;
|
||||
this.pub = pub;
|
||||
this.derived = derived;
|
||||
}
|
||||
|
||||
static class DerivedTestCase {
|
||||
final String name;
|
||||
final ChildNumber[] path;
|
||||
final String pub;
|
||||
final String priv;
|
||||
|
||||
DerivedTestCase(String name, ChildNumber[] path, String priv, String pub) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.pub = pub;
|
||||
this.priv = priv;
|
||||
}
|
||||
|
||||
String getPathDescription() {
|
||||
return "m/" + Joiner.on("/").join(Iterables.transform(Arrays.asList(path), Functions.toStringFunction()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,195 @@
|
||||
package com.google.bitcoin.crypto.hd;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
*
|
||||
* This test is adapted from Armory's BIP 32 tests.
|
||||
*/
|
||||
public class ChildKeyDerivationTest {
|
||||
private static final Logger log = LoggerFactory.getLogger(ChildKeyDerivationTest.class);
|
||||
|
||||
private static final int HDW_CHAIN_EXTERNAL = 0;
|
||||
private static final int HDW_CHAIN_INTERNAL = 1;
|
||||
|
||||
@Test
|
||||
public void testChildKeyDerivation() throws Exception {
|
||||
String ckdTestVectors[] = {
|
||||
// test case 1:
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"04" + "6a04ab98d9e4774ad806e302dddeb63b" +
|
||||
"ea16b5cb5f223ee77478e861bb583eb3" +
|
||||
"36b6fbcb60b5b3d4f1551ac45e5ffc49" +
|
||||
"36466e7d98f6c7c0ec736539f74691a6",
|
||||
"dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
|
||||
// test case 2:
|
||||
"be05d9ded0a73f81b814c93792f753b35c575fe446760005d44e0be13ba8935a",
|
||||
"02" + "b530da16bbff1428c33020e87fc9e699" +
|
||||
"cc9c753a63b8678ce647b7457397acef",
|
||||
"7012bc411228495f25d666d55fdce3f10a93908b5f9b9b7baa6e7573603a7bda"
|
||||
};
|
||||
|
||||
for(int i = 0; i < 1; i++) {
|
||||
byte[] priv = Hex.decode(ckdTestVectors[3 * i]);
|
||||
byte[] pub = Hex.decode(ckdTestVectors[3 * i + 1]);
|
||||
byte[] chain = Hex.decode(ckdTestVectors[3 * i + 2]); // chain code
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Start with an extended PRIVATE key
|
||||
ExtendedHierarchicKey ekprv = HDKeyDerivation.createMasterPrivKeyFromBytes(priv, chain);
|
||||
|
||||
// Create two accounts
|
||||
ExtendedHierarchicKey ekprv_0 = HDKeyDerivation.deriveChildKey(ekprv, 0);
|
||||
ExtendedHierarchicKey ekprv_1 = HDKeyDerivation.deriveChildKey(ekprv, 1);
|
||||
|
||||
// Create internal and external chain on Account 0
|
||||
ExtendedHierarchicKey ekprv_0_EX = HDKeyDerivation.deriveChildKey(ekprv_0, HDW_CHAIN_EXTERNAL);
|
||||
ExtendedHierarchicKey ekprv_0_IN = HDKeyDerivation.deriveChildKey(ekprv_0, HDW_CHAIN_INTERNAL);
|
||||
|
||||
// Create three addresses on external chain
|
||||
ExtendedHierarchicKey ekprv_0_EX_0 = HDKeyDerivation.deriveChildKey(ekprv_0_EX, 0);
|
||||
ExtendedHierarchicKey ekprv_0_EX_1 = HDKeyDerivation.deriveChildKey(ekprv_0_EX, 1);
|
||||
ExtendedHierarchicKey ekprv_0_EX_2 = HDKeyDerivation.deriveChildKey(ekprv_0_EX, 2);
|
||||
|
||||
// Create three addresses on internal chain
|
||||
ExtendedHierarchicKey ekprv_0_IN_0 = HDKeyDerivation.deriveChildKey(ekprv_0_IN, 0);
|
||||
ExtendedHierarchicKey ekprv_0_IN_1 = HDKeyDerivation.deriveChildKey(ekprv_0_IN, 1);
|
||||
ExtendedHierarchicKey ekprv_0_IN_2 = HDKeyDerivation.deriveChildKey(ekprv_0_IN, 2);
|
||||
|
||||
// Now add a few more addresses with very large indices
|
||||
ExtendedHierarchicKey ekprv_1_IN = HDKeyDerivation.deriveChildKey(ekprv_1, HDW_CHAIN_INTERNAL);
|
||||
ExtendedHierarchicKey ekprv_1_IN_4095 = HDKeyDerivation.deriveChildKey(ekprv_1_IN, 4095);
|
||||
// ExtendedHierarchicKey ekprv_1_IN_4bil = HDKeyDerivation.deriveChildKey(ekprv_1_IN, 4294967295L);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Repeat the above with PUBLIC key
|
||||
ExtendedHierarchicKey ekpub = HDKeyDerivation.createMasterPubKeyFromBytes(HDUtils.toCompressed(pub), chain);
|
||||
|
||||
// Create two accounts
|
||||
ExtendedHierarchicKey ekpub_0 = HDKeyDerivation.deriveChildKey(ekpub, 0);
|
||||
ExtendedHierarchicKey ekpub_1 = HDKeyDerivation.deriveChildKey(ekpub, 1);
|
||||
|
||||
// Create internal and external chain on Account 0
|
||||
ExtendedHierarchicKey ekpub_0_EX = HDKeyDerivation.deriveChildKey(ekpub_0, HDW_CHAIN_EXTERNAL);
|
||||
ExtendedHierarchicKey ekpub_0_IN = HDKeyDerivation.deriveChildKey(ekpub_0, HDW_CHAIN_INTERNAL);
|
||||
|
||||
// Create three addresses on external chain
|
||||
ExtendedHierarchicKey ekpub_0_EX_0 = HDKeyDerivation.deriveChildKey(ekpub_0_EX, 0);
|
||||
ExtendedHierarchicKey ekpub_0_EX_1 = HDKeyDerivation.deriveChildKey(ekpub_0_EX, 1);
|
||||
ExtendedHierarchicKey ekpub_0_EX_2 = HDKeyDerivation.deriveChildKey(ekpub_0_EX, 2);
|
||||
|
||||
// Create three addresses on internal chain
|
||||
ExtendedHierarchicKey ekpub_0_IN_0 = HDKeyDerivation.deriveChildKey(ekpub_0_IN, 0);
|
||||
ExtendedHierarchicKey ekpub_0_IN_1 = HDKeyDerivation.deriveChildKey(ekpub_0_IN, 1);
|
||||
ExtendedHierarchicKey ekpub_0_IN_2 = HDKeyDerivation.deriveChildKey(ekpub_0_IN, 2);
|
||||
|
||||
// Now add a few more addresses with very large indices
|
||||
ExtendedHierarchicKey ekpub_1_IN = HDKeyDerivation.deriveChildKey(ekpub_1, HDW_CHAIN_INTERNAL);
|
||||
ExtendedHierarchicKey ekpub_1_IN_4095 = HDKeyDerivation.deriveChildKey(ekpub_1_IN, 4095);
|
||||
// ExtendedHierarchicKey ekpub_1_IN_4bil = HDKeyDerivation.deriveChildKey(ekpub_1_IN, 4294967295L);
|
||||
|
||||
checkKeyMatch(ekprv, ekpub);
|
||||
checkKeyMatch(ekprv_0, ekpub_0);
|
||||
checkKeyMatch(ekprv_1, ekpub_1);
|
||||
checkKeyMatch(ekprv_0_IN, ekpub_0_IN);
|
||||
checkKeyMatch(ekprv_0_IN_0, ekpub_0_IN_0);
|
||||
checkKeyMatch(ekprv_0_IN_1, ekpub_0_IN_1);
|
||||
checkKeyMatch(ekprv_0_IN_2, ekpub_0_IN_2);
|
||||
checkKeyMatch(ekprv_0_EX_0, ekpub_0_EX_0);
|
||||
checkKeyMatch(ekprv_0_EX_1, ekpub_0_EX_1);
|
||||
checkKeyMatch(ekprv_0_EX_2, ekpub_0_EX_2);
|
||||
checkKeyMatch(ekprv_1_IN, ekpub_1_IN);
|
||||
checkKeyMatch(ekprv_1_IN_4095, ekpub_1_IN_4095);
|
||||
// checkKeyMatch(ekprv_1_IN_4bil, ekpub_1_IN_4bil);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkKeyMatch(ExtendedHierarchicKey ekprv, ExtendedHierarchicKey ekpub) {
|
||||
String fromPriv = hexEncodePub(ekprv.getPubOnly());
|
||||
String fromParentPublic = hexEncodePub(ekpub);
|
||||
log.info((fromPriv.equals(fromParentPublic) ? "OK: " : "***FAILED*** : ") + fromPriv + ", " + fromParentPublic);
|
||||
}
|
||||
|
||||
/*
|
||||
* Commented out; these are old sipa's test vectors and the BIP32 derivation algorithm has been changed since.
|
||||
*/
|
||||
/*
|
||||
@Test
|
||||
public void testChildKeyDerivationSecond() throws Exception {
|
||||
|
||||
// Using the test vectors created by sipa, starting with only a 16-byte seed
|
||||
byte[] seed = Hex.decode("ff000000000000000000000000000000");
|
||||
|
||||
// We only need to compare public keys at each step, because any errors in
|
||||
// those would cascade and cause everything else to be incorrect.
|
||||
// Remember BIP 32 says all steps must use compressed public keys.
|
||||
byte[] sipaPubKeyAnswers[] = {
|
||||
Hex.decode("02b530da16bbff1428c33020e87fc9e699cc9c753a63b8678ce647b7457397acef"),
|
||||
Hex.decode("032ad2472db0e9b1706c816a93dc55c72ef2ff339818718b676a563e063afa3f38"),
|
||||
Hex.decode("02655643c6fba3edf1139d59261590e5b358cbf19a063c88448f01558dd4fbf2c7"),
|
||||
Hex.decode("02a3b9ce007bbcfa0b9ec81779d07413256e72e516d14468a2e21172663376c233"),
|
||||
Hex.decode("03737b8811cda598ed635621997305e7a84e41c99990b69b88dfb021e74625247a"),
|
||||
Hex.decode("03bc1a550813b185e61d82a0823636e539dd86adbf591e9d3f91e32a579506c050"),
|
||||
Hex.decode("03f8d3b8825607daaca137909d9a74c8bb5d667bb06b9be519c2b171f47e6322d2"),
|
||||
Hex.decode("0322dd499c356165d7cbc6072be77354041b7ff6c7c256130189f829968275cfdc"),
|
||||
Hex.decode("0288e3cd9838a0d4c09d469befa2ff7fa0bbd2829a24bd8e1b3d3e6a64fe0f5380"),
|
||||
Hex.decode("0262001d6694e0c02a3fdc95e1e0ca3a2687233bd15145415cc6a4daf4b57c595e"),
|
||||
Hex.decode("02ea90cfbbeacc9bcd695449a4406fd886b3757774160e3de0d7fb7d9297ff5f1a"),
|
||||
Hex.decode("026199b74e5d514b0f520c40fc9f3eb9cba965bfa64aa70c435e9db6c7bb5ecd21"),
|
||||
Hex.decode("02415b0da16af9b210ba5c998b9d07553c33e1c570a34d174728c2211f5a894bf9"),
|
||||
Hex.decode("0330cb9a20e013cf203a79fc2c15c588019bb1016193f82ddefd28c05552b36503"),
|
||||
Hex.decode("02d8a3f07e15e34f6a14187907f49e5634080b7de946eb1f27f7979abf2fd54e72"),
|
||||
Hex.decode("032af575cb4fa722febb29ea3a7d8c9efd9dc410fcf86153c91b57c484c1d20313"),
|
||||
Hex.decode("031d55c55998f29a15fe38b0d466347df7519092ef8bc48193395167ff28cf99af")
|
||||
};
|
||||
|
||||
// Master Key comes from HMAC_SHA512 using "Bitcoin seed" as the "key"
|
||||
ExtendedHierarchicKey computedPrivEK = HDKeyDerivation.createMasterPrivateKey(seed);
|
||||
ExtendedHierarchicKey computedPubEK = computedPrivEK.getPubOnly();
|
||||
|
||||
log.info("********************************************************************************");
|
||||
log.info("Testing key chaining");
|
||||
for (int i = 0; i <= 16; i++) {
|
||||
log.info("*** {} ****************************************************************************", i);
|
||||
ExtendedHierarchicKey pubKey = computedPrivEK.getPubOnly();
|
||||
int childNumber = (1 << i) - 1;
|
||||
debug(computedPrivEK, computedPubEK, childNumber);
|
||||
Assert.assertArrayEquals(pubKey.getPubKeyBytes(), sipaPubKeyAnswers[i]);
|
||||
Assert.assertArrayEquals(computedPubEK.getPubKeyBytes(), sipaPubKeyAnswers[i]);
|
||||
log.info("OK.", childNumber);
|
||||
computedPrivEK = HDKeyDerivation.deriveChildKey(computedPrivEK, childNumber);
|
||||
computedPubEK = HDKeyDerivation.deriveChildKey(computedPubEK, childNumber);
|
||||
}
|
||||
}
|
||||
|
||||
private void debug(ExtendedHierarchicKey computedPrivEK, ExtendedHierarchicKey computedPubEK, long childNumber) {
|
||||
log.info("Index: {}", childNumber);
|
||||
log.info("Pr path: {}", computedPrivEK.getPath());
|
||||
log.info("Pr Key: {}", hexEncode(computedPrivEK.getPrivKeyBytes()));
|
||||
log.info("Pr Identifier: {}", hexEncode(computedPrivEK.getIdentifier()));
|
||||
log.info("Pr Chain Code: {}", hexEncode(computedPrivEK.getChainCode()));
|
||||
log.info("Pb Key: {}", hexEncode(computedPubEK.getPubKeyBytes()));
|
||||
log.info("Pb path: {}", computedPubEK.getPath());
|
||||
log.info("Pb Identifier: {}", hexEncode(computedPubEK.getIdentifier()));
|
||||
log.info("Pb Fingerprint: {}", hexEncode(computedPubEK.getFingerprint()));
|
||||
log.info("Pb Chain Code: {}", hexEncode(computedPubEK.getChainCode()));
|
||||
}
|
||||
|
||||
private String hexEncodePriv(ExtendedHierarchicKey privKey) {
|
||||
return hexEncode(privKey.getPrivKeyBytes());
|
||||
}
|
||||
*/
|
||||
|
||||
private String hexEncodePub(ExtendedHierarchicKey pubKey) {
|
||||
return hexEncode(pubKey.getPubKeyBytes());
|
||||
}
|
||||
|
||||
private String hexEncode(byte[] bytes) {
|
||||
return new String(Hex.encode(bytes));
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.google.bitcoin.crypto.hd;
|
||||
|
||||
import com.google.bitcoin.crypto.hd.wallet.DeterministicKeyGenerator;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
*/
|
||||
public class DeterministicHierarchyTest {
|
||||
|
||||
/**
|
||||
* Test creating a sequence of derived keys using the internal and external chain.
|
||||
*/
|
||||
@Test
|
||||
public void testHierarchy() throws Exception {
|
||||
ExtendedHierarchicKey m = HDKeyDerivation.createMasterPrivateKey(new SecureRandom().generateSeed(32));
|
||||
|
||||
for (int iWallet = 0; iWallet < 3; iWallet++) {
|
||||
ExtendedHierarchicKey walletRootKey = HDKeyDerivation.deriveChildKey(m, iWallet);
|
||||
DeterministicKeyGenerator hdWalletKeyGen = new DeterministicKeyGenerator(walletRootKey);
|
||||
Assert.assertEquals(walletRootKey.getChildNumber().getChildNumber(), iWallet);
|
||||
|
||||
Assert.assertEquals(0L, hdWalletKeyGen.nextInternal().getChildNumber().getChildNumber());
|
||||
Assert.assertEquals(1L, hdWalletKeyGen.nextInternal().getChildNumber().getChildNumber());
|
||||
Assert.assertEquals(2L, hdWalletKeyGen.nextInternal().getChildNumber().getChildNumber());
|
||||
|
||||
Assert.assertEquals(0L, hdWalletKeyGen.nextExternal().getChildNumber().getChildNumber());
|
||||
Assert.assertEquals(1L, hdWalletKeyGen.nextExternal().getChildNumber().getChildNumber());
|
||||
Assert.assertEquals(2L, hdWalletKeyGen.nextExternal().getChildNumber().getChildNumber());
|
||||
|
||||
Assert.assertEquals(3L, hdWalletKeyGen.nextInternal().getChildNumber().getChildNumber());
|
||||
|
||||
Assert.assertEquals(3L, hdWalletKeyGen.nextExternal().getChildNumber().getChildNumber());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package com.google.bitcoin.crypto.hd;
|
||||
|
||||
import junit.framework.Assert;
|
||||
import org.junit.Test;
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.math.ec.ECCurve;
|
||||
import org.spongycastle.math.ec.ECFieldElement;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
* This is just to see if I know how to use the EC machinery.
|
||||
*/
|
||||
public class ECOperationTest {
|
||||
|
||||
private static final ECDomainParameters EC_PARAMS = HDUtils.getEcParams();
|
||||
private static final ECCurve.Fp CURVE = (ECCurve.Fp) EC_PARAMS.getCurve();
|
||||
private static final BigInteger N = EC_PARAMS.getN();
|
||||
private static final Random RND = new Random();
|
||||
|
||||
@Test
|
||||
public void testFieldElementToBigIntConversions() throws Exception {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
BigInteger bi = rnd();
|
||||
Assert.assertEquals("Case " + i, bi, CURVE.fromBigInteger(bi).toBigInteger());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a * b = b * a, where a, b are scalars.
|
||||
*/
|
||||
@Test
|
||||
public void testCommutativity() throws Exception {
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
BigInteger biA = rnd();
|
||||
BigInteger biB = rnd();
|
||||
ECFieldElement scA = CURVE.fromBigInteger(biA);
|
||||
ECFieldElement scB = CURVE.fromBigInteger(biB);
|
||||
Assert.assertEquals("Scalar Commutativity " + i, scB.multiply(scA).toBigInteger(), scA.multiply(scB).toBigInteger());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that (ab)P = a*(b*P), where a, b are scalars and P is an EC point.
|
||||
*/
|
||||
@Test
|
||||
public void testAssociativity() throws Exception {
|
||||
for (int i = 0; i < 30; i++) {
|
||||
BigInteger biA = rnd();
|
||||
BigInteger biB = rnd();
|
||||
ECPoint point = getRandomPoint();
|
||||
Assert.assertEquals("Associativity " + i, point.multiply(biB).multiply(biA), point.multiply(biA.multiply(biB).mod(N)));
|
||||
}
|
||||
}
|
||||
|
||||
private ECPoint getRandomPoint() {
|
||||
return EC_PARAMS.getG().multiply(rnd());
|
||||
}
|
||||
|
||||
private BigInteger rnd() {
|
||||
BigInteger r;
|
||||
do r = new BigInteger(N.bitLength(), RND); while (r.compareTo(N) >= 0);
|
||||
return r;
|
||||
}
|
||||
}
|
169
core/src/test/java/com/google/bitcoin/crypto/hd/HDUtilsTest.java
Normal file
169
core/src/test/java/com/google/bitcoin/crypto/hd/HDUtilsTest.java
Normal file
@ -0,0 +1,169 @@
|
||||
package com.google.bitcoin.crypto.hd;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.crypto.params.ECDomainParameters;
|
||||
import org.spongycastle.math.ec.ECCurve;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Matija Mazi <br/>
|
||||
*/
|
||||
public class HDUtilsTest {
|
||||
private static final Logger log = LoggerFactory.getLogger(HDUtilsTest.class);
|
||||
|
||||
@Test
|
||||
public void testHmac() throws Exception {
|
||||
String tv[] = {
|
||||
"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b" +
|
||||
"0b0b0b0b",
|
||||
"4869205468657265",
|
||||
"87aa7cdea5ef619d4ff0b4241a1d6cb0" +
|
||||
"2379f4e2ce4ec2787ad0b30545e17cde" +
|
||||
"daa833b7d6b8a702038b274eaea3f4e4" +
|
||||
"be9d914eeb61f1702e696c203a126854",
|
||||
|
||||
"4a656665",
|
||||
"7768617420646f2079612077616e7420" +
|
||||
"666f72206e6f7468696e673f",
|
||||
"164b7a7bfcf819e2e395fbe73b56e0a3" +
|
||||
"87bd64222e831fd610270cd7ea250554" +
|
||||
"9758bf75c05a994a6d034f65f8f0e6fd" +
|
||||
"caeab1a34d4a6b4b636e070a38bce737",
|
||||
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaa",
|
||||
"dddddddddddddddddddddddddddddddd" +
|
||||
"dddddddddddddddddddddddddddddddd" +
|
||||
"dddddddddddddddddddddddddddddddd" +
|
||||
"dddd",
|
||||
"fa73b0089d56a284efb0f0756c890be9" +
|
||||
"b1b5dbdd8ee81a3655f83e33b2279d39" +
|
||||
"bf3e848279a722c806b485a47e67c807" +
|
||||
"b946a337bee8942674278859e13292fb",
|
||||
|
||||
"0102030405060708090a0b0c0d0e0f10" +
|
||||
"111213141516171819",
|
||||
"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" +
|
||||
"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" +
|
||||
"cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd" +
|
||||
"cdcd",
|
||||
"b0ba465637458c6990e5a8c5f61d4af7" +
|
||||
"e576d97ff94b872de76f8050361ee3db" +
|
||||
"a91ca5c11aa25eb4d679275cc5788063" +
|
||||
"a5f19741120c4f2de2adebeb10a298dd",
|
||||
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaa",
|
||||
"54657374205573696e67204c61726765" +
|
||||
"72205468616e20426c6f636b2d53697a" +
|
||||
"65204b6579202d2048617368204b6579" +
|
||||
"204669727374",
|
||||
"80b24263c7c1a3ebb71493c1dd7be8b4" +
|
||||
"9b46d1f41b4aeec1121b013783f8f352" +
|
||||
"6b56d037e05f2598bd0fd2215d6a1e52" +
|
||||
"95e64f73f63f0aec8b915a985d786598",
|
||||
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
|
||||
"aaaaaa",
|
||||
"54686973206973206120746573742075" +
|
||||
"73696e672061206c6172676572207468" +
|
||||
"616e20626c6f636b2d73697a65206b65" +
|
||||
"7920616e642061206c61726765722074" +
|
||||
"68616e20626c6f636b2d73697a652064" +
|
||||
"6174612e20546865206b6579206e6565" +
|
||||
"647320746f2062652068617368656420" +
|
||||
"6265666f7265206265696e6720757365" +
|
||||
"642062792074686520484d414320616c" +
|
||||
"676f726974686d2e",
|
||||
"e37b6a775dc87dbaa4dfa9f96e5e3ffd" +
|
||||
"debd71f8867289865df5a32d20cdc944" +
|
||||
"b6022cac3c4982b10d5eeb55c3e4de15" +
|
||||
"134676fb6de0446065c97440fa8c6a58"
|
||||
};
|
||||
|
||||
for (int i = 0; i < tv.length; i += 3) {
|
||||
Assert.assertArrayEquals("Case " + i, getBytes(tv, i + 2), HDUtils.hmacSha256(getBytes(tv, i), getBytes(tv, i + 1)));
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getBytes(String[] hmacTestVectors, int i) {
|
||||
return Hex.decode(hmacTestVectors[i]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPointCompression() {
|
||||
List<String> testPubKey = Arrays.asList(
|
||||
"044f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa385b6b1b8ead809ca67454d9683fcf2ba03456d6fe2c4abe2b07f0fbdbb2f1c1",
|
||||
"04ed83704c95d829046f1ac27806211132102c34e9ac7ffa1b71110658e5b9d1bdedc416f5cefc1db0625cd0c75de8192d2b592d7e3b00bcfb4a0e860d880fd1fc",
|
||||
"042596957532fc37e40486b910802ff45eeaa924548c0e1c080ef804e523ec3ed3ed0a9004acf927666eee18b7f5e8ad72ff100a3bb710a577256fd7ec81eb1cb3");
|
||||
|
||||
ECDomainParameters ecp = HDUtils.getEcParams();
|
||||
ECCurve curve = ecp.getCurve();
|
||||
|
||||
for (String testpkStr : testPubKey) {
|
||||
byte[] testpk = Hex.decode(testpkStr);
|
||||
|
||||
BigInteger pubX = HDUtils.toBigInteger(Arrays.copyOfRange(testpk, 1, 33));
|
||||
BigInteger pubY = HDUtils.toBigInteger(Arrays.copyOfRange(testpk, 33, 65));
|
||||
|
||||
ECPoint ptFlat = curve.createPoint(pubX, pubY, false); // 65
|
||||
ECPoint ptComp = curve.createPoint(pubX, pubY, true); // 33
|
||||
ECPoint uncompressed = HDUtils.toUncompressed(ptComp);
|
||||
ECPoint recompressed = HDUtils.compressedCopy(uncompressed);
|
||||
ECPoint orig = curve.decodePoint(testpk);
|
||||
|
||||
log.info("====================");
|
||||
log.info("Flat: {}", asHexStr(ptFlat));
|
||||
log.info("Compressed: {}", asHexStr(ptComp));
|
||||
log.info("Uncompressed: {}", asHexStr(uncompressed));
|
||||
log.info("Recompressed: {}", asHexStr(recompressed));
|
||||
log.info("Original (uncomp): {}", asHexStr(orig));
|
||||
|
||||
// assert point equality:
|
||||
Assert.assertEquals(ptFlat, uncompressed);
|
||||
Assert.assertEquals(ptFlat, ptComp);
|
||||
Assert.assertEquals(ptComp, recompressed);
|
||||
Assert.assertEquals(ptComp, orig);
|
||||
|
||||
// assert bytes equality:
|
||||
Assert.assertArrayEquals(ptFlat.getEncoded(), uncompressed.getEncoded());
|
||||
Assert.assertArrayEquals(ptComp.getEncoded(), recompressed.getEncoded());
|
||||
Assert.assertArrayEquals(ptFlat.getEncoded(), orig.getEncoded());
|
||||
Assert.assertFalse(Arrays.equals(ptFlat.getEncoded(), ptComp.getEncoded()));
|
||||
|
||||
// todo: assert header byte
|
||||
}
|
||||
}
|
||||
|
||||
private String asHexStr(ECPoint ptFlat) {
|
||||
return new String(Hex.encode(ptFlat.getEncoded()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongToByteArray() throws Exception {
|
||||
byte[] bytes = HDUtils.longTo4ByteArray(1026);
|
||||
Assert.assertEquals("00000402", new String(Hex.encode(bytes)));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user