package org.stellar.sdk; import com.google.common.base.Objects; import net.i2p.crypto.eddsa.EdDSAEngine; import net.i2p.crypto.eddsa.EdDSAPrivateKey; import net.i2p.crypto.eddsa.EdDSAPublicKey; import net.i2p.crypto.eddsa.KeyPairGenerator; import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec; import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec; import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; import org.stellar.sdk.xdr.*; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.Signature; import java.security.SignatureException; import java.util.Arrays; import static com.google.common.base.Preconditions.checkNotNull; /** * Holds a Stellar keypair. */ public class KeyPair { private static final EdDSANamedCurveSpec ed25519 = EdDSANamedCurveTable.ED_25519_CURVE_SPEC; private final EdDSAPublicKey mPublicKey; private final EdDSAPrivateKey mPrivateKey; /** * Creates a new KeyPair without a private key. Useful to simply verify a signature from a * given public address. * @param publicKey */ public KeyPair(EdDSAPublicKey publicKey) { this(publicKey, null); } /** * Creates a new KeyPair from the given public and private keys. * @param publicKey * @param privateKey */ public KeyPair(EdDSAPublicKey publicKey, EdDSAPrivateKey privateKey) { mPublicKey = checkNotNull(publicKey, "publicKey cannot be null"); mPrivateKey = privateKey; } /** * Returns true if this Keypair is capable of signing */ public boolean canSign() { return mPrivateKey != null; } /** * Creates a new Stellar KeyPair from a strkey encoded Stellar secret seed. * @param seed Char array containing strkey encoded Stellar secret seed. * @return {@link KeyPair} */ public static KeyPair fromSecretSeed(char[] seed) { byte[] decoded = StrKey.decodeStellarSecretSeed(seed); KeyPair keypair = fromSecretSeed(decoded); Arrays.fill(decoded, (byte) 0); return keypair; } /** * <strong>Insecure</strong> Creates a new Stellar KeyPair from a strkey encoded Stellar secret seed. * This method is <u>insecure</u>. Use only if you are aware of security implications. * @see <a href="http://docs.oracle.com/javase/1.5.0/docs/guide/security/jce/JCERefGuide.html#PBEEx" target="_blank">Using Password-Based Encryption</a> * @param seed The strkey encoded Stellar secret seed. * @return {@link KeyPair} */ public static KeyPair fromSecretSeed(String seed) { char[] charSeed = seed.toCharArray(); byte[] decoded = StrKey.decodeStellarSecretSeed(charSeed); KeyPair keypair = fromSecretSeed(decoded); Arrays.fill(charSeed, ' '); return keypair; } /** * Creates a new Stellar keypair from a raw 32 byte secret seed. * @param seed The 32 byte secret seed. * @return {@link KeyPair} */ public static KeyPair fromSecretSeed(byte[] seed) { EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(seed, ed25519); EdDSAPublicKeySpec publicKeySpec = new EdDSAPublicKeySpec(privKeySpec.getA().toByteArray(), ed25519); return new KeyPair(new EdDSAPublicKey(publicKeySpec), new EdDSAPrivateKey(privKeySpec)); } /** * Creates a new Stellar KeyPair from a strkey encoded Stellar account ID. * @param accountId The strkey encoded Stellar account ID. * @return {@link KeyPair} */ public static KeyPair fromAccountId(String accountId) { byte[] decoded = StrKey.decodeStellarAccountId(accountId); return fromPublicKey(decoded); } /** * Creates a new Stellar keypair from a 32 byte address. * @param publicKey The 32 byte public key. * @return {@link KeyPair} */ public static KeyPair fromPublicKey(byte[] publicKey) { EdDSAPublicKeySpec publicKeySpec; try { publicKeySpec = new EdDSAPublicKeySpec(publicKey, ed25519); } catch (IllegalArgumentException e) { throw new RuntimeException("Public key is invalid"); } return new KeyPair(new EdDSAPublicKey(publicKeySpec)); } /** * Finds the KeyPair for the path m/44'/148'/accountNumber' using the method described in * <a href="https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0005.md">SEP-0005</a>. * * @param bip39Seed The output of BIP0039 * @param accountNumber The number of the account * @return KeyPair with secret */ public static KeyPair fromBip39Seed(byte[] bip39Seed, int accountNumber) { try { return KeyPair.fromSecretSeed(SLIP10.deriveEd25519PrivateKey(bip39Seed, 44, 148, accountNumber)); } catch (Exception e) { throw new RuntimeException(e); } } /** * Generates a random Stellar keypair. * @return a random Stellar keypair. */ public static KeyPair random() { java.security.KeyPair keypair = new KeyPairGenerator().generateKeyPair(); return new KeyPair((EdDSAPublicKey) keypair.getPublic(), (EdDSAPrivateKey) keypair.getPrivate()); } /** * Returns the human readable account ID encoded in strkey. */ public String getAccountId() { return StrKey.encodeStellarAccountId(mPublicKey.getAbyte()); } /** * Returns the human readable secret seed encoded in strkey. */ public char[] getSecretSeed() { return StrKey.encodeStellarSecretSeed(mPrivateKey.getSeed()); } public byte[] getPublicKey() { return mPublicKey.getAbyte(); } public SignatureHint getSignatureHint() { try { ByteArrayOutputStream publicKeyBytesStream = new ByteArrayOutputStream(); XdrDataOutputStream xdrOutputStream = new XdrDataOutputStream(publicKeyBytesStream); PublicKey.encode(xdrOutputStream, this.getXdrPublicKey()); byte[] publicKeyBytes = publicKeyBytesStream.toByteArray(); byte[] signatureHintBytes = Arrays.copyOfRange(publicKeyBytes, publicKeyBytes.length - 4, publicKeyBytes.length); SignatureHint signatureHint = new SignatureHint(); signatureHint.setSignatureHint(signatureHintBytes); return signatureHint; } catch (IOException e) { throw new AssertionError(e); } } public PublicKey getXdrPublicKey() { PublicKey publicKey = new PublicKey(); publicKey.setDiscriminant(PublicKeyType.PUBLIC_KEY_TYPE_ED25519); Uint256 uint256 = new Uint256(); uint256.setUint256(getPublicKey()); publicKey.setEd25519(uint256); return publicKey; } public SignerKey getXdrSignerKey() { SignerKey signerKey = new SignerKey(); signerKey.setDiscriminant(SignerKeyType.SIGNER_KEY_TYPE_ED25519); Uint256 uint256 = new Uint256(); uint256.setUint256(getPublicKey()); signerKey.setEd25519(uint256); return signerKey; } public static KeyPair fromXdrPublicKey(PublicKey key) { return KeyPair.fromPublicKey(key.getEd25519().getUint256()); } public static KeyPair fromXdrSignerKey(SignerKey key) { return KeyPair.fromPublicKey(key.getEd25519().getUint256()); } /** * Sign the provided data with the keypair's private key. * @param data The data to sign. * @return signed bytes, null if the private key for this keypair is null. */ public byte[] sign(byte[] data) { if (mPrivateKey == null) { throw new RuntimeException("KeyPair does not contain secret key. Use KeyPair.fromSecretSeed method to create a new KeyPair with a secret key."); } try { Signature sgr = new EdDSAEngine(MessageDigest.getInstance("SHA-512")); sgr.initSign(mPrivateKey); sgr.update(data); return sgr.sign(); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } /** * Sign the provided data with the keypair's private key and returns {@link DecoratedSignature}. * @param data */ public DecoratedSignature signDecorated(byte[] data) { byte[] signatureBytes = this.sign(data); org.stellar.sdk.xdr.Signature signature = new org.stellar.sdk.xdr.Signature(); signature.setSignature(signatureBytes); DecoratedSignature decoratedSignature = new DecoratedSignature(); decoratedSignature.setHint(this.getSignatureHint()); decoratedSignature.setSignature(signature); return decoratedSignature; } /** * Verify the provided data and signature match this keypair's public key. * @param data The data that was signed. * @param signature The signature. * @return True if they match, false otherwise. * @throws RuntimeException */ public boolean verify(byte[] data, byte[] signature) { try { Signature sgr = new EdDSAEngine(MessageDigest.getInstance("SHA-512")); sgr.initVerify(mPublicKey); sgr.update(data); return sgr.verify(signature); } catch (SignatureException e) { return false; } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } public int hashCode() { return Objects.hashCode(this.mPrivateKey, this.mPublicKey); } @Override public boolean equals(Object object) { if (object == null || !(object instanceof KeyPair)) { return false; } KeyPair other = (KeyPair) object; return this.mPrivateKey.equals(other.mPrivateKey) && this.mPublicKey.equals(other.mPublicKey); } }