0

Consider the following transaction expressed as hex:

010000000001019F92908527985B1ED14E7F6127684808523750076AB82A03A6A5A50107AC40190000000000FFFFFFFF02446C1E0000000000160014ABFA07FB45DE4B7187360F0B5E4014991666ACCB40420F0000000000160014DFFE4A0C857A04597E0DCB446DB284DD0C068E8602473044022051F99A39749B925E32BF1ED0573ED4CCFC6965F92CD0F016F644C8CF17CA57E102202FCC06C7C26A7E5F71A2DF3353BDE494561E4A6394955DE9C825D08DB573BB1D0121024B2512FFD4AA888E0E0D358083770A4A45093952EE68F3753651932849F008EC00000000

If I paste it into online transaction decoders/validators like the ones here and here, everything looks solid.

But submitting them to any public pushTX API generates an error. The problem is that the error messages are so vague (e.g. "invalid transaction") I'm not even sure where to look.

BlockCypher gives the following error:

"Error running script for input 0 referencing 1940ac0701a5a5a6032ab86a0750375208486827617f4ed11e5b98278590929f at 0: Script was NOT verified successfully."

I've researched this particular error and people occasionally encounter it when a signature is bad. But I've checked the signature countless times. It's valid. In fact, pasting the transaction here actually shows a little checkmark next to the "signed" icon indicating a proper signature.

Further, the decoders all show the correct inputs, outputs, amounts, etc. So the serialized data doesn't appear to be corrupt or otherwise unreadable.

What could I possibly be doing wrong? I've spent the entire day researching it but can't come up with anything.

  • What software did you use to create this transaction? – Andrew Chow Jul 16 at 3:01
1

It's valid. In fact, pasting the transaction here actually shows a little checkmark next to the "signed" icon indicating a proper signature.

It is not valid. That website did not validate anything. It just checked whether there was something in the scriptWitness field, and if there was, it was marked as signed. That checkmark does not mean that it is valid.

Your signature is simply incorrect. There's no other way to put it. Running it through my Bitcoin Core node gives

64: non-mandatory-script-verify-flag (Signature must be zero for failed CHECK(MULTI)SIG operation)

which is the typical invalid signature error.

Double check that you are creating the sighash correctly. Problems like this usually are the result of incorrectly serializing the transaction for signing.

  • Thanks, that error message you provided at least points me in the right direction. I'm using this .NET component called BouncyCastle which has been faithfully doing the encryption/signing work for me for years, absolutely flawlessly. I 'upgraded' the component recently and things are kind of breaking down. I've been able to find workarounds for most of the problems but this sighash/signature issue has me stumped. I'll keep at it. Thanks again – Festus Martingale Jul 16 at 3:04
  • @FestusMartingale: It would probably help if you add your code to the question. – Nate Eldredge Jul 16 at 4:51
1

I suspect it's considered tacky in programmer circles to post an answer to one's own question, but in this case my mistake was rather dangerous so I'd like to share the details with y'all.

RECAP:

My question stated that the public 'push tx' APIs weren't giving viable error messages after refusing my hex-formatted raw transactions. The answer from Mr. Chow indicated that the problem was a signature error, which he received after attempting to push my malformed TX through his personal node. That pointed me in the right direction.

His suggestion was that perhaps I wasn't serializing my sighash properly, since that often leads to the error message he referenced. But the code that formulates that sighash has been working fine for years so I figured the problem was elsewhere. Indeed, it was. The short story is that my logic used to derive a public key from a private key, which had worked flawlessly forever, was now failing after an upgrade of the BouncyCastle .NET encryption component.

Here's an excerpt of my code that converts a private key to public key:

public class BTCUtils2{
    public static byte[] DerivePublicKey(byte[] privKey,bool isCompr){
        ECPoint pubPt=ComputePublicECPoint(privKey);

        //byte[] xCoord = pubPt.X.ToBigInteger().ToByteArrayUnsigned(),     //2015: worked properly in with BouncyCastle 1.7.4114.6375
        //       yCoord = pubPt.Y.ToBigInteger().ToByteArrayUnsigned();

        //byte[] xCoord = pubPt.XCoord.GetEncoded(),                        //2019-07-01: upgraded to 1.8.5; generates bad data
        //       yCoord = pubPt.YCoord.GetEncoded();

        ECPublicKeyParameters publicParams = new ECPublicKeyParameters(pubPt,ECParams); //2019-07-16; proper usage of 1.8.5
        byte[] xCoord=publicParams.Q.XCoord.GetEncoded(),
               yCoord=publicParams.Q.YCoord.GetEncoded();

        if(xCoord.Length!=32 || yCoord.Length!=32){throw new ApplicationException("SANITY CHECK: Expected 32 bytes for X/Y coords");}
        byte[] bytes=new byte[isCompr?33:65];                               //public key consists of a one byte prefix (0x04=uncompress,0x02=compressed w\ y=even, 0x03=compressed w\ y=odd) prior to the payload
        if(isCompr){
            bytes[0]=((yCoord[31] & 0x01)==0)?(byte)0x02:(byte)0x03;        //compressed; set prefix based on whether Y is even or odd
            Array.Copy(xCoord,0,bytes,1,32);                                //x coord
        }
        else{
            bytes[0]=0x04;                                                  //uncompressed; set prefix to 0x04
            Array.Copy(xCoord,0,bytes,1,32);                                //x coord
            Array.Copy(yCoord,0,bytes,33,32);                               //y coord
        }
        return bytes;
    }
    private static ECPoint ComputePublicECPoint(byte[] privKey){return ECParams.G.Multiply(new BigInteger(1,privKey));}
    private static readonly ECDomainParameters  ECParams=null;
    static BTCUtils2(){
        X9ECParameters crv = Org.BouncyCastle.Asn1.Sec.SecNamedCurves.GetByName("secp256k1");
        ECParams=new ECDomainParameters(crv.Curve, crv.G, crv.N, crv.H);
    }
}

The problem occurs at the top, where I'm converting a private key, expressed as a byte array, to a public key, expressed in terms of its x/y coords. See my comments. The 2015 code worked just fine for years with the 2011 edition of BouncyCastle.

On July of 2019-07-01, I had to upgrade to version 1.8.5 of bouncy castle. The code no longer compiled. I figured hey, they must have renamed a few things or whatever so I applied the two lines denoted as 2019-07-01, figuring I was accomplishing the same thing. MAJOR MISTAKE. I'm not sure exactly what those function calls return, but they aren't the x/y coord of the public key. They're something else entirely.

As you can imagine, having a public key that doesn't properly correspond to your private key leads to all sorts of problems including malformed Bitcoin addresses, signing errors, etc.

Today, I replaced those bad lines with the ones labeled 2019-07-16. That did the trick. I verified that those lines were in fact generating a valid public key. Transactions now submit without error.

Hope this is value to someone.

Best,

Festus

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.