1

So i'm working with the altcoin denarius which has the following raw TX structure. This is a transaction I am try to sign:

Original:
01000000a73a7b5d01068ee1e392668191ce0ae4337074e7a384be3c5d72bd45b7f7ce6fa2006258110100000000ffffffff0208c29400000000001976a914ebb93cbd9952b3941e0415509b301dfd1cb84b0388ac90d00300000000001976a914b1acab9b97a1d9f35898b9d74fe11619e636ac3d88ac00000000

VERSION: 01000000
TIME: a73a7b5d

INPUT COUNT: 01
INPUT [0] PREV TXID: 068ee1e392668191ce0ae4337074e7a384be3c5d72bd45b7f7ce6fa200625811
INPUT [0] PREV OUTPUT INDEX: 01000000
INPUT [0] SCRIPT LENGTH: 00
INPUT [0] SEQUENCE NUMBER: ffffffff

NUMBER OF OUTPUTS:02
OUTPUT [0] VALUE: 08c2940000000000
OUTPUT [0] PUBLIC KEY SCRIPT LENGTH: 19 (25 in decimal)
OUTPUT [0] PUBLIC KEY SCRIPT: 
  76 (OP_DUP)
  a9 (OP_HASH160)
  14 (bytes to push)
  SCRIPT HASH: ebb93cbd9952b3941e0415509b301dfd1cb84b03 (pubkey hash)
  88 (OP_EQUALVERIFY)
  ac (OP_CHECKSIG)

OUTPUT [1] VALUE: 90d0030000000000
OUTPUT [1] PUBLIC KEY SCRIPT LENGTH: 19 (25 in decimal)
OUTPUT [1] PUBLIC KEY SCRIPT:
  76 (OP_DUP)
  a9 (OP_HASH160)
  14 (bytes to push)
  SCRIPT HASH: b1acab9b97a1d9f35898b9d74fe11619e636ac3d (pubkey hash)
  88 (OP_EQUALVERIFY)
  ac (OP_CHECKSIG)

LOCKTIME: 00000000

I have various methods to convert bytes to hex strings and a few others:

    public static String hex2smallEndian(String hex){
        String[] dat = split(hex, 2);
        String swapped = "";
        for(int i = dat.length - 1; i >= 0; i--){
            swapped += dat[i];
        }

        return swapped;
    }

    public static String[] split(String str, int count){
        String[] dat = new String[str.length() / count];
        for(int i = 0; i < str.length(); i+=count){
            dat[i / count] = str.substring(i, i + count);
        }

        return dat;
    }
    private static byte[] hex2bytes(String s) {
        int len = s.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
                    + Character.digit(s.charAt(i + 1), 16));
        }
        return data;
    }

    public static String bytes2hex(byte[] bytes) {
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }

Here is my RawTransactionBuilder class:

import java.math.BigDecimal;
import java.security.Signature;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.ArrayList;

import static com.xemplarsoft.Vars.TX_FEE;
import static com.xemplarsoft.libs.crypto.KeyManager.bytes2hex;
import static com.xemplarsoft.libs.crypto.KeyManager.getPubFromPriv;
import static com.xemplarsoft.libs.util.Sha256.hex2bytes;

public class RawTransactionBuilder {
    public static final String VERSION = "01000000";
    public static final String SEQUENCE_NUMBER = "ffffffff";
    public static final String LOCKTIME = "00000000";

    public final int inputCount, outputCount;
    public final RawOutput[] outputs;
    public final RawInput[] inputs;
    public RawTransactionBuilder(ArrayList<UTXO> utxos, String from, String to, BigDecimal amount){
        inputCount = utxos.size();
        outputCount = 2;
        outputs = new RawOutput[outputCount];
        inputs = new RawInput[inputCount];

        BigDecimal total = new BigDecimal(0);
        for(int i = 0; i < utxos.size(); i++){
            UTXO u = utxos.get(i);

            total = total.add(u.getAmount());
            inputs[i] = new RawInput(u.getTxId(), u.getScriptPubKey(), u.getVout());
        }

        BigDecimal change = total.subtract(amount).subtract(TX_FEE);
        outputs[1] = new RawOutput(from, change);
        outputs[0] = new RawOutput(to, amount);
    }

    public String buildUnsignedRawTX(){
        long mills = System.currentTimeMillis();
        String ret = VERSION; // VERSION

        ret += hex2smallEndian(String.format("%1$08x", mills / 1000L)); // TIME
        ret += hex2smallEndian(String.format("%1$02x", inputCount)); // INPUT COUNT

        for(int i = 0; i < inputCount; i++){
            RawInput in = inputs[i];
            ret += in.getPrevTXHexLE(); // INPUT[i] PREV TXID
            ret += in.getPrevOutIndexLE(); //INPUT[i] PREV OUTPUT INDEX
            ret += "00"; //INPUT[i] SCRIPT LENGTH
            ret += SEQUENCE_NUMBER; //INPUT[i] SEQUENCE NUMBER
        }

        ret += hex2smallEndian(String.format("%1$02x", outputCount)); // OUTPUT COUNT

        for(int i = 0; i < outputCount; i++){
            RawOutput out = outputs[i];
            ret += out.getValueHexLE(); // OUTPUT[i] VALUE
            ret += out.getScriptLengthHexLE(); //OUTPUT[i] PUBLIC KEY SCRIPT LENGTH
            ret += out.getScriptPubKey(); //OUTPUT[i] PUBLIC KEY SCRIPT
        }

        return ret += LOCKTIME;
    }

    private String buildSignedRawTX(){
        long mills = System.currentTimeMillis();
        String ret = VERSION; // VERSION

        ret += hex2smallEndian(String.format("%1$08x", mills / 1000L)); // TIME
        ret += hex2smallEndian(String.format("%1$02x", inputCount)); // INPUT COUNT

        for(int i = 0; i < inputCount; i++){
            RawInput in = inputs[i];
            ret += in.getPrevTXHexLE(); // INPUT[i] PREV TXID
            ret += in.getPrevOutIndexLE(); //INPUT[i] PREV OUTPUT INDEX
            ret += in.getScriptSigLength(); //INPUT[i] SCRIPT LENGTH
            ret += in.getScriptSig(); //INPUT[i] SCRIPT SIG
            ret += SEQUENCE_NUMBER; //INPUT[i] SEQUENCE NUMBER
        }

        ret += hex2smallEndian(String.format("%1$02x", outputCount)); // OUTPUT COUNT

        for(int i = 0; i < outputCount; i++){
            RawOutput out = outputs[i];
            ret += out.getValueHexLE(); // OUTPUT[i] VALUE
            ret += out.getScriptLengthHexLE(); //OUTPUT[i] PUBLIC KEY SCRIPT LENGTH
            ret += out.getScriptPubKey(); //OUTPUT[i] PUBLIC KEY SCRIPT
        }

        return ret += LOCKTIME;
    }

    public String buildUnsignedRawTXForSiging(int inputIndex){
        long mills = System.currentTimeMillis();
        String ret = VERSION; // VERSION

        ret += hex2smallEndian(String.format("%1$08x", mills / 1000L)); // TIME
        ret += hex2smallEndian(String.format("%1$02x", inputCount)); // INPUT COUNT

        for(int i = 0; i < inputCount; i++){
            RawInput in = inputs[i];
            ret += in.getPrevTXHexLE(); // INPUT[i] PREV TXID
            ret += in.getPrevOutIndexLE(); //INPUT[i] PREV OUTPUT INDEX
            if(i == inputIndex){
                ret += in.getScriptPubKeyLength(); //INPUT[i] SCRIPT LENGTH
                ret += in.getScriptPubKey(); //INPUT[i] TEMP SCRIPT SIG
            } else {
                ret += "00"; //INPUT[i] SCRIPT LENGTH
            }
            ret += SEQUENCE_NUMBER; //INPUT[i] SEQUENCE NUMBER
        }

        ret += hex2smallEndian(String.format("%1$02x", outputCount)); // OUTPUT COUNT

        for(int i = 0; i < outputCount; i++){
            RawOutput out = outputs[i];
            ret += out.getValueHexLE(); // OUTPUT[i] VALUE
            ret += out.getScriptLengthHexLE(); //OUTPUT[i] PUBLIC KEY SCRIPT LENGTH
            ret += out.getScriptPubKey(); //OUTPUT[i] PUBLIC KEY SCRIPT
        }

        ret += LOCKTIME;
        ret += "01000000";

        return ret;
    }

    public String signTX(ECPrivateKey priv){
        Signature sig = null;
        ECPublicKey pub = null;
        try{
            pub = getPubFromPriv(priv);
            for(int i = 0; i < inputs.length; i++) {
                sig = Signature.getInstance("SHA256withECDSA");
                sig.initSign(priv);
                String hash = hex2smallEndian(bytes2hex(Sha256.getHash(Sha256.getHash(buildUnsignedRawTXForSiging(i)))));
                sig.update(hex2bytes(hash));
                inputs[i].setScriptSig(sig.sign(), pub);
            }
        } catch (Exception e){
            e.printStackTrace();
            return null;
        }

        return buildSignedRawTX();
    }

My RawInput class:

public class RawInput {
    private final String prevTXID, scriptPubKey;
    private final long prevOutIndex;

    private String scriptSig;

    public RawInput(String prevTX, String scriptPubKey, long prevOutIndex){
        this.prevTXID = prevTX;
        this.prevOutIndex = prevOutIndex;
        this.scriptPubKey = scriptPubKey;
    }

    public String getPrevTXHexLE(){
        return hex2smallEndian(prevTXID);
    }

    public String getPrevOutIndexLE(){
        return hex2smallEndian(String.format("%1$08x", prevOutIndex));
    }

    public String getScriptPubKey(){
        System.out.println("Script Pub Key: " + scriptPubKey);
        return scriptPubKey;
    }

    public String getScriptPubKeyLength(){
        return String.format("%1$02x", scriptPubKey.length() / 2);
    }

    public void setScriptSig(byte[] sig, ECPublicKey pub){
        String signature = bytes2hex(sig) + "01";

        ECPoint pt = pub.getW();
        String sx = adjustTo64(pt.getAffineX().toString(16));
        String sy = adjustTo64(pt.getAffineY().toString(16));

        String pubkey = "1E" + sx + sy;

        String sigLength = String.format("%1$02x", signature.length() / 2);
        String pubLength = String.format("%1$02x", pubkey.length() / 2);

        System.out.println("PUBKEY: " + convertPubToAddress(pubkey));

        this.scriptSig = sigLength + signature + pubLength + pubkey;
    }

    public String getScriptSigLength(){
        return String.format("%1$02x", scriptSig.length() / 2);
    }

    public String getScriptSig(){
        return scriptSig;
    }
}

And my payment method group that is called when a payment is requested:

//Payment Method Group
    private String to, from, narration;
    private BigDecimal amt, total;
    public void makePayment(String to, BigDecimal amount, String narration, String from){
        this.to = to;
        this.from = from;
        this.amt = amount;
        this.total = amount.add(TX_FEE);
        this.narration = narration;

        cli.createRawTXList(from);
    }
    private void makePayment(ArrayList<UTXO> utxos){
        if(utxos == null){
            this.to = null;
            this.from = null;
            this.amt = null;
            this.total = null;
            this.narration = null;
            return;
        }

        ArrayList<UTXO> required = new ArrayList<>();
        ArrayList<UTXOverview> requiredOverview = new ArrayList<>();
        BigDecimal soFar = new BigDecimal(0);
        boolean needsMore = true;
        int index = 0;

        while(needsMore){
            UTXO current = utxos.get(index);
            soFar = soFar.add(current.getAmount());
            required.add(current);
            requiredOverview.add(current.asOverview());

            if(soFar.compareTo(total) >= 0){
                needsMore = false;
            }
            index++;

            if(index >= utxos.size()) needsMore = false;
        }

        String WIF = "EXAMPLE_PRIV_KEY";
        ECPrivateKey priv = convertWIFtoECPrivateKey(WIF);
        System.out.println("WIF: " + convertPrivToWIF(adjustTo64(priv.getS().toString(16)).toUpperCase()));

        RawTransactionBuilder raw = new RawTransactionBuilder(required, from, to, amt);
        System.out.println("RAWTXBUILDER unsigned: " + raw.buildUnsignedRawTX());
        System.out.println("RAWTXBUILDER txid: " + bytes2hex(Sha256.getHash(Sha256.getHash(raw.buildUnsignedRawTX()))));
        try {
            System.out.println("RAWTXBUILDER signed: " + raw.signTX(priv));
        } catch (Exception e){
            e.printStackTrace();
        }
        this.to = null;
        this.amt = null;
        this.total = null;
        this.narration = null;
    }

My problem is that whenever I run the above code, with sendrawtransaction I get TX rejected (code -22) which poins to an error of some sort. I've tried flipping the transaction hash to little endian, and leaving it as it generates. Signing the hash as a raw hex string and as raw bytes. I know that my convert functions work as I have verified that the proper WIF and Address are recreated. I'm not sure where my issue is.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.