From c163c9209f6b7b0f830429ab8f11e668cdac3f64 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Sat, 14 Mar 2026 11:48:27 -0700 Subject: [PATCH 01/11] DeterministicSeed: rename method `splitMnemonicCode()` from `decodeMnemonicCode()` --- .../main/java/org/bitcoinj/wallet/DeterministicSeed.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java index fc22a656aee..a44b2d038cf 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java @@ -137,7 +137,7 @@ public static DeterministicSeed ofRandom(SecureRandom random, int bits, String p * Use {@link #ofMnemonic(String, String, Instant)} or {@link #ofMnemonic(String, String)} instead. */ DeterministicSeed(String mnemonicString, byte[] seed, String passphrase, @Nullable Instant creationTime) { - this(decodeMnemonicCode(mnemonicString), seed, passphrase, creationTime); + this(splitMnemonicCode(mnemonicString), seed, passphrase, creationTime); } /** Internal use only. */ @@ -326,11 +326,13 @@ public String getMnemonicString() { return mnemonicCode != null ? InternalUtils.SPACE_JOINER.join(mnemonicCode) : null; } + // decode to String from byte[] private static List decodeMnemonicCode(byte[] mnemonicCode) { - return decodeMnemonicCode(new String(mnemonicCode, StandardCharsets.UTF_8)); + return splitMnemonicCode(new String(mnemonicCode, StandardCharsets.UTF_8)); } - private static List decodeMnemonicCode(String mnemonicCode) { + // Split mnemonic code into List + private static List splitMnemonicCode(String mnemonicCode) { return InternalUtils.WHITESPACE_SPLITTER.splitToList(mnemonicCode); } } From e0026eb51e8f671f387b5e8013581baab4492099 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Sat, 14 Mar 2026 11:52:25 -0700 Subject: [PATCH 02/11] DeterministicSeed: remove "internal use only" comment from private methods --- core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java index a44b2d038cf..17f040ed661 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java @@ -140,7 +140,6 @@ public static DeterministicSeed ofRandom(SecureRandom random, int bits, String p this(splitMnemonicCode(mnemonicString), seed, passphrase, creationTime); } - /** Internal use only. */ private DeterministicSeed(byte[] seed, List mnemonic, @Nullable Instant creationTime) { this.seed = Objects.requireNonNull(seed); this.mnemonicCode = Objects.requireNonNull(mnemonic); @@ -158,7 +157,6 @@ private DeterministicSeed(byte[] seed, List mnemonic, @Nullable Instant this.creationTime = creationTime; } - /** Internal use only. */ private DeterministicSeed(List mnemonicCode, byte @Nullable [] seed, String passphrase, @Nullable Instant creationTime) { this((seed != null ? seed : MnemonicCode.toSeed(mnemonicCode, Objects.requireNonNull(passphrase))), mnemonicCode, creationTime); } @@ -167,7 +165,6 @@ private DeterministicSeed(SecureRandom random, int bits, String passphrase) { this(getEntropy(random, bits), Objects.requireNonNull(passphrase), TimeUtils.currentTime().truncatedTo(ChronoUnit.SECONDS)); } - /** Internal use only. */ private DeterministicSeed(byte[] entropy, String passphrase, @Nullable Instant creationTime) { checkArgument(entropy.length * 8 >= DEFAULT_SEED_ENTROPY_BITS, () -> "entropy size too small"); Objects.requireNonNull(passphrase); From 92b6fdb8fb0c0bcc570ce175331e9e0433fcfcc0 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Sat, 14 Mar 2026 12:36:56 -0700 Subject: [PATCH 03/11] DeterministicKeyChainTest, DeterministicSeedTest: use `DeterministicSeed.ofMnemonic()` instead of private constructor This change also clarifies that in these cases we don't need a constructor that takes a binary seed. --- .../java/org/bitcoinj/wallet/DeterministicKeyChainTest.java | 2 +- .../test/java/org/bitcoinj/wallet/DeterministicSeedTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java b/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java index 41131919619..1311bab67ec 100644 --- a/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java +++ b/core/src/test/java/org/bitcoinj/wallet/DeterministicKeyChainTest.java @@ -799,7 +799,7 @@ private String readResourceFile(String filename) { @Test public void testToString() { - DeterministicSeed seed = new DeterministicSeed("correct horse battery staple", null, "", Instant.ofEpochSecond(1000L)); + DeterministicSeed seed = DeterministicSeed.ofMnemonic("correct horse battery staple", "", Instant.ofEpochSecond(1000L)); DeterministicKeyChain chain = DeterministicKeyChain.builder().seed(seed).build(); String str = chain.toString(); diff --git a/core/src/test/java/org/bitcoinj/wallet/DeterministicSeedTest.java b/core/src/test/java/org/bitcoinj/wallet/DeterministicSeedTest.java index fe988406ed4..aa679436f46 100644 --- a/core/src/test/java/org/bitcoinj/wallet/DeterministicSeedTest.java +++ b/core/src/test/java/org/bitcoinj/wallet/DeterministicSeedTest.java @@ -27,7 +27,7 @@ public class DeterministicSeedTest { @Test public void testToString() { long creationTime = 1000L; - DeterministicSeed seed = new DeterministicSeed("correct horse battery staple", null, "", Instant.ofEpochSecond(creationTime)); + DeterministicSeed seed = DeterministicSeed.ofMnemonic("correct horse battery staple", "", Instant.ofEpochSecond(creationTime)); String s1 = seed.toString(); assertTrue(s1.contains("DeterministicSeed")); From 4ec9a10c86acff590a745318a444009c126c0b9d Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Sat, 14 Mar 2026 12:44:18 -0700 Subject: [PATCH 04/11] DeteministicSeed: add static factories `fromProtobuf()`, `fromProtobufEncrypted() Use them in `DeterministicKeyChain` and make the "internal use only" constructors private. --- .../wallet/DeterministicKeyChain.java | 4 ++-- .../bitcoinj/wallet/DeterministicSeed.java | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java index 9cf257ec3b8..b86c7d88440 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicKeyChain.java @@ -880,7 +880,7 @@ public static List fromProtobuf(List keys, @N if (key.hasDeterministicSeed()) { seedBytes = key.getDeterministicSeed().toByteArray(); } - seed = new DeterministicSeed(key.getSecretBytes().toStringUtf8(), seedBytes, passphrase, seedCreationTime); + seed = DeterministicSeed.fromProtobuf(key.getSecretBytes().toStringUtf8(), seedBytes, passphrase, seedCreationTime); } else if (key.hasEncryptedData()) { if (key.hasDeterministicSeed()) throw new UnreadableWalletException("Malformed key proto: " + key); @@ -892,7 +892,7 @@ public static List fromProtobuf(List keys, @N encryptedSeedBytes = new EncryptedData(encryptedSeed.getInitialisationVector().toByteArray(), encryptedSeed.getEncryptedPrivateKey().toByteArray()); } - seed = new DeterministicSeed(data, encryptedSeedBytes, seedCreationTime); + seed = DeterministicSeed.fromProtobufEncrypted(data, encryptedSeedBytes, seedCreationTime); } else { throw new UnreadableWalletException("Malformed key proto: " + key); } diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java index 17f040ed661..3f56e009033 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java @@ -132,11 +132,17 @@ public static DeterministicSeed ofRandom(SecureRandom random, int bits, String p return new DeterministicSeed(random, bits, passphrase); } - /** - * Internal use only – will be restricted to private in a future release. - * Use {@link #ofMnemonic(String, String, Instant)} or {@link #ofMnemonic(String, String)} instead. - */ - DeterministicSeed(String mnemonicString, byte[] seed, String passphrase, @Nullable Instant creationTime) { + // For use in DeteministicKeyChain.fromProtobuf() only + static DeterministicSeed fromProtobuf(String mnemonicString, byte[] seed, String passphrase, @Nullable Instant creationTime) { + return new DeterministicSeed(mnemonicString, seed, passphrase, creationTime); + } + + // For use in DeteministicKeyChain.fromProtobuf() only + static DeterministicSeed fromProtobufEncrypted(EncryptedData encryptedMnemonic, @Nullable EncryptedData encryptedSeed, @Nullable Instant creationTime) { + return new DeterministicSeed(encryptedMnemonic, encryptedSeed, creationTime); + } + + private DeterministicSeed(String mnemonicString, byte[] seed, String passphrase, @Nullable Instant creationTime) { this(splitMnemonicCode(mnemonicString), seed, passphrase, creationTime); } @@ -148,8 +154,7 @@ private DeterministicSeed(byte[] seed, List mnemonic, @Nullable Instant this.creationTime = creationTime; } - /** Internal use only – will be restricted to private in a future release. */ - DeterministicSeed(EncryptedData encryptedMnemonic, @Nullable EncryptedData encryptedSeed, @Nullable Instant creationTime) { + private DeterministicSeed(EncryptedData encryptedMnemonic, @Nullable EncryptedData encryptedSeed, @Nullable Instant creationTime) { this.seed = null; this.mnemonicCode = null; this.encryptedMnemonicCode = Objects.requireNonNull(encryptedMnemonic); From 1c2be728d39585dc9f835c1cd3c2849e9f60a6ba Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Sat, 14 Mar 2026 12:51:38 -0700 Subject: [PATCH 05/11] DeterministicSeed: remove redundant private constructor that takes mnemonic code Instead use `splitMnemonicCode()` directly in the factory methods. --- .../java/org/bitcoinj/wallet/DeterministicSeed.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java index 3f56e009033..04973e65584 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java @@ -66,7 +66,7 @@ public class DeterministicSeed implements EncryptableItem { * @param creationTime when the seed was originally created */ public static DeterministicSeed ofMnemonic(String mnemonicCode, String passphrase, Instant creationTime) { - return new DeterministicSeed(mnemonicCode, null, passphrase, Objects.requireNonNull(creationTime)); + return new DeterministicSeed(splitMnemonicCode(mnemonicCode), null, passphrase, Objects.requireNonNull(creationTime)); } /** @@ -76,7 +76,7 @@ public static DeterministicSeed ofMnemonic(String mnemonicCode, String passphras * @param passphrase user supplied passphrase, or empty string if there is no passphrase */ public static DeterministicSeed ofMnemonic(String mnemonicCode, String passphrase) { - return new DeterministicSeed(mnemonicCode, null, passphrase, null); + return new DeterministicSeed(splitMnemonicCode(mnemonicCode), null, passphrase, null); } /** @@ -134,7 +134,7 @@ public static DeterministicSeed ofRandom(SecureRandom random, int bits, String p // For use in DeteministicKeyChain.fromProtobuf() only static DeterministicSeed fromProtobuf(String mnemonicString, byte[] seed, String passphrase, @Nullable Instant creationTime) { - return new DeterministicSeed(mnemonicString, seed, passphrase, creationTime); + return new DeterministicSeed(splitMnemonicCode(mnemonicString), seed, passphrase, creationTime); } // For use in DeteministicKeyChain.fromProtobuf() only @@ -142,10 +142,6 @@ static DeterministicSeed fromProtobufEncrypted(EncryptedData encryptedMnemonic, return new DeterministicSeed(encryptedMnemonic, encryptedSeed, creationTime); } - private DeterministicSeed(String mnemonicString, byte[] seed, String passphrase, @Nullable Instant creationTime) { - this(splitMnemonicCode(mnemonicString), seed, passphrase, creationTime); - } - private DeterministicSeed(byte[] seed, List mnemonic, @Nullable Instant creationTime) { this.seed = Objects.requireNonNull(seed); this.mnemonicCode = Objects.requireNonNull(mnemonic); From 03cdf026005c036c7e9e148d7b5d27860ac80f33 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Sat, 14 Mar 2026 12:52:57 -0700 Subject: [PATCH 06/11] DeterministicSeed: remove redundant private constructor that takes `SecureRandom` Instead use `getEntropy()` directly in the `ofRandom()` factory method. --- .../main/java/org/bitcoinj/wallet/DeterministicSeed.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java index 04973e65584..f2cc5544a75 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java @@ -129,7 +129,7 @@ public static DeterministicSeed ofEntropy(byte[] entropy, String passphrase) { * @param passphrase user supplied passphrase, or empty string if there is no passphrase */ public static DeterministicSeed ofRandom(SecureRandom random, int bits, String passphrase) { - return new DeterministicSeed(random, bits, passphrase); + return new DeterministicSeed(getEntropy(random, bits), Objects.requireNonNull(passphrase), TimeUtils.currentTime().truncatedTo(ChronoUnit.SECONDS)); } // For use in DeteministicKeyChain.fromProtobuf() only @@ -162,10 +162,6 @@ private DeterministicSeed(List mnemonicCode, byte @Nullable [] seed, Str this((seed != null ? seed : MnemonicCode.toSeed(mnemonicCode, Objects.requireNonNull(passphrase))), mnemonicCode, creationTime); } - private DeterministicSeed(SecureRandom random, int bits, String passphrase) { - this(getEntropy(random, bits), Objects.requireNonNull(passphrase), TimeUtils.currentTime().truncatedTo(ChronoUnit.SECONDS)); - } - private DeterministicSeed(byte[] entropy, String passphrase, @Nullable Instant creationTime) { checkArgument(entropy.length * 8 >= DEFAULT_SEED_ENTROPY_BITS, () -> "entropy size too small"); Objects.requireNonNull(passphrase); From 7cdc500f03522e48e7229666ea657af4a1e88f12 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Sat, 14 Mar 2026 13:20:31 -0700 Subject: [PATCH 07/11] DeterministicSeed: extract private helpers `optionalSeedFromMnemonic()`, `seedFromMnemonic()` from constructor --- .../java/org/bitcoinj/wallet/DeterministicSeed.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java index f2cc5544a75..d7fec2e0d4a 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java @@ -159,7 +159,7 @@ private DeterministicSeed(EncryptedData encryptedMnemonic, @Nullable EncryptedDa } private DeterministicSeed(List mnemonicCode, byte @Nullable [] seed, String passphrase, @Nullable Instant creationTime) { - this((seed != null ? seed : MnemonicCode.toSeed(mnemonicCode, Objects.requireNonNull(passphrase))), mnemonicCode, creationTime); + this(optionalSeedFromMnemonic(mnemonicCode, passphrase, seed), mnemonicCode, creationTime); } private DeterministicSeed(byte[] entropy, String passphrase, @Nullable Instant creationTime) { @@ -173,6 +173,15 @@ private DeterministicSeed(byte[] entropy, String passphrase, @Nullable Instant c this.creationTime = creationTime; } + // If seed is null, generate seed from mnemonic and passphrase. Otherwise, return unmodified seed. + private static byte[] optionalSeedFromMnemonic(List mnemonicCode, String passphrase, byte @Nullable [] seed) { + return seed != null ? seed : seedFromMnemonic(mnemonicCode, passphrase); + } + + private static byte[] seedFromMnemonic(List mnemonicCode, String passphrase) { + return MnemonicCode.toSeed(mnemonicCode, Objects.requireNonNull(passphrase)); + } + private static byte[] getEntropy(SecureRandom random, int bits) { checkArgument(bits <= MAX_SEED_ENTROPY_BITS, () -> "requested entropy size too large"); From 3aae45419798d92aa3dcd9c65fd14e578166835b Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Sat, 14 Mar 2026 13:27:32 -0700 Subject: [PATCH 08/11] DeterministicSeed: remove redundant constructor that takes an optional mnemonic code Instead use `optionalSeedFromMnemonic()` and `seedFromMnemonic()` directly in the constructors and factory methods. --- .../org/bitcoinj/wallet/DeterministicSeed.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java index d7fec2e0d4a..0b3dabc8ba2 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java @@ -66,7 +66,7 @@ public class DeterministicSeed implements EncryptableItem { * @param creationTime when the seed was originally created */ public static DeterministicSeed ofMnemonic(String mnemonicCode, String passphrase, Instant creationTime) { - return new DeterministicSeed(splitMnemonicCode(mnemonicCode), null, passphrase, Objects.requireNonNull(creationTime)); + return new DeterministicSeed(seedFromMnemonic(splitMnemonicCode(mnemonicCode), passphrase), splitMnemonicCode(mnemonicCode), Objects.requireNonNull(creationTime)); } /** @@ -76,7 +76,7 @@ public static DeterministicSeed ofMnemonic(String mnemonicCode, String passphras * @param passphrase user supplied passphrase, or empty string if there is no passphrase */ public static DeterministicSeed ofMnemonic(String mnemonicCode, String passphrase) { - return new DeterministicSeed(splitMnemonicCode(mnemonicCode), null, passphrase, null); + return new DeterministicSeed(seedFromMnemonic(splitMnemonicCode(mnemonicCode), passphrase), splitMnemonicCode(mnemonicCode), null); } /** @@ -87,7 +87,7 @@ public static DeterministicSeed ofMnemonic(String mnemonicCode, String passphras * @param creationTime when the seed was originally created */ public static DeterministicSeed ofMnemonic(List mnemonicCode, String passphrase, Instant creationTime) { - return new DeterministicSeed(mnemonicCode, null, passphrase, Objects.requireNonNull(creationTime)); + return new DeterministicSeed(seedFromMnemonic(mnemonicCode, passphrase), mnemonicCode, Objects.requireNonNull(creationTime)); } /** @@ -97,7 +97,7 @@ public static DeterministicSeed ofMnemonic(List mnemonicCode, String pas * @param passphrase user supplied passphrase, or empty string if there is no passphrase */ public static DeterministicSeed ofMnemonic(List mnemonicCode, String passphrase) { - return new DeterministicSeed(mnemonicCode, null, passphrase, null); + return new DeterministicSeed(seedFromMnemonic(mnemonicCode, passphrase), mnemonicCode, null); } /** @@ -133,8 +133,8 @@ public static DeterministicSeed ofRandom(SecureRandom random, int bits, String p } // For use in DeteministicKeyChain.fromProtobuf() only - static DeterministicSeed fromProtobuf(String mnemonicString, byte[] seed, String passphrase, @Nullable Instant creationTime) { - return new DeterministicSeed(splitMnemonicCode(mnemonicString), seed, passphrase, creationTime); + static DeterministicSeed fromProtobuf(String mnemonicString, byte @Nullable [] seed, String passphrase, @Nullable Instant creationTime) { + return new DeterministicSeed(optionalSeedFromMnemonic(splitMnemonicCode(mnemonicString), passphrase, seed), splitMnemonicCode(mnemonicString), creationTime); } // For use in DeteministicKeyChain.fromProtobuf() only @@ -158,10 +158,6 @@ private DeterministicSeed(EncryptedData encryptedMnemonic, @Nullable EncryptedDa this.creationTime = creationTime; } - private DeterministicSeed(List mnemonicCode, byte @Nullable [] seed, String passphrase, @Nullable Instant creationTime) { - this(optionalSeedFromMnemonic(mnemonicCode, passphrase, seed), mnemonicCode, creationTime); - } - private DeterministicSeed(byte[] entropy, String passphrase, @Nullable Instant creationTime) { checkArgument(entropy.length * 8 >= DEFAULT_SEED_ENTROPY_BITS, () -> "entropy size too small"); Objects.requireNonNull(passphrase); @@ -284,7 +280,7 @@ public DeterministicSeed decrypt(KeyCrypter crypter, String passphrase, AesKey a Objects.requireNonNull(encryptedMnemonicCode); List mnemonic = decodeMnemonicCode(crypter.decrypt(encryptedMnemonicCode, aesKey)); byte[] seed = encryptedSeed == null ? null : crypter.decrypt(encryptedSeed, aesKey); - return new DeterministicSeed(mnemonic, seed, passphrase, creationTime); + return new DeterministicSeed(optionalSeedFromMnemonic(mnemonic, passphrase, seed), mnemonic, creationTime); } @Override From db4f38f7e0e92a929c7c2ec509fb5010983ad3c6 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Fri, 20 Mar 2026 07:29:45 -0700 Subject: [PATCH 09/11] DeterministicSeed: add static factory `ofEntropyInternal()` This internal factory takes an optional `creationTime` and will be used to replace the private constructor that takes entrophy. --- .../java/org/bitcoinj/wallet/DeterministicSeed.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java index 0b3dabc8ba2..eb544b97ce0 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java @@ -108,7 +108,7 @@ public static DeterministicSeed ofMnemonic(List mnemonicCode, String pas * @param creationTime when the seed was originally created */ public static DeterministicSeed ofEntropy(byte[] entropy, String passphrase, Instant creationTime) { - return new DeterministicSeed(entropy, passphrase, Objects.requireNonNull(creationTime)); + return DeterministicSeed.ofEntropyInternal(entropy, passphrase, Objects.requireNonNull(creationTime)); } /** @@ -118,7 +118,11 @@ public static DeterministicSeed ofEntropy(byte[] entropy, String passphrase, Ins * @param passphrase user supplied passphrase, or empty string if there is no passphrase */ public static DeterministicSeed ofEntropy(byte[] entropy, String passphrase) { - return new DeterministicSeed(entropy, passphrase, null); + return DeterministicSeed.ofEntropyInternal(entropy, passphrase, null); + } + + private static DeterministicSeed ofEntropyInternal(byte[] entropy, String passphrase, @Nullable Instant creationTime) { + return new DeterministicSeed(entropy, passphrase, creationTime); } /** @@ -129,7 +133,7 @@ public static DeterministicSeed ofEntropy(byte[] entropy, String passphrase) { * @param passphrase user supplied passphrase, or empty string if there is no passphrase */ public static DeterministicSeed ofRandom(SecureRandom random, int bits, String passphrase) { - return new DeterministicSeed(getEntropy(random, bits), Objects.requireNonNull(passphrase), TimeUtils.currentTime().truncatedTo(ChronoUnit.SECONDS)); + return DeterministicSeed.ofEntropyInternal(getEntropy(random, bits), Objects.requireNonNull(passphrase), TimeUtils.currentTime().truncatedTo(ChronoUnit.SECONDS)); } // For use in DeteministicKeyChain.fromProtobuf() only From f8db4d07de9da2d0d4138ec5c1f17e723b95e6dd Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Sat, 14 Mar 2026 13:39:49 -0700 Subject: [PATCH 10/11] DeterministicSeed: replace constructor that takes entrophy with `ofEntropyInternal()` Move the checking and conversion functionalities of the entropy constructor to the `ofEntropyInternal()` factory, and remove the constructor that takes entrophy. --- .../org/bitcoinj/wallet/DeterministicSeed.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java index eb544b97ce0..aaeebbff13d 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java @@ -122,7 +122,11 @@ public static DeterministicSeed ofEntropy(byte[] entropy, String passphrase) { } private static DeterministicSeed ofEntropyInternal(byte[] entropy, String passphrase, @Nullable Instant creationTime) { - return new DeterministicSeed(entropy, passphrase, creationTime); + checkArgument(entropy.length * 8 >= DEFAULT_SEED_ENTROPY_BITS, () -> "entropy size too small"); + Objects.requireNonNull(passphrase); + List mnemonicCode = MnemonicCode.INSTANCE.toMnemonic(entropy); + byte[] seed = MnemonicCode.toSeed(mnemonicCode, passphrase); + return new DeterministicSeed(seed, mnemonicCode, creationTime); } /** @@ -162,17 +166,6 @@ private DeterministicSeed(EncryptedData encryptedMnemonic, @Nullable EncryptedDa this.creationTime = creationTime; } - private DeterministicSeed(byte[] entropy, String passphrase, @Nullable Instant creationTime) { - checkArgument(entropy.length * 8 >= DEFAULT_SEED_ENTROPY_BITS, () -> "entropy size too small"); - Objects.requireNonNull(passphrase); - - this.mnemonicCode = MnemonicCode.INSTANCE.toMnemonic(entropy); - this.seed = MnemonicCode.toSeed(mnemonicCode, passphrase); - this.encryptedMnemonicCode = null; - this.encryptedSeed = null; - this.creationTime = creationTime; - } - // If seed is null, generate seed from mnemonic and passphrase. Otherwise, return unmodified seed. private static byte[] optionalSeedFromMnemonic(List mnemonicCode, String passphrase, byte @Nullable [] seed) { return seed != null ? seed : seedFromMnemonic(mnemonicCode, passphrase); From ca7195ce841f649367864462d507296d4073dcb5 Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Sat, 14 Mar 2026 13:50:05 -0700 Subject: [PATCH 11/11] DeterministicSeed: add comment to each of remaining "canonical" constructors --- core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java index aaeebbff13d..fdd91424f45 100644 --- a/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java +++ b/core/src/main/java/org/bitcoinj/wallet/DeterministicSeed.java @@ -150,6 +150,7 @@ static DeterministicSeed fromProtobufEncrypted(EncryptedData encryptedMnemonic, return new DeterministicSeed(encryptedMnemonic, encryptedSeed, creationTime); } + // Canonical constructor: both seed and mnemonic sentence are present private DeterministicSeed(byte[] seed, List mnemonic, @Nullable Instant creationTime) { this.seed = Objects.requireNonNull(seed); this.mnemonicCode = Objects.requireNonNull(mnemonic); @@ -158,6 +159,7 @@ private DeterministicSeed(byte[] seed, List mnemonic, @Nullable Instant this.creationTime = creationTime; } + // Canonical constructor: encrypted mnemonic sentence and optional encrypted seed private DeterministicSeed(EncryptedData encryptedMnemonic, @Nullable EncryptedData encryptedSeed, @Nullable Instant creationTime) { this.seed = null; this.mnemonicCode = null;