package snowblossom.client; import com.google.common.collect.ImmutableList; import com.google.protobuf.ByteString; import java.io.ByteArrayInputStream; import java.math.BigInteger; import java.security.KeyFactory; import java.security.PrivateKey; import java.security.SecureRandom; import java.security.spec.ECPrivateKeySpec; import java.util.LinkedList; import java.util.List; import java.util.Random; import java.util.Scanner; import org.bitcoinj.crypto.ChildNumber; import org.bitcoinj.crypto.DeterministicHierarchy; import org.bitcoinj.crypto.DeterministicKey; import org.bitcoinj.crypto.HDKeyDerivation; import org.bitcoinj.crypto.MnemonicCode; import snowblossom.lib.AddressUtil; import snowblossom.lib.Globals; import snowblossom.lib.KeyUtil; import snowblossom.lib.NetworkParams; import snowblossom.lib.SignatureUtil; import snowblossom.lib.ValidationException; import snowblossom.proto.*; public class SeedUtil { public static MnemonicCode getMCode() { StringBuilder sb = new StringBuilder(); for(String s : SeedWordList.getWordList()) { sb.append(s); sb.append('\n'); } byte[] b = sb.toString().getBytes(); try { return new MnemonicCode(new ByteArrayInputStream(b),"ad90bf3beb7b0eb7e5acd74727dc0da96e0a280a258354e7293fb7e211ac03db"); } catch(java.io.IOException e) { throw new RuntimeException(e); } } public static String generateSeed(int words) { Random rnd = new SecureRandom(); int bc = 0; if (words == 12) bc=16; if (words == 18) bc=24; if (words == 24) bc=32; if (bc == 0) throw new RuntimeException("Words must be 12, 18 or 24"); byte[] entropy = new byte[bc]; rnd.nextBytes(entropy); try { List<String> lst = getMCode().toMnemonic(entropy); StringBuilder sb = new StringBuilder(); boolean first=true; for(String s : lst) { if (!first) sb.append(" "); sb.append(s); first=false; } return sb.toString(); } catch(org.bitcoinj.crypto.MnemonicException e) { throw new RuntimeException(e); } } public static ImmutableList<String> getWordsFromSeed(String str) { Scanner scan = new Scanner(str.toLowerCase()); LinkedList<String> lst = new LinkedList<>(); while(scan.hasNext()) { lst.add(scan.next()); } return ImmutableList.copyOf(lst); } public static void checkSeed(String seed) throws ValidationException { List<String> lst = getWordsFromSeed(seed); try { getMCode().check(lst); } catch(org.bitcoinj.crypto.MnemonicException e) { throw new ValidationException(e); } } public static ByteString decodeSeed(String seed, String pass) { List<String> lst = getWordsFromSeed(seed); return ByteString.copyFrom(getMCode().toSeed( lst, pass)); } public static ByteString getSeedIdFromXpub(String xpub) { DeterministicKey account_key = DeterministicKey.deserializeB58(xpub, org.bitcoinj.params.MainNetParams.get()); ByteString seed_id = ByteString.copyFrom(account_key.getIdentifier()); return seed_id; } public static ByteString getSeedId(NetworkParams params, String seed_str, String pass, int account) { ByteString seed = decodeSeed(seed_str,pass); DeterministicKey dk = HDKeyDerivation.createMasterPrivateKey(seed.toByteArray()); DeterministicHierarchy dh = new DeterministicHierarchy(dk); DeterministicKey dk_acct = dh.get( ImmutableList.of( new ChildNumber(44,true), new ChildNumber(params.getBIP44CoinNumber(),true), new ChildNumber(account,true) ), true, true); String xpub = dk_acct.serializePubB58( org.bitcoinj.params.MainNetParams.get() ); ByteString seed_id = ByteString.copyFrom(dk_acct.getIdentifier()); return seed_id; } public static String getSeedXpub(NetworkParams params, String seed_str, String pass, int account) { ByteString seed = decodeSeed(seed_str,pass); DeterministicKey dk = HDKeyDerivation.createMasterPrivateKey(seed.toByteArray()); DeterministicHierarchy dh = new DeterministicHierarchy(dk); DeterministicKey dk_acct = dh.get( ImmutableList.of( new ChildNumber(44,true), new ChildNumber(params.getBIP44CoinNumber(),true), new ChildNumber(account,true) ), true, true); String xpub = dk_acct.serializePubB58( org.bitcoinj.params.MainNetParams.get() ); return xpub; } public static AddressSpec getAddressSpec(NetworkParams params, String xpub, int change, int index) { DeterministicKey account_key = DeterministicKey.deserializeB58(xpub, org.bitcoinj.params.MainNetParams.get()); DeterministicHierarchy dh = new DeterministicHierarchy(account_key); DeterministicKey dk_addr = dh.get( ImmutableList.of( new ChildNumber(change,false), new ChildNumber(index,false) ), true, true); ByteString public_key = ByteString.copyFrom(dk_addr.getPubKey()); AddressSpec addr_spec = AddressUtil.getSimpleSpecForKey(public_key, SignatureUtil.SIG_TYPE_ECDSA_COMPRESSED); return addr_spec; } public static WalletKeyPair getKey(NetworkParams params, String seed_str, String pass, int account, int change, int index) { ByteString seed = decodeSeed(seed_str,pass); DeterministicKey dk = HDKeyDerivation.createMasterPrivateKey(seed.toByteArray()); DeterministicHierarchy dh = new DeterministicHierarchy(dk); ByteString seed_id = getSeedId(params, seed_str, pass, 0); DeterministicKey dk_addr = dh.get( ImmutableList.of( new ChildNumber(44,true), new ChildNumber(params.getBIP44CoinNumber(),true), new ChildNumber(account,true), new ChildNumber(change,false), new ChildNumber(index,false) ), true, true); ByteString priv_key = ByteString.copyFrom(dk_addr.getPrivKeyBytes()); BigInteger priv_bi = dk_addr.getPrivKey(); PrivateKey pk = null; try { ECPrivateKeySpec priv_spec = new ECPrivateKeySpec(priv_bi, KeyUtil.getECHDSpec()); KeyFactory kf = KeyFactory.getInstance("ECDSA", Globals.getCryptoProviderName()); pk = kf.generatePrivate(priv_spec); } catch(Exception e){throw new RuntimeException(e);} ByteString public_key = ByteString.copyFrom(dk_addr.getPubKey()); return WalletKeyPair.newBuilder() .setPublicKey(public_key) .setPrivateKey(ByteString.copyFrom(pk.getEncoded())) .setSignatureType(SignatureUtil.SIG_TYPE_ECDSA_COMPRESSED) .setSeedId(seed_id) .setHdPath(dk_addr.getPathAsString()) .setHdChange(change) .setHdIndex(index) .build(); } }