AergoKey.java
/*
* @copyright defined in LICENSE.txt
*/
package hera.key;
import static hera.util.IoUtils.from;
import static hera.util.Sha256Utils.digest;
import static org.slf4j.LoggerFactory.getLogger;
import hera.annotation.ApiAudience;
import hera.annotation.ApiStability;
import hera.api.encode.Decoder;
import hera.api.encode.Encoder;
import hera.api.model.AccountAddress;
import hera.api.model.BytesValue;
import hera.api.model.EncryptedPrivateKey;
import hera.api.model.Hash;
import hera.api.model.RawTransaction;
import hera.api.model.Signature;
import hera.api.model.Transaction;
import hera.api.model.TxHash;
import hera.exception.HerajException;
import hera.spec.resolver.AddressResolver;
import hera.spec.resolver.EncryptedPrivateKeyResolver;
import hera.spec.resolver.SignatureResolver;
import hera.spec.resolver.TransactionHashResolver;
import hera.util.NumberUtils;
import hera.util.pki.ECDSAKey;
import hera.util.pki.ECDSAKeyGenerator;
import hera.util.pki.ECDSASignature;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.PublicKey;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.slf4j.Logger;
@ApiAudience.Public
@ApiStability.Unstable
@EqualsAndHashCode(exclude = {"logger", "verifier"})
public class AergoKey implements KeyPair, Signer, MessageSigner {
/**
* Create a key pair with encoded encrypted private key and password.
*
* @param encodedEncryptedPrivateKey base58 with checksum encoded encrypted private key
* @param password password to decrypt
* @return key instance
*/
public static AergoKey of(final String encodedEncryptedPrivateKey, final String password) {
return new AergoKey(encodedEncryptedPrivateKey, password);
}
/**
* Create a key pair with encrypted private key and password.
*
* @param encryptedPrivateKey encrypted private key
* @param password password to decrypt
* @return key instance
*/
public static AergoKey of(final EncryptedPrivateKey encryptedPrivateKey, final String password) {
return new AergoKey(encryptedPrivateKey, password);
}
protected final transient Logger logger = getLogger(getClass());
protected final AergoSignVerifier verifier = new AergoSignVerifier();
protected final ECDSAKey ecdsakey;
@Getter
protected final AccountAddress address;
/**
* AergoKey constructor.
*
* @param encodedEncryptedPrivateKey base58 with checksum encoded encrypted private key
* @param password password to decrypt
*/
public AergoKey(final String encodedEncryptedPrivateKey, final String password) {
this(new EncryptedPrivateKey(encodedEncryptedPrivateKey), password);
}
/**
* AergoKey constructor.
*
* @param encryptedPrivateKey encrypted private key
* @param password password to decrypt
*/
public AergoKey(final EncryptedPrivateKey encryptedPrivateKey, final String password) {
try {
final BytesValue decryptedBytes =
EncryptedPrivateKeyResolver.decrypt(encryptedPrivateKey, password);
final byte[] rawPrivateKey = decryptedBytes.getValue();
this.ecdsakey = new ECDSAKeyGenerator().create(new BigInteger(1, rawPrivateKey));
this.address = AddressResolver.deriveAddress(ecdsakey.getPublicKey());
} catch (final Exception e) {
throw new HerajException(e);
}
}
/**
* AergoKey constructor.
*
* @param ecdsakey keypair
*/
public AergoKey(final ECDSAKey ecdsakey) {
this.ecdsakey = ecdsakey;
this.address = AddressResolver.deriveAddress(ecdsakey.getPublicKey());
}
@Override
public PrivateKey getPrivateKey() {
return ecdsakey.getPrivateKey();
}
@Override
public PublicKey getPublicKey() {
return ecdsakey.getPublicKey();
}
@Override
public AccountAddress getPrincipal() {
return getAddress();
}
@Override
public Transaction sign(final RawTransaction rawTransaction) {
try {
logger.debug("Sign raw transaction: {}", rawTransaction);
final TxHash withoutSignature = TransactionHashResolver.calculateHash(rawTransaction);
final ECDSASignature ecdsaSignature =
ecdsakey.sign(withoutSignature.getBytesValue().getValue());
final Signature signature =
SignatureResolver.serialize(ecdsaSignature, ecdsakey.getParams().getN());
logger.trace("Raw signature: {}", ecdsaSignature);
logger.trace("Serialized signature: {}", signature);
final TxHash withSignature = TransactionHashResolver.calculateHash(rawTransaction, signature);
final Transaction transaction = Transaction.newBuilder()
.rawTransaction(rawTransaction)
.signature(signature)
.hash(withSignature)
.build();
return transaction;
} catch (HerajException e) {
throw e;
} catch (Exception e) {
throw new HerajException(e);
}
}
@Override
public String signMessage(final String message) {
return signMessage(message, Encoder.Base64);
}
@Override
public String signMessage(final String message, final Encoder encoder) {
try {
final Signature signature = signMessage(new BytesValue(message.getBytes()));
final byte[] rawSignature = signature.getSign().getValue();
return from(encoder.encode(new ByteArrayInputStream(rawSignature)));
} catch (HerajException e) {
throw e;
} catch (Exception e) {
throw new HerajException(e);
}
}
@Override
public Signature signMessage(final BytesValue message) {
try {
logger.debug("Sign to message: {}", message);
final Hash hash = new Hash(new BytesValue(digest(message.getValue())));
logger.debug("Hashed message: {}", hash);
final ECDSASignature ecdsaSignature = ecdsakey.sign(hash.getBytesValue().getValue());
final Signature signature =
SignatureResolver.serialize(ecdsaSignature, ecdsakey.getParams().getN());
logger.trace("Serialized signature: {}", signature);
return signature;
} catch (Exception e) {
throw new HerajException(e);
}
}
/**
* Check if {@code Transaction} is valid.
*
* @param transaction transaction to verify
* @return if valid
*/
public boolean verify(final Transaction transaction) {
return verifier.verify(transaction);
}
/**
* Check if {@code base64EncodedSignature} is valid for current {@code accountAddress} and
* {@code message}. It hashes {@code message} and verify hashed one.
*
* @param message a message
* @param base64EncodedSignature a base64 encoded signature
* @return if valid
*/
public boolean verifyMessage(final String message, final String base64EncodedSignature) {
return verifier.verifyMessage(getAddress(), message, base64EncodedSignature);
}
/**
* Check if {@code base64EncodedSignature} is valid for current {@code accountAddress} and
* {@code message}. It hashes {@code message} and verify hashed one.
*
* @param message a message
* @param encodedSignature an encoded signature
* @param decoder a decoder to decode encoded signature
*
* @return if valid
*/
public boolean verifyMessage(String message, String encodedSignature, Decoder decoder) {
return verifier.verifyMessage(getAddress(), message, encodedSignature, decoder);
}
/**
* Check if {@code signature} is valid for current {@code accountAddress} and {@code message}. It
* hashes {@code message} and verify hashed one.
*
* @param message a message
* @param signature signature
* @return if valid
*/
public boolean verifyMessage(final BytesValue message, final Signature signature) {
return verifier.verifyMessage(getAddress(), message, signature);
}
/**
* Check if {@code signature} is valid for {@code hash} and current address.
*
* @param hashedMessage a hashed message
* @param signature signature to verify
* @return if valid
*/
public boolean verifyMessage(final Hash hashedMessage, final Signature signature) {
return verifier.verifyMessage(getAddress(), hashedMessage, signature);
}
/**
* Return encrypted private key.
*
* @param password encrypt key
* @return encrypted key
*/
public EncryptedPrivateKey export(final String password) {
try {
final BytesValue privateKeyBytes = new BytesValue(getRawPrivateKey());
return EncryptedPrivateKeyResolver.encrypt(privateKeyBytes, password);
} catch (Exception e) {
throw new HerajException(e);
}
}
protected byte[] getRawPrivateKey() {
final org.bouncycastle.jce.interfaces.ECPrivateKey ecPrivateKey =
(org.bouncycastle.jce.interfaces.ECPrivateKey) getPrivateKey();
final BigInteger d = ecPrivateKey.getD();
return NumberUtils.positiveToByteArray(d);
}
@Override
public String toString() {
return String.format("AergoKey(address=%s)", getAddress());
}
}