package jstellarapi.keys; import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.Security; import jstellarapi.core.StellarPrivateKey; import jstellarapi.core.StellarPublicGeneratorAddress; import jstellarapi.core.StellarPublicKey; import jstellarapi.core.StellarSeedAddress; import org.bouncycastle.asn1.sec.SECNamedCurves; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.crypto.params.ECDomainParameters; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.util.Arrays; public class StellarDeterministicKeyGenerator { public static ECDomainParameters SECP256K1_PARAMS; protected byte[] seedBytes; static { // ECGenParameterSpec ecSpec = new ECGenParameterSpec("SECp256k1"); Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); X9ECParameters params = SECNamedCurves.getByName("secp256k1"); SECP256K1_PARAMS = new ECDomainParameters(params.getCurve(), params.getG(), params.getN(), params.getH()); } public StellarDeterministicKeyGenerator(StellarSeedAddress secret) { this(secret.getBytes()); } public StellarDeterministicKeyGenerator(byte[] bytesSeed) { if(bytesSeed.length!=32){ throw new RuntimeException("The seed size should be 256 bit, was "+bytesSeed.length*8); } this.seedBytes = bytesSeed; } public static byte[] halfSHA512(byte[] bytesToHash) { try { MessageDigest sha512Digest = MessageDigest.getInstance("SHA-512", "BC"); byte [] bytesHash = sha512Digest.digest(bytesToHash); byte[] first256BitsOfHash = Arrays.copyOf(bytesHash, 32); return first256BitsOfHash; } catch (Exception e) { throw new RuntimeException(e); } } protected byte[] getPrivateRootKeyBytes() { for(int seq=0;; seq++){ byte[] seqBytes = ByteBuffer.allocate(4).putInt(seq).array(); byte[] seedAndSeqBytes = Arrays.concatenate(seedBytes, seqBytes); byte[] privateGeneratorBytes = halfSHA512(seedAndSeqBytes); BigInteger privateGeneratorBI = new BigInteger(1, privateGeneratorBytes); if(privateGeneratorBI.compareTo(SECP256K1_PARAMS.getN()) ==-1){ return privateGeneratorBytes; //We return the byte[] instead of the BigInteger because the toArray of BigInt allocates only the minimal number of bytes to represent the value. } } } //PublicGenerator is also known as PublicRootKey protected ECPoint getPublicGeneratorPoint() { byte[] privateGeneratorBytes = getPrivateRootKeyBytes(); ECPoint publicGenerator = new StellarPrivateKey(privateGeneratorBytes).getPublicKey().getPublicPoint(); return publicGenerator; } public StellarPrivateKey getAccountPrivateKey(int accountNumber) { BigInteger privateRootKeyBI = new BigInteger(1, getPrivateRootKeyBytes()); //TODO factor out the common part with the public key ECPoint publicGeneratorPoint = getPublicGeneratorPoint(); byte[] publicGeneratorBytes = publicGeneratorPoint.getEncoded(); byte[] accountNumberBytes = ByteBuffer.allocate(4).putInt(accountNumber).array(); BigInteger pubGenSeqSubSeqHashBI; for(int subSequence=0;; subSequence++){ byte[] subSequenceBytes = ByteBuffer.allocate(4).putInt(subSequence).array(); //FIXME Avoid pointless concatenation or arrays everywhere byte[] pubGenAccountSubSeqBytes = Arrays.concatenate(publicGeneratorBytes, accountNumberBytes, subSequenceBytes); byte[] publicGeneratorAccountSeqHashBytes = halfSHA512(pubGenAccountSubSeqBytes); pubGenSeqSubSeqHashBI = new BigInteger(1, publicGeneratorAccountSeqHashBytes); if(pubGenSeqSubSeqHashBI.compareTo(SECP256K1_PARAMS.getN()) ==-1 && !pubGenSeqSubSeqHashBI.equals(BigInteger.ZERO)){ break; } } BigInteger privateKeyForAccount = privateRootKeyBI.add(pubGenSeqSubSeqHashBI).mod(SECP256K1_PARAMS.getN()); return new StellarPrivateKey(privateKeyForAccount); } public StellarPublicKey getAccountPublicKey(int accountNumber) { //FIXME This method should be able to generate public addresses from the publicGenerator only (Deterministic watch only addresses) ECPoint publicGeneratorPoint = getPublicGeneratorPoint(); byte[] publicGeneratorBytes = publicGeneratorPoint.getEncoded(); byte[] accountNumberBytes = ByteBuffer.allocate(4).putInt(accountNumber).array(); byte[] publicGeneratorAccountSeqHashBytes; for(int subSequence=0;; subSequence++){ byte[] subSequenceBytes = ByteBuffer.allocate(4).putInt(subSequence).array(); byte[] pubGenAccountSubSeqBytes = Arrays.concatenate(publicGeneratorBytes, accountNumberBytes, subSequenceBytes); publicGeneratorAccountSeqHashBytes = halfSHA512(pubGenAccountSubSeqBytes); BigInteger pubGenSeqSubSeqHashBI = new BigInteger(1, publicGeneratorAccountSeqHashBytes); if(pubGenSeqSubSeqHashBI.compareTo(SECP256K1_PARAMS.getN()) ==-1){ break; } } ECPoint temporaryPublicPoint = new StellarPrivateKey(publicGeneratorAccountSeqHashBytes).getPublicKey().getPublicPoint(); ECPoint accountPublicKeyPoint = publicGeneratorPoint.add(temporaryPublicPoint); byte[] publicKeyBytes = accountPublicKeyPoint.getEncoded(); return new StellarPublicKey(publicKeyBytes); } public StellarPublicGeneratorAddress getPublicGeneratorFamily() throws Exception { byte[] publicGeneratorBytes = getPublicGeneratorPoint().getEncoded(); return new StellarPublicGeneratorAddress(publicGeneratorBytes); } }