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

com.klaytn.caver.transaction.AbstractFeeDelegatedTransaction Maven / Gradle / Ivy

There is a newer version: 1.12.2-android
Show newest version
/*
 * Copyright 2020 The caver-java Authors
 *
 * Licensed under the Apache License, Version 2.0 (the “License”);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an “AS IS” BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.klaytn.caver.transaction;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.klaytn.caver.rpc.Klay;
import com.klaytn.caver.account.AccountKeyRoleBased;
import com.klaytn.caver.utils.Utils;
import com.klaytn.caver.wallet.keyring.AbstractKeyring;
import com.klaytn.caver.wallet.keyring.KeyringFactory;
import com.klaytn.caver.wallet.keyring.SignatureData;
import com.klaytn.caver.wallet.keyring.SingleKeyring;
import org.web3j.rlp.RlpEncoder;
import org.web3j.rlp.RlpList;
import org.web3j.rlp.RlpString;
import org.web3j.rlp.RlpType;
import org.web3j.utils.Numeric;

import java.io.IOException;
import java.math.BigInteger;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

/**
 * Abstract class that implements common logic for each fee delegated transaction type.
 */
abstract public class AbstractFeeDelegatedTransaction extends AbstractTransaction{

    /**
     * The address of fee payer.
     */
    String feePayer;

    /**
     * The fee payer's signatures.
     */
    List feePayerSignatures = new ArrayList<>();

    /**
     * Represent a AbstractFeeDelegatedTransaction builder
     * @param  An generic extends to AbstractFeeDelegatedTransaction.Builder
     */
    public static class Builder extends AbstractTransaction.Builder {
        String feePayer;
        private List feePayerSignatures = new ArrayList<>();

        public Builder(String type) {
            super(type);
        }

        public B setFeePayer(String feePayer) {
            this.feePayer = feePayer;
            return (B) this;
        }

        public B setFeePayerSignatures(List feePayerSignatures) {
            this.feePayerSignatures = feePayerSignatures;
            return (B) this;
        }

        public B setFeePayerSignatures(SignatureData data) {
            if(data == null) {
                data = SignatureData.getEmptySignature();
            }

            this.feePayerSignatures.add(data);
            return (B) this;
        }
    }

    /**
     * Create an AbstractFeeDelegatedTransaction instance
     * @param builder AbstractFeeDelegatedTransaction.Builder
     */
    public AbstractFeeDelegatedTransaction(Builder builder) {
        super(builder);
        setFeePayer(builder.feePayer);
        setFeePayerSignatures(builder.feePayerSignatures);
    }

    /**
     * Create an AbstractFeeDelegatedTransaction instance
     * @param klaytnCall Klay RPC instance
     * @param type Transaction's type string
     * @param from The address of the sender.
     * @param nonce A value used to uniquely identify a sender’s transaction.
     * @param gas The maximum amount of gas the transaction is allowed to use.
     * @param chainId Network ID
     * @param signatures A Signature list
     * @param feePayer The address of the fee payer.
     * @param feePayerSignatures The fee payers's signatures.
     */
    public AbstractFeeDelegatedTransaction(Klay klaytnCall, String type, String from, String nonce, String gas, String chainId, List signatures, String feePayer, List feePayerSignatures) {
        super(klaytnCall, type, from, nonce, gas, chainId, signatures);
        setFeePayer(feePayer);
        setFeePayerSignatures(feePayerSignatures);
    }

    /**
     * Signs to the transaction with a single private key as a fee payer.
     * It sets Hasher default value.
     *   - signer : TransactionHasher.getHashForFeePayerSignature()
     * @param keyString The private key string.
     * @return AbstractFeeDelegatedTransaction
     * @throws IOException
     */
    public AbstractFeeDelegatedTransaction signAsFeePayer(String keyString) throws IOException {
        SingleKeyring keyring = KeyringFactory.createFromPrivateKey(keyString);

        return signAsFeePayer(keyring, TransactionHasher::getHashForFeePayerSignature);
    }

    /**
     * Signs to the transaction with a single private key as a fee payer.
     * @param keyString The private key string.
     * @param hasher The function to get hash of transaction.
     * @return AbstractFeeDelegatedTransaction
     * @throws IOException
     */
    public AbstractFeeDelegatedTransaction signAsFeePayer(String keyString, Function hasher) throws IOException {
        SingleKeyring keyring = KeyringFactory.createFromPrivateKey(keyString);

        return signAsFeePayer(keyring, hasher);
    }

    /**
     * Sign the transaction as a fee payer using all private keys used as roleFeePayerKey in the Keyring instance.
     * It sets index and Hasher default value.
     *    - signer : TransactionHasher.getHashForFeePayerSignature()
     * @param keyring The Keyring instance.
     * @return AbstractFeeDelegatedTransaction
     * @throws IOException
     */
    public AbstractFeeDelegatedTransaction signAsFeePayer(AbstractKeyring keyring) throws IOException {
        return signAsFeePayer(keyring, TransactionHasher::getHashForFeePayerSignature);
    }

    /**
     * Sign the the transaction as a fee payer using a private key at the index among the private keys used as roleFeePayerKey in the Keyring instance.
     * @param keyring The Keyring instance.
     * @param index The index of private key to use in Keyring instance.
     * @return AbstractFeeDelegatedTransaction
     * @throws IOException
     */
    public AbstractFeeDelegatedTransaction signAsFeePayer(AbstractKeyring keyring, int index) throws IOException {
        return signAsFeePayer(keyring, index, TransactionHasher::getHashForFeePayerSignature);
    }

    /**
     * Sign the transaction as a fee payer using all private keys used as roleFeePayerKey in the Keyring instance.
     * @param keyring The Keyring instance.
     * @param hasher The function to get hash of transaction.
     * @return AbstractFeeDelegatedTransaction
     * @throws IOException
     */
    public AbstractFeeDelegatedTransaction signAsFeePayer(AbstractKeyring keyring, Function hasher) throws IOException {
        if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) {
            this.setFeePayer(keyring.getAddress());
        }

        if(!this.getFeePayer().toLowerCase().equals(keyring.getAddress().toLowerCase())) {
            throw new IllegalArgumentException("The feePayer address of the transaction is different with the address of the keyring to use.");
        }

        this.fillTransaction();
        int role = AccountKeyRoleBased.RoleGroup.FEE_PAYER.getIndex();

        String hash = hasher.apply(this);
        List sigList = keyring.sign(hash, Numeric.toBigInt(this.getChainId()).intValue(), role);

        this.appendFeePayerSignatures(sigList);

        return this;
    }

    /**
     * Sign the the transaction as a fee payer using a private key at the index among the private keys used as roleFeePayerKey in the Keyring instance.
     * @param keyring The Keyring instance.
     * @param index The index of private key to use in Keyring instance.
     * @param hasher The function to get hash of transaction.
     * @return AbstractFeeDelegatedTransaction
     * @throws IOException
     */
    public AbstractFeeDelegatedTransaction signAsFeePayer(AbstractKeyring keyring, int index, Function hasher) throws IOException {
        if(this.getFeePayer().equals("0x") || this.getFeePayer().equals(Utils.DEFAULT_ZERO_ADDRESS)) {
            this.setFeePayer(keyring.getAddress());
        }

        if(!this.getFeePayer().toLowerCase().equals(keyring.getAddress().toLowerCase())) {
            throw new IllegalArgumentException("The feePayer address of the transaction is different with the address of the keyring to use.");
        }

        this.fillTransaction();
        int role = AccountKeyRoleBased.RoleGroup.FEE_PAYER.getIndex();

        String hash = hasher.apply(this);
        SignatureData sigList = keyring.sign(hash, Numeric.toBigInt(this.getChainId()).intValue(), role, index);

        this.appendFeePayerSignatures(sigList);

        return this;
    }

    /**
     * Appends fee payer's signatures to the transaction.
     * @param signatureData SignatureData instance contains ECDSA signature data
     */
    public void appendFeePayerSignatures(SignatureData signatureData) {
        List feePayerSignatureList = new ArrayList<>();
        feePayerSignatureList.add(signatureData);

        appendFeePayerSignatures(feePayerSignatureList);
    }

    /**
     * Appends fee payer's signatures to the transaction.
     * @param signatureData List of SignatureData contains ECDSA signature data
     */
    public void appendFeePayerSignatures(List signatureData) {
        setFeePayerSignatures(signatureData);
    }

    /**
     * Returns a RLP-encoded transaction string for making fee payer's signature.
     * @return String
     */
    @JsonIgnore
    public String getRLPEncodingForFeePayerSignature() {
        byte[] txRLP = Numeric.hexStringToByteArray(getCommonRLPEncodingForSignature());

        List rlpTypeList = new ArrayList<>();
        rlpTypeList.add(RlpString.create(txRLP));
        rlpTypeList.add(RlpString.create(Numeric.hexStringToByteArray(this.getFeePayer())));
        rlpTypeList.add(RlpString.create(Numeric.toBigInt(this.getChainId())));
        rlpTypeList.add(RlpString.create(0));
        rlpTypeList.add(RlpString.create(0));
        byte[] encoded = RlpEncoder.encode(new RlpList(rlpTypeList));
        return Numeric.toHexString(encoded);
    }

    /**
     * Check equals txObj passed parameter and current instance.
     * @param txObj The AbstractFeeDelegatedTransaction Object to compare
     * @param checkSig Check whether signatures field is equal.
     * @return boolean
     */
    public boolean compareTxField(AbstractFeeDelegatedTransaction txObj, boolean checkSig) {
        if(!super.compareTxField(txObj, checkSig)) return false;

        if(!this.getFeePayer().toLowerCase().equals(txObj.getFeePayer().toLowerCase())) return false;

        if(checkSig) {
            List dataList = this.getFeePayerSignatures();

            if(dataList.size() != txObj.getFeePayerSignatures().size()) return false;

            for(int i=0; i< dataList.size(); i++) {
                if(!dataList.get(i).equals(txObj.getFeePayerSignatures().get(i))) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Recovers the public key strings from "feePayerSignatures" field in transaction object.

* If you want to derive an address from public key, please use {@link Utils#publicKeyToAddress(String)}. *

Example :
     * {@code
     * List publicKeys = tx.recoverFeePayerPublicKeys();
     * }
     * 
* @return List<String> */ public List recoverFeePayerPublicKeys() { try { if(Utils.isEmptySig(this.getFeePayerSignatures())) { throw new RuntimeException("Failed to recover public keys from feePayerSignatures: feePayerSignatures is empty."); } // For recover signature. We need to find chainId from signatures' v field. // The V value in Tx signatures is set by [parity value {0,1} + chainId * 2 + 35] // https://eips.ethereum.org/EIPS/eip-155 if(this.getChainId().equals("0x")) { BigInteger chainId = this.getFeePayerSignatures().get(0).getChainId(); setChainId(chainId); } String sigHash = TransactionHasher.getHashForFeePayerSignature(this); List publicKeyList = new ArrayList<>(); for(SignatureData signatureData : this.getFeePayerSignatures()) { if(Numeric.toBigInt(this.getChainId()).compareTo(signatureData.getChainId()) != 0) { throw new RuntimeException("Invalid Signature data : chain id is not matched."); } publicKeyList.add(Utils.recoverPublicKey(sigHash, signatureData, true)); } return publicKeyList; } catch(SignatureException e) { throw new RuntimeException(e); } } /** * Getter function for feePayer * @return String */ public String getFeePayer() { return feePayer; } /** * Setter function for feePayer * @param feePayer The address of fee payer. */ public void setFeePayer(String feePayer) { if(feePayer == null || feePayer.equals("0x")) { feePayer = Utils.DEFAULT_ZERO_ADDRESS; } if(!Utils.isAddress(feePayer)) { throw new IllegalArgumentException("Invalid address. : " + feePayer); } this.feePayer = feePayer; } /** * Getter function for feePayerSignatures * @return List */ @JsonIgnore public List getFeePayerSignatures() { return feePayerSignatures; } public void setFeePayerSignatures(List feePayerSignatures) { if(feePayerSignatures == null || feePayerSignatures.size() == 0) { feePayerSignatures = Arrays.asList(SignatureData.getEmptySignature()); } if(!Utils.isEmptySig(feePayerSignatures)) { if (feePayer.equals("0x") || feePayer.equals(Utils.DEFAULT_ZERO_ADDRESS)) { throw new IllegalArgumentException("feePayer is missing: feePayer must be defined with feePayerSignatures."); } } this.feePayerSignatures.addAll(feePayerSignatures); this.feePayerSignatures = refineSignature(this.getFeePayerSignatures()); } }