/
DeterministicKey.java
643 lines (578 loc) · 28.1 KB
/
DeterministicKey.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
/*
* Copyright 2013 Matija Mazi.
* Copyright 2014 Andreas Schildbach
*
* 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.crypto;
import org.bitcoinj.core.*;
import org.bitcoinj.script.Script;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.math.ec.ECPoint;
import javax.annotation.Nullable;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Comparator;
import static org.bitcoinj.core.Utils.HEX;
import static com.google.common.base.Preconditions.*;
/**
* A deterministic key is a node in a {@link DeterministicHierarchy}. As per
* <a href="https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki">the BIP 32 specification</a> it is a pair
* (key, chaincode). If you know its path in the tree and its chain code you can derive more keys from this. To obtain
* one of these, you can call {@link HDKeyDerivation#createMasterPrivateKey(byte[])}.
*/
public class DeterministicKey extends ECKey {
/** Sorts deterministic keys in the order of their child number. That's <i>usually</i> the order used to derive them. */
public static final Comparator<ECKey> CHILDNUM_ORDER = new Comparator<ECKey>() {
@Override
public int compare(ECKey k1, ECKey k2) {
ChildNumber cn1 = ((DeterministicKey) k1).getChildNumber();
ChildNumber cn2 = ((DeterministicKey) k2).getChildNumber();
return cn1.compareTo(cn2);
}
};
private final DeterministicKey parent;
private final ImmutableList<ChildNumber> childNumberPath;
private final int depth;
private int parentFingerprint; // 0 if this key is root node of key hierarchy
/** 32 bytes */
private final byte[] chainCode;
/** Constructs a key from its components. This is not normally something you should use. */
public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
byte[] chainCode,
LazyECPoint publicAsPoint,
@Nullable BigInteger priv,
@Nullable DeterministicKey parent) {
super(priv, compressPoint(checkNotNull(publicAsPoint)));
checkArgument(chainCode.length == 32);
this.parent = parent;
this.childNumberPath = checkNotNull(childNumberPath);
this.chainCode = Arrays.copyOf(chainCode, chainCode.length);
this.depth = parent == null ? 0 : parent.depth + 1;
this.parentFingerprint = (parent != null) ? parent.getFingerprint() : 0;
}
public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
byte[] chainCode,
ECPoint publicAsPoint,
@Nullable BigInteger priv,
@Nullable DeterministicKey parent) {
this(childNumberPath, chainCode, new LazyECPoint(publicAsPoint), priv, parent);
}
/** Constructs a key from its components. This is not normally something you should use. */
public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
byte[] chainCode,
BigInteger priv,
@Nullable DeterministicKey parent) {
super(priv, compressPoint(ECKey.publicPointFromPrivate(priv)));
checkArgument(chainCode.length == 32);
this.parent = parent;
this.childNumberPath = checkNotNull(childNumberPath);
this.chainCode = Arrays.copyOf(chainCode, chainCode.length);
this.depth = parent == null ? 0 : parent.depth + 1;
this.parentFingerprint = (parent != null) ? parent.getFingerprint() : 0;
}
/** Constructs a key from its components. This is not normally something you should use. */
public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
byte[] chainCode,
KeyCrypter crypter,
LazyECPoint pub,
EncryptedData priv,
@Nullable DeterministicKey parent) {
this(childNumberPath, chainCode, pub, null, parent);
this.encryptedPrivateKey = checkNotNull(priv);
this.keyCrypter = checkNotNull(crypter);
}
/**
* Return the fingerprint of this key's parent as an int value, or zero if this key is the
* root node of the key hierarchy. Raise an exception if the arguments are inconsistent.
* This method exists to avoid code repetition in the constructors.
*/
private int ascertainParentFingerprint(DeterministicKey parentKey, int parentFingerprint)
throws IllegalArgumentException {
if (parentFingerprint != 0) {
if (parent != null)
checkArgument(parent.getFingerprint() == parentFingerprint,
"parent fingerprint mismatch",
Integer.toHexString(parent.getFingerprint()), Integer.toHexString(parentFingerprint));
return parentFingerprint;
} else return 0;
}
/**
* Constructs a key from its components, including its public key data and possibly-redundant
* information about its parent key. Invoked when deserializing, but otherwise not something that
* you normally should use.
*/
public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
byte[] chainCode,
LazyECPoint publicAsPoint,
@Nullable DeterministicKey parent,
int depth,
int parentFingerprint) {
super(null, compressPoint(checkNotNull(publicAsPoint)));
checkArgument(chainCode.length == 32);
this.parent = parent;
this.childNumberPath = checkNotNull(childNumberPath);
this.chainCode = Arrays.copyOf(chainCode, chainCode.length);
this.depth = depth;
this.parentFingerprint = ascertainParentFingerprint(parent, parentFingerprint);
}
/**
* Constructs a key from its components, including its private key data and possibly-redundant
* information about its parent key. Invoked when deserializing, but otherwise not something that
* you normally should use.
*/
public DeterministicKey(ImmutableList<ChildNumber> childNumberPath,
byte[] chainCode,
BigInteger priv,
@Nullable DeterministicKey parent,
int depth,
int parentFingerprint) {
super(priv, compressPoint(ECKey.publicPointFromPrivate(priv)));
checkArgument(chainCode.length == 32);
this.parent = parent;
this.childNumberPath = checkNotNull(childNumberPath);
this.chainCode = Arrays.copyOf(chainCode, chainCode.length);
this.depth = depth;
this.parentFingerprint = ascertainParentFingerprint(parent, parentFingerprint);
}
/** Clones the key */
public DeterministicKey(DeterministicKey keyToClone, DeterministicKey newParent) {
super(keyToClone.priv, keyToClone.pub.get());
this.parent = newParent;
this.childNumberPath = keyToClone.childNumberPath;
this.chainCode = keyToClone.chainCode;
this.encryptedPrivateKey = keyToClone.encryptedPrivateKey;
this.depth = this.childNumberPath.size();
this.parentFingerprint = this.parent.getFingerprint();
}
/**
* Returns the path through some {@link DeterministicHierarchy} which reaches this keys position in the tree.
* A path can be written as 0/1/0 which means the first child of the root, the second child of that node, then
* the first child of that node.
*/
public ImmutableList<ChildNumber> getPath() {
return childNumberPath;
}
/**
* Returns the path of this key as a human readable string starting with M to indicate the master key.
*/
public String getPathAsString() {
return HDUtils.formatPath(getPath());
}
/**
* Return this key's depth in the hierarchy, where the root node is at depth zero.
* This may be different than the number of segments in the path if this key was
* deserialized without access to its parent.
*/
public int getDepth() {
return depth;
}
/** Returns the last element of the path returned by {@link DeterministicKey#getPath()} */
public ChildNumber getChildNumber() {
return childNumberPath.size() == 0 ? ChildNumber.ZERO : childNumberPath.get(childNumberPath.size() - 1);
}
/**
* Returns the chain code associated with this key. See the specification to learn more about chain codes.
*/
public byte[] getChainCode() {
return chainCode;
}
/**
* Returns RIPE-MD160(SHA256(pub key bytes)).
*/
public byte[] getIdentifier() {
return Utils.sha256hash160(getPubKey());
}
/** Returns the first 32 bits of the result of {@link #getIdentifier()}. */
public int getFingerprint() {
// TODO: why is this different than armory's fingerprint? BIP 32: "The first 32 bits of the identifier are called the fingerprint."
return ByteBuffer.wrap(Arrays.copyOfRange(getIdentifier(), 0, 4)).getInt();
}
@Nullable
public DeterministicKey getParent() {
return parent;
}
/**
* Return the fingerprint of the key from which this key was derived, if this is a
* child key, or else an array of four zero-value bytes.
*/
public int getParentFingerprint() {
return parentFingerprint;
}
/**
* Returns private key bytes, padded with zeros to 33 bytes.
* @throws java.lang.IllegalStateException if the private key bytes are missing.
*/
public byte[] getPrivKeyBytes33() {
byte[] bytes33 = new byte[33];
byte[] priv = getPrivKeyBytes();
System.arraycopy(priv, 0, bytes33, 33 - priv.length, priv.length);
return bytes33;
}
/**
* Returns the same key with the private bytes removed. May return the same instance. The purpose of this is to save
* memory: the private key can always be very efficiently rederived from a parent that a private key, so storing
* all the private keys in RAM is a poor tradeoff especially on constrained devices. This means that the returned
* key may still be usable for signing and so on, so don't expect it to be a true pubkey-only object! If you want
* that then you should follow this call with a call to {@link #dropParent()}.
*/
public DeterministicKey dropPrivateBytes() {
if (isPubKeyOnly())
return this;
else
return new DeterministicKey(getPath(), getChainCode(), pub, null, parent);
}
/**
* <p>Returns the same key with the parent pointer removed (it still knows its own path and the parent fingerprint).</p>
*
* <p>If this key doesn't have private key bytes stored/cached itself, but could rederive them from the parent, then
* the new key returned by this method won't be able to do that. Thus, using dropPrivateBytes().dropParent() on a
* regular DeterministicKey will yield a new DeterministicKey that cannot sign or do other things involving the
* private key at all.</p>
*/
public DeterministicKey dropParent() {
DeterministicKey key = new DeterministicKey(getPath(), getChainCode(), pub, priv, null);
key.parentFingerprint = parentFingerprint;
return key;
}
static byte[] addChecksum(byte[] input) {
int inputLength = input.length;
byte[] checksummed = new byte[inputLength + 4];
System.arraycopy(input, 0, checksummed, 0, inputLength);
byte[] checksum = Sha256Hash.hashTwice(input);
System.arraycopy(checksum, 0, checksummed, inputLength, 4);
return checksummed;
}
@Override
public DeterministicKey encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) throws KeyCrypterException {
throw new UnsupportedOperationException("Must supply a new parent for encryption");
}
public DeterministicKey encrypt(KeyCrypter keyCrypter, KeyParameter aesKey, @Nullable DeterministicKey newParent) throws KeyCrypterException {
// Same as the parent code, except we construct a DeterministicKey instead of an ECKey.
checkNotNull(keyCrypter);
if (newParent != null)
checkArgument(newParent.isEncrypted());
final byte[] privKeyBytes = getPrivKeyBytes();
checkState(privKeyBytes != null, "Private key is not available");
EncryptedData encryptedPrivateKey = keyCrypter.encrypt(privKeyBytes, aesKey);
DeterministicKey key = new DeterministicKey(childNumberPath, chainCode, keyCrypter, pub, encryptedPrivateKey, newParent);
if (newParent == null)
key.setCreationTimeSeconds(getCreationTimeSeconds());
return key;
}
/**
* A deterministic key is considered to be 'public key only' if it hasn't got a private key part and it cannot be
* rederived. If the hierarchy is encrypted this returns true.
*/
@Override
public boolean isPubKeyOnly() {
return super.isPubKeyOnly() && (parent == null || parent.isPubKeyOnly());
}
@Override
public boolean hasPrivKey() {
return findParentWithPrivKey() != null;
}
@Nullable
@Override
public byte[] getSecretBytes() {
return priv != null ? getPrivKeyBytes() : null;
}
/**
* A deterministic key is considered to be encrypted if it has access to encrypted private key bytes, OR if its
* parent does. The reason is because the parent would be encrypted under the same key and this key knows how to
* rederive its own private key bytes from the parent, if needed.
*/
@Override
public boolean isEncrypted() {
return priv == null && (super.isEncrypted() || (parent != null && parent.isEncrypted()));
}
/**
* Returns this keys {@link KeyCrypter} <b>or</b> the keycrypter of its parent key.
*/
@Override @Nullable
public KeyCrypter getKeyCrypter() {
if (keyCrypter != null)
return keyCrypter;
else if (parent != null)
return parent.getKeyCrypter();
else
return null;
}
@Override
public ECDSASignature sign(Sha256Hash input, @Nullable KeyParameter aesKey) throws KeyCrypterException {
if (isEncrypted()) {
// If the key is encrypted, ECKey.sign will decrypt it first before rerunning sign. Decryption walks the
// key hierarchy to find the private key (see below), so, we can just run the inherited method.
return super.sign(input, aesKey);
} else {
// If it's not encrypted, derive the private via the parents.
final BigInteger privateKey = findOrDerivePrivateKey();
if (privateKey == null) {
// This key is a part of a public-key only hierarchy and cannot be used for signing
throw new MissingPrivateKeyException();
}
return super.doSign(input, privateKey);
}
}
@Override
public DeterministicKey decrypt(KeyCrypter keyCrypter, KeyParameter aesKey) throws KeyCrypterException {
checkNotNull(keyCrypter);
// Check that the keyCrypter matches the one used to encrypt the keys, if set.
if (this.keyCrypter != null && !this.keyCrypter.equals(keyCrypter))
throw new KeyCrypterException("The keyCrypter being used to decrypt the key is different to the one that was used to encrypt it");
BigInteger privKey = findOrDeriveEncryptedPrivateKey(keyCrypter, aesKey);
DeterministicKey key = new DeterministicKey(childNumberPath, chainCode, privKey, parent);
if (!Arrays.equals(key.getPubKey(), getPubKey()))
throw new KeyCrypterException("Provided AES key is wrong");
if (parent == null)
key.setCreationTimeSeconds(getCreationTimeSeconds());
return key;
}
@Override
public DeterministicKey decrypt(KeyParameter aesKey) throws KeyCrypterException {
return (DeterministicKey) super.decrypt(aesKey);
}
// For when a key is encrypted, either decrypt our encrypted private key bytes, or work up the tree asking parents
// to decrypt and re-derive.
private BigInteger findOrDeriveEncryptedPrivateKey(KeyCrypter keyCrypter, KeyParameter aesKey) {
if (encryptedPrivateKey != null)
return new BigInteger(1, keyCrypter.decrypt(encryptedPrivateKey, aesKey));
// Otherwise we don't have it, but maybe we can figure it out from our parents. Walk up the tree looking for
// the first key that has some encrypted private key data.
DeterministicKey cursor = parent;
while (cursor != null) {
if (cursor.encryptedPrivateKey != null) break;
cursor = cursor.parent;
}
if (cursor == null)
throw new KeyCrypterException("Neither this key nor its parents have an encrypted private key");
byte[] parentalPrivateKeyBytes = keyCrypter.decrypt(cursor.encryptedPrivateKey, aesKey);
return derivePrivateKeyDownwards(cursor, parentalPrivateKeyBytes);
}
private DeterministicKey findParentWithPrivKey() {
DeterministicKey cursor = this;
while (cursor != null) {
if (cursor.priv != null) break;
cursor = cursor.parent;
}
return cursor;
}
@Nullable
private BigInteger findOrDerivePrivateKey() {
DeterministicKey cursor = findParentWithPrivKey();
if (cursor == null)
return null;
return derivePrivateKeyDownwards(cursor, cursor.priv.toByteArray());
}
private BigInteger derivePrivateKeyDownwards(DeterministicKey cursor, byte[] parentalPrivateKeyBytes) {
DeterministicKey downCursor = new DeterministicKey(cursor.childNumberPath, cursor.chainCode,
cursor.pub, new BigInteger(1, parentalPrivateKeyBytes), cursor.parent);
// Now we have to rederive the keys along the path back to ourselves. That path can be found by just truncating
// our path with the length of the parents path.
ImmutableList<ChildNumber> path = childNumberPath.subList(cursor.getPath().size(), childNumberPath.size());
for (ChildNumber num : path) {
downCursor = HDKeyDerivation.deriveChildKey(downCursor, num);
}
// downCursor is now the same key as us, but with private key bytes.
// If it's not, it means we tried decrypting with an invalid password and earlier checks e.g. for padding didn't
// catch it.
if (!downCursor.pub.equals(pub))
throw new KeyCrypterException("Could not decrypt bytes");
return checkNotNull(downCursor.priv);
}
/**
* Derives a child at the given index using hardened derivation. Note: {@code index} is
* not the "i" value. If you want the softened derivation, then use instead
* {@code HDKeyDerivation.deriveChildKey(this, new ChildNumber(child, false))}.
*/
public DeterministicKey derive(int child) {
return HDKeyDerivation.deriveChildKey(this, new ChildNumber(child, true));
}
/**
* Returns the private key of this deterministic key. Even if this object isn't storing the private key,
* it can be re-derived by walking up to the parents if necessary and this is what will happen.
* @throws java.lang.IllegalStateException if the parents are encrypted or a watching chain.
*/
@Override
public BigInteger getPrivKey() {
final BigInteger key = findOrDerivePrivateKey();
checkState(key != null, "Private key bytes not available");
return key;
}
@Deprecated
public byte[] serializePublic(NetworkParameters params) {
return serialize(params, true, Script.ScriptType.P2PKH);
}
@Deprecated
public byte[] serializePrivate(NetworkParameters params) {
return serialize(params, false, Script.ScriptType.P2PKH);
}
private byte[] serialize(NetworkParameters params, boolean pub, Script.ScriptType outputScriptType) {
ByteBuffer ser = ByteBuffer.allocate(78);
if (outputScriptType == Script.ScriptType.P2PKH)
ser.putInt(pub ? params.getBip32HeaderP2PKHpub() : params.getBip32HeaderP2PKHpriv());
else if (outputScriptType == Script.ScriptType.P2WPKH)
ser.putInt(pub ? params.getBip32HeaderP2WPKHpub() : params.getBip32HeaderP2WPKHpriv());
else
throw new IllegalStateException(outputScriptType.toString());
ser.put((byte) getDepth());
ser.putInt(getParentFingerprint());
ser.putInt(getChildNumber().i());
ser.put(getChainCode());
ser.put(pub ? getPubKey() : getPrivKeyBytes33());
checkState(ser.position() == 78);
return ser.array();
}
public String serializePubB58(NetworkParameters params, Script.ScriptType outputScriptType) {
return toBase58(serialize(params, true, outputScriptType));
}
public String serializePrivB58(NetworkParameters params, Script.ScriptType outputScriptType) {
return toBase58(serialize(params, false, outputScriptType));
}
public String serializePubB58(NetworkParameters params) {
return serializePubB58(params, Script.ScriptType.P2PKH);
}
public String serializePrivB58(NetworkParameters params) {
return serializePrivB58(params, Script.ScriptType.P2PKH);
}
static String toBase58(byte[] ser) {
return Base58.encode(addChecksum(ser));
}
/** Deserialize a base-58-encoded HD Key with no parent */
public static DeterministicKey deserializeB58(String base58, NetworkParameters params) {
return deserializeB58(null, base58, params);
}
/**
* Deserialize a base-58-encoded HD Key.
* @param parent The parent node in the given key's deterministic hierarchy.
* @throws IllegalArgumentException if the base58 encoded key could not be parsed.
*/
public static DeterministicKey deserializeB58(@Nullable DeterministicKey parent, String base58, NetworkParameters params) {
return deserialize(params, Base58.decodeChecked(base58), parent);
}
/**
* Deserialize an HD Key with no parent
*/
public static DeterministicKey deserialize(NetworkParameters params, byte[] serializedKey) {
return deserialize(params, serializedKey, null);
}
/**
* Deserialize an HD Key.
* @param parent The parent node in the given key's deterministic hierarchy.
*/
public static DeterministicKey deserialize(NetworkParameters params, byte[] serializedKey, @Nullable DeterministicKey parent) {
ByteBuffer buffer = ByteBuffer.wrap(serializedKey);
int header = buffer.getInt();
final boolean pub = header == params.getBip32HeaderP2PKHpub() || header == params.getBip32HeaderP2WPKHpub();
final boolean priv = header == params.getBip32HeaderP2PKHpriv() || header == params.getBip32HeaderP2WPKHpriv();
if (!(pub || priv))
throw new IllegalArgumentException("Unknown header bytes: " + toBase58(serializedKey).substring(0, 4));
int depth = buffer.get() & 0xFF; // convert signed byte to positive int since depth cannot be negative
final int parentFingerprint = buffer.getInt();
final int i = buffer.getInt();
final ChildNumber childNumber = new ChildNumber(i);
ImmutableList<ChildNumber> path;
if (parent != null) {
if (parentFingerprint == 0)
throw new IllegalArgumentException("Parent was provided but this key doesn't have one");
if (parent.getFingerprint() != parentFingerprint)
throw new IllegalArgumentException("Parent fingerprints don't match");
path = HDUtils.append(parent.getPath(), childNumber);
if (path.size() != depth)
throw new IllegalArgumentException("Depth does not match");
} else {
if (depth >= 1)
// We have been given a key that is not a root key, yet we lack the object representing the parent.
// This can happen when deserializing an account key for a watching wallet. In this case, we assume that
// the client wants to conceal the key's position in the hierarchy. The path is truncated at the
// parent's node.
path = ImmutableList.of(childNumber);
else path = ImmutableList.of();
}
byte[] chainCode = new byte[32];
buffer.get(chainCode);
byte[] data = new byte[33];
buffer.get(data);
checkArgument(!buffer.hasRemaining(), "Found unexpected data in key");
if (pub) {
return new DeterministicKey(path, chainCode, new LazyECPoint(ECKey.CURVE.getCurve(), data), parent, depth, parentFingerprint);
} else {
return new DeterministicKey(path, chainCode, new BigInteger(1, data), parent, depth, parentFingerprint);
}
}
/**
* The creation time of a deterministic key is equal to that of its parent, unless this key is the root of a tree
* in which case the time is stored alongside the key as per normal, see {@link ECKey#getCreationTimeSeconds()}.
*/
@Override
public long getCreationTimeSeconds() {
if (parent != null)
return parent.getCreationTimeSeconds();
else
return super.getCreationTimeSeconds();
}
/**
* The creation time of a deterministic key is equal to that of its parent, unless this key is the root of a tree.
* Thus, setting the creation time on a leaf is forbidden.
*/
@Override
public void setCreationTimeSeconds(long newCreationTimeSeconds) {
if (parent != null)
throw new IllegalStateException("Creation time can only be set on root keys.");
else
super.setCreationTimeSeconds(newCreationTimeSeconds);
}
/**
* Verifies equality of all fields but NOT the parent pointer (thus the same key derived in two separate hierarchy
* objects will equal each other.
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DeterministicKey other = (DeterministicKey) o;
return super.equals(other)
&& Arrays.equals(this.chainCode, other.chainCode)
&& Objects.equal(this.childNumberPath, other.childNumberPath);
}
@Override
public int hashCode() {
return Objects.hashCode(super.hashCode(), Arrays.hashCode(chainCode), childNumberPath);
}
@Override
public String toString() {
final MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(this).omitNullValues();
helper.add("pub", Utils.HEX.encode(pub.getEncoded()));
helper.add("chainCode", HEX.encode(chainCode));
helper.add("path", getPathAsString());
if (creationTimeSeconds > 0)
helper.add("creationTimeSeconds", creationTimeSeconds);
helper.add("isEncrypted", isEncrypted());
helper.add("isPubKeyOnly", isPubKeyOnly());
return helper.toString();
}
@Override
public void formatKeyWithAddress(boolean includePrivateKeys, @Nullable KeyParameter aesKey, StringBuilder builder,
NetworkParameters params, Script.ScriptType outputScriptType) {
builder.append(" addr:").append(Address.fromKey(params, this, outputScriptType).toString());
builder.append(" hash160:").append(Utils.HEX.encode(getPubKeyHash()));
builder.append(" (").append(getPathAsString()).append(")\n");
if (includePrivateKeys) {
builder.append(" ").append(toStringWithPrivate(aesKey, params)).append("\n");
}
}
}