All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.neow3j.wallet.Account Maven / Gradle / Ivy

package io.neow3j.wallet;

import io.neow3j.constants.OpCode;
import io.neow3j.contract.ScriptHash;
import io.neow3j.crypto.ECKeyPair;
import io.neow3j.crypto.NEP2;
import io.neow3j.crypto.ScryptParams;
import io.neow3j.crypto.Sign;
import io.neow3j.crypto.WIF;
import io.neow3j.crypto.exceptions.CipherException;
import io.neow3j.crypto.exceptions.NEP2InvalidFormat;
import io.neow3j.crypto.exceptions.NEP2InvalidPassphrase;
import io.neow3j.crypto.transaction.RawVerificationScript;
import io.neow3j.model.types.ContractParameterType;
import io.neow3j.protocol.Neow3j;
import io.neow3j.protocol.core.methods.response.NeoGetNep5Balances;
import io.neow3j.protocol.core.methods.response.NeoGetUnspents;
import io.neow3j.protocol.exceptions.ErrorResponseException;
import io.neow3j.utils.Keys;
import io.neow3j.utils.Numeric;
import io.neow3j.wallet.Balances.AssetBalance;
import io.neow3j.wallet.exceptions.InsufficientFundsException;
import io.neow3j.wallet.nep6.NEP6Account;
import io.neow3j.wallet.nep6.NEP6Contract;
import io.neow3j.wallet.nep6.NEP6Contract.NEP6Parameter;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.IntStream;

import static io.neow3j.constants.OpCode.CHECKMULTISIG;

@SuppressWarnings("unchecked")
public class Account {

    private BigInteger privateKey;
    private BigInteger publicKey;
    private String address;
    private String encryptedPrivateKey;
    private String label;
    private boolean isDefault;
    private boolean isLocked;
    private NEP6Contract contract;
    private Balances balances;

    private Account() {
    }

    protected Account(Builder b) {
        this.label = b.label;
        this.privateKey = b.privateKey;
        this.publicKey = b.publicKey;
        this.isDefault = b.isDefault;
        this.isLocked = b.isLocked;
        this.address = b.address;
        this.encryptedPrivateKey = b.encryptedPrivateKey;
        this.contract = b.contract;
        this.balances = new Balances(this);
        this.tryAddVerificationScriptContract();
    }

    public String getAddress() {
        return address;
    }

    public ScriptHash getScriptHash() {
        return ScriptHash.fromAddress(address);
    }

    public ECKeyPair getECKeyPair() {
        if (privateKey != null && publicKey != null) {
            return new ECKeyPair(privateKey, publicKey);
        } else if (privateKey != null) {
            return ECKeyPair.create(privateKey);
        } else {
            return null;
        }
    }

    public BigInteger getPrivateKey() {
        return privateKey;
    }

    public BigInteger getPublicKey() {
        return publicKey;
    }

    public String getLabel() {
        return label;
    }

    public Boolean isDefault() {
        return isDefault;
    }

    void setIsDefault(boolean isDefault) {
        this.isDefault = isDefault;
    }

    public Boolean isLocked() {
        return isLocked;
    }

    public NEP6Contract getContract() {
        return contract;
    }

    public String getEncryptedPrivateKey() {
        return encryptedPrivateKey;
    }

    public Balances getBalances() {
        return balances;
    }

    /**
     * 

Gets the balance (the amount and a set of UTXOs) for the given asset id.

*
*

Note that updating the balance information via a call to a RPC node is left to the library * user. Call {@link Account#updateAssetBalances(Neow3j)} to have the most recent balance * information

* * @param assetId The id/hash of the asset. * @return the asset balance of this account. */ public AssetBalance getAssetBalance(String assetId) { return this.balances.getAssetBalance(assetId); } public void updateAssetBalances(Neow3j neow3j) throws IOException, ErrorResponseException { NeoGetUnspents response = neow3j.getUnspents(getAddress()).send(); response.throwOnError(); balances.updateAssetBalances(response.getUnspents()); } public void updateTokenBalances(Neow3j neow3j) throws IOException, ErrorResponseException { NeoGetNep5Balances response = neow3j.getNep5Balances(getAddress()).send(); response.throwOnError(); balances.updateTokenBalances(response.getBalances()); } /** *

Fetches a set of UTXOs from this account that fulfill the required asset amount.

*
*

Usually the UTXOs will not cover the amount exactly but cover a larger amount. Therefore * it is important to calculate the necessary change before using the UTXOs in a transaction.

* * @param assetId The asset needed. * @param amount The amount needed. * @param strategy The strategy with which to choose the UTXOs available on this account. * @return the list of UTXOs covering the required amount. * @throws IllegalStateException if this account does not have any balances, e.g. because they * have not been updated before. * @throws InsufficientFundsException if this account does does not possess enough UTXOs to * fulfill the required amount. */ public List getUtxosForAssetAmount(String assetId, BigDecimal amount, InputCalculationStrategy strategy) { if (getBalances() == null) { throw new IllegalStateException("Account does not have any asset balances. " + "Update account's asset balances first."); } if (!getBalances().hasAsset(assetId)) { throw new InsufficientFundsException("Account balance does not contain the asset " + "with ID " + assetId); } AssetBalance balance = getBalances().getAssetBalance(assetId); if (balance.getAmount().compareTo(amount) < 0) { throw new InsufficientFundsException("Needed " + amount + " but only found " + balance.getAmount() + " for asset with ID " + assetId); } return strategy.calculateInputs(balance.getUtxos(), amount); } /** * Decrypts this account's private key, according to the NEP-2 standard, if not already decrypted. * * @param password The passphrase used to decrypt this account's private key. * @param scryptParams The Scrypt parameters used for decryption. * @throws NEP2InvalidFormat throws if the encrypted NEP2 has an invalid format. * @throws CipherException throws if failed encrypt the created wallet. * @throws NEP2InvalidPassphrase throws if the passphrase is not valid. */ public void decryptPrivateKey(String password, ScryptParams scryptParams) throws NEP2InvalidFormat, CipherException, NEP2InvalidPassphrase { if (privateKey == null) { if (encryptedPrivateKey == null) { throw new IllegalStateException("The account does not hold an encrypted private key."); } ECKeyPair ecKeyPair = NEP2.decrypt(password, encryptedPrivateKey, scryptParams); privateKey = ecKeyPair.getPrivateKey(); publicKey = ecKeyPair.getPublicKey(); tryAddVerificationScriptContract(); } } /** * Encrypts this account's private key, according to the NEP-2 standard, if not already encrypted. * * @param password The passphrase used to encrypt this account's private key. * @param scryptParams The Scrypt parameters used for encryption. * @throws CipherException throws if failed encrypt the created wallet. */ public void encryptPrivateKey(String password, ScryptParams scryptParams) throws CipherException { if (encryptedPrivateKey == null) { if (privateKey == null) { throw new IllegalStateException("The account does not hold a private key."); } this.encryptedPrivateKey = NEP2.encrypt(password, getECKeyPair(), scryptParams); // TODO: 2019-07-14 Guil: // Is it the safest way of overwriting a variable on the JVM? // I don't think so. ;-) this.privateKey = null; } } public boolean isMultiSig() { // TODO Claude 20.06.19: // Even if the contract script is not empty this might be a multi-sig account. Additionally, // the script in the contract could be something else than a verification script. // Clarify if it makes sense to enforce that the contract's script must be a verification // script and that it must be available (especially for multi-sig accounts). if (contract != null && contract.getScript() != null && contract.getScript().length() >= 2) { String script = contract.getScript(); return script.substring(script.length() - 2).equals(OpCode.toHexString(CHECKMULTISIG)); } return false; } public NEP6Account toNEP6Account() { if (encryptedPrivateKey == null) { throw new IllegalStateException("Private key is not encrypted. Encrypt private key first."); } return new NEP6Account(getAddress(), label, isDefault, isLocked, encryptedPrivateKey, contract, null); } private void tryAddVerificationScriptContract() { if (contract == null || contract.getScript() == null) { if (publicKey != null) { byte[] scriptBytes = RawVerificationScript.fromPublicKey(publicKey).getScript(); String scriptHex = Numeric.toHexStringNoPrefix(scriptBytes); NEP6Parameter param = new NEP6Parameter("signature", ContractParameterType.SIGNATURE); contract = new NEP6Contract(scriptHex, Collections.singletonList(param), false); } } } /** * Creates a multi-sig account builder from the given public keys. * Mind that the ordering of the keys is important for later usage of the account. * * @param publicKeys The public keys from which to derive the multi-sig account. * @param signatureThreshold The number of signatures needed when using this account for signing * transactions. * @return the multi-sig account builder; */ public static Builder fromMultiSigKeys(List publicKeys, int signatureThreshold) { // TODO: 2019-07-14 Guil: // Review this method and the functionality it provides. // Maybe we should get rid of this. Builder b = new Builder(); b.address = Keys.getMultiSigAddress(signatureThreshold, publicKeys); b.label = b.address; byte[] script = RawVerificationScript.fromPublicKeys(signatureThreshold, publicKeys).getScript(); String scriptHexString = Numeric.toHexStringNoPrefix(script); List parameters = new ArrayList<>(); IntStream.range(0, publicKeys.size()).forEachOrdered(i -> parameters.add(new NEP6Parameter("signature" + i, ContractParameterType.SIGNATURE))); b.contract = new NEP6Contract(scriptHexString, parameters, false); return b; } public static Builder fromWIF(String wif) { Builder b = new Builder(); b.privateKey = Numeric.toBigInt(WIF.getPrivateKeyFromWIF(wif)); b.publicKey = Sign.publicKeyFromPrivate(b.privateKey); b.address = Keys.getAddress(b.publicKey); b.label = b.address; return b; } public static Builder fromNewECKeyPair() { try { return fromECKeyPair(ECKeyPair.createEcKeyPair()); } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchProviderException e) { throw new RuntimeException("Failed to create a new EC key pair.", e); } } public static Builder fromECKeyPair(ECKeyPair ecKeyPair) { Builder b = new Builder(); b.privateKey = ecKeyPair.getPrivateKey(); b.publicKey = ecKeyPair.getPublicKey(); b.address = ecKeyPair.getAddress(); b.label = b.address; return b; } public static Builder fromNEP6Account(NEP6Account nep6Acct) { Builder b = new Builder(); b.address = nep6Acct.getAddress(); b.label = nep6Acct.getLabel(); b.encryptedPrivateKey = nep6Acct.getKey(); b.isLocked = nep6Acct.getLock(); b.isDefault = nep6Acct.getDefault(); b.contract = nep6Acct.getContract(); return b; } public static Builder fromAddress(String address) { Builder b = new Builder(); b.address = address; b.label = address; return b; } /** * Creates a new generic account with a fresh key pair. * * @return the new account. */ public static Account createAccount() { return fromNewECKeyPair().build(); } public static class Builder> { String label; BigInteger privateKey; BigInteger publicKey; boolean isDefault; boolean isLocked; String address; String encryptedPrivateKey; NEP6Contract contract; protected Builder() { isDefault = false; isLocked = false; } public B label(String label) { this.label = label; return (B) this; } public B isDefault(boolean isDefault) { this.isDefault = isDefault; return (B) this; } public B isLocked(boolean isLocked) { this.isLocked = isLocked; return (B) this; } public T build() { return (T) new Account(this); } } @Override public String toString() { return "Account{" + "privateKey=" + privateKey + ", publicKey=" + publicKey + ", address='" + address + '\'' + ", encryptedPrivateKey='" + encryptedPrivateKey + '\'' + ", label='" + label + '\'' + ", isDefault=" + isDefault + ", isLocked=" + isLocked + ", contract=" + contract + ", balances=" + balances + '}'; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy