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.

1

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.