forked from Qortal/qortal
Merge pull request #53 from DrewMPeacock/master
Removes the flawed BIP-39 implementation in the core.
This commit is contained in:
commit
549b68cf71
@ -33,7 +33,6 @@ import org.qortal.transaction.Transaction.TransactionType;
|
|||||||
import org.qortal.transform.Transformer;
|
import org.qortal.transform.Transformer;
|
||||||
import org.qortal.transform.transaction.TransactionTransformer;
|
import org.qortal.transform.transaction.TransactionTransformer;
|
||||||
import org.qortal.transform.transaction.TransactionTransformer.Transformation;
|
import org.qortal.transform.transaction.TransactionTransformer.Transformation;
|
||||||
import org.qortal.utils.BIP39;
|
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
@ -195,123 +194,6 @@ public class UtilsResource {
|
|||||||
return Base58.encode(random);
|
return Base58.encode(random);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("/mnemonic")
|
|
||||||
@Operation(
|
|
||||||
summary = "Generate 12-word BIP39 mnemonic",
|
|
||||||
description = "Optionally pass 16-byte, base58-encoded entropy or entropy will be internally generated.<br>"
|
|
||||||
+ "Example entropy input: YcVfxkQb6JRzqk5kF2tNLv",
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "mnemonic",
|
|
||||||
content = @Content(
|
|
||||||
mediaType = MediaType.TEXT_PLAIN,
|
|
||||||
schema = @Schema(
|
|
||||||
type = "string"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.INVALID_DATA})
|
|
||||||
public String getMnemonic(@QueryParam("entropy") String suppliedEntropy) {
|
|
||||||
if (Settings.getInstance().isApiRestricted())
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* BIP39 word lists have 2048 entries so can be represented by 11 bits.
|
|
||||||
* UUID (128bits) and another 4 bits gives 132 bits.
|
|
||||||
* 132 bits, divided by 11, gives 12 words.
|
|
||||||
*/
|
|
||||||
byte[] entropy;
|
|
||||||
if (suppliedEntropy != null) {
|
|
||||||
// Use caller-supplied entropy input
|
|
||||||
try {
|
|
||||||
entropy = Base58.decode(suppliedEntropy);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must be 16-bytes
|
|
||||||
if (entropy.length != 16)
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
|
||||||
} else {
|
|
||||||
// Generate entropy internally
|
|
||||||
UUID uuid = UUID.randomUUID();
|
|
||||||
|
|
||||||
byte[] uuidMSB = Longs.toByteArray(uuid.getMostSignificantBits());
|
|
||||||
byte[] uuidLSB = Longs.toByteArray(uuid.getLeastSignificantBits());
|
|
||||||
entropy = Bytes.concat(uuidMSB, uuidLSB);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use SHA256 to generate more bits
|
|
||||||
byte[] hash = Crypto.digest(entropy);
|
|
||||||
|
|
||||||
// Append first 4 bits from hash to end. (Actually 8 bits but we only use 4).
|
|
||||||
byte checksum = (byte) (hash[0] & 0xf0);
|
|
||||||
entropy = Bytes.concat(entropy, new byte[] {
|
|
||||||
checksum
|
|
||||||
});
|
|
||||||
|
|
||||||
return BIP39.encode(entropy, "en");
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("/mnemonic")
|
|
||||||
@Operation(
|
|
||||||
summary = "Calculate binary entropy from 12-word BIP39 mnemonic",
|
|
||||||
description = "Returns the base58-encoded binary form, or \"false\" if mnemonic is invalid.",
|
|
||||||
requestBody = @RequestBody(
|
|
||||||
required = true,
|
|
||||||
content = @Content(
|
|
||||||
mediaType = MediaType.TEXT_PLAIN,
|
|
||||||
schema = @Schema(
|
|
||||||
type = "string"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
responses = {
|
|
||||||
@ApiResponse(
|
|
||||||
description = "entropy in base58",
|
|
||||||
content = @Content(
|
|
||||||
mediaType = MediaType.TEXT_PLAIN,
|
|
||||||
schema = @Schema(
|
|
||||||
type = "string"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@ApiErrors({ApiError.NON_PRODUCTION})
|
|
||||||
public String fromMnemonic(String mnemonic) {
|
|
||||||
if (Settings.getInstance().isApiRestricted())
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
|
||||||
|
|
||||||
if (mnemonic.isEmpty())
|
|
||||||
return "false";
|
|
||||||
|
|
||||||
// Strip leading/trailing whitespace if any
|
|
||||||
mnemonic = mnemonic.trim();
|
|
||||||
|
|
||||||
String[] phraseWords = mnemonic.split(" ");
|
|
||||||
if (phraseWords.length != 12)
|
|
||||||
return "false";
|
|
||||||
|
|
||||||
// Convert BIP39 mnemonic to binary
|
|
||||||
byte[] binary = BIP39.decode(phraseWords, "en");
|
|
||||||
if (binary == null)
|
|
||||||
return "false";
|
|
||||||
|
|
||||||
byte[] entropy = Arrays.copyOf(binary, 16); // 132 bits is 16.5 bytes, but we're discarding checksum nybble
|
|
||||||
|
|
||||||
byte checksumNybble = (byte) (binary[16] & 0xf0);
|
|
||||||
byte[] checksum = Crypto.digest(entropy);
|
|
||||||
if (checksumNybble != (byte) (checksum[0] & 0xf0))
|
|
||||||
return "false";
|
|
||||||
|
|
||||||
return Base58.encode(entropy);
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/privatekey")
|
@Path("/privatekey")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
package org.qortal.utils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.qortal.globalization.BIP39WordList;
|
|
||||||
|
|
||||||
public class BIP39 {
|
|
||||||
|
|
||||||
private static final int BITS_PER_WORD = 11;
|
|
||||||
|
|
||||||
/** Convert BIP39 mnemonic to binary 'entropy' */
|
|
||||||
public static byte[] decode(String[] phraseWords, String lang) {
|
|
||||||
if (lang == null)
|
|
||||||
lang = "en";
|
|
||||||
|
|
||||||
List<String> wordList = BIP39WordList.INSTANCE.getByLang(lang);
|
|
||||||
if (wordList == null)
|
|
||||||
throw new IllegalStateException("BIP39 word list for lang '" + lang + "' unavailable");
|
|
||||||
|
|
||||||
byte[] entropy = new byte[(phraseWords.length * BITS_PER_WORD + 7) / 8];
|
|
||||||
int byteIndex = 0;
|
|
||||||
int bitShift = 3;
|
|
||||||
|
|
||||||
for (int i = 0; i < phraseWords.length; ++i) {
|
|
||||||
int wordListIndex = wordList.indexOf(phraseWords[i]);
|
|
||||||
if (wordListIndex == -1)
|
|
||||||
// Word not found
|
|
||||||
return null;
|
|
||||||
|
|
||||||
entropy[byteIndex++] |= (byte) (wordListIndex >> bitShift);
|
|
||||||
|
|
||||||
bitShift = 8 - bitShift;
|
|
||||||
if (bitShift >= 0) {
|
|
||||||
// Leftover fits inside one byte
|
|
||||||
entropy[byteIndex] |= (byte) ((wordListIndex << bitShift));
|
|
||||||
bitShift = BITS_PER_WORD - bitShift;
|
|
||||||
} else {
|
|
||||||
// Leftover spread over next two bytes
|
|
||||||
bitShift = - bitShift;
|
|
||||||
entropy[byteIndex++] |= (byte) (wordListIndex >> bitShift);
|
|
||||||
|
|
||||||
entropy[byteIndex] |= (byte) (wordListIndex << (8 - bitShift));
|
|
||||||
bitShift = bitShift + BITS_PER_WORD - 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entropy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Convert binary entropy to BIP39 mnemonic */
|
|
||||||
public static String encode(byte[] entropy, String lang) {
|
|
||||||
if (lang == null)
|
|
||||||
lang = "en";
|
|
||||||
|
|
||||||
List<String> wordList = BIP39WordList.INSTANCE.getByLang(lang);
|
|
||||||
if (wordList == null)
|
|
||||||
throw new IllegalStateException("BIP39 word list for lang '" + lang + "' unavailable");
|
|
||||||
|
|
||||||
List<String> phraseWords = new ArrayList<>();
|
|
||||||
|
|
||||||
int bitMask = 128; // MSB first
|
|
||||||
int byteIndex = 0;
|
|
||||||
while (true) {
|
|
||||||
int wordListIndex = 0;
|
|
||||||
for (int bitCount = 0; bitCount < BITS_PER_WORD; ++bitCount) {
|
|
||||||
wordListIndex <<= 1;
|
|
||||||
|
|
||||||
if ((entropy[byteIndex] & bitMask) != 0)
|
|
||||||
++wordListIndex;
|
|
||||||
|
|
||||||
bitMask >>= 1;
|
|
||||||
if (bitMask == 0) {
|
|
||||||
bitMask = 128;
|
|
||||||
++byteIndex;
|
|
||||||
|
|
||||||
if (byteIndex >= entropy.length)
|
|
||||||
return String.join(" ", phraseWords);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
phraseWords.add(wordList.get(wordListIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,6 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|||||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
import org.qortal.account.PrivateKeyAccount;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.utils.BIP39;
|
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
|
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
@ -44,15 +43,13 @@ public class VanityGen {
|
|||||||
byte checksum = (byte) (hash[0] & 0xf0);
|
byte checksum = (byte) (hash[0] & 0xf0);
|
||||||
byte[] entropy132 = Bytes.concat(entropy, new byte[] { checksum });
|
byte[] entropy132 = Bytes.concat(entropy, new byte[] { checksum });
|
||||||
|
|
||||||
String mnemonic = BIP39.encode(entropy132, "en");
|
|
||||||
|
|
||||||
PrivateKeyAccount account = new PrivateKeyAccount(null, hash);
|
PrivateKeyAccount account = new PrivateKeyAccount(null, hash);
|
||||||
|
|
||||||
if (!account.getAddress().startsWith(prefix))
|
if (!account.getAddress().startsWith(prefix))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
System.out.println(String.format("Address: %s, public key: %s, private key: %s, mnemonic: %s",
|
System.out.println(String.format("Address: %s, public key: %s, private key: %s",
|
||||||
account.getAddress(), Base58.encode(account.getPublicKey()), Base58.encode(hash), mnemonic));
|
account.getAddress(), Base58.encode(account.getPublicKey()), Base58.encode(hash)));
|
||||||
System.out.flush();
|
System.out.flush();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user