bitcoinjs / bitcoinjs-lib

A javascript Bitcoin library for node.js and browsers.
MIT License
5.68k stars 2.1k forks source link

Transaction has unexpected data #1690

Closed marcianovc closed 3 years ago

marcianovc commented 3 years ago

/root/node_modules/bitcoinjs-lib/src/transaction.js:91 throw new Error('Transaction has unexpected data'); ^

Error: Transaction has unexpected data at Function.fromBuffer (/root/node_modules/bitcoinjs-lib/src/transaction.js:91:13) at addNonWitnessTxCache (/root/node_modules/bitcoinjs-lib/src/psbt.js:1207:40) at Psbt.addInput (/root/node_modules/bitcoinjs-lib/src/psbt.js:206:7) at Object. (/root/help_github.js:32:6) at Module._compile (internal/modules/cjs/loader.js:999:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10) at Module.load (internal/modules/cjs/loader.js:863:32) at Function.Module._load (internal/modules/cjs/loader.js:708:14) at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12) at internal/main/run_main_module.js:17:47

RawTransaction https://blockbook.martexcoin.org/tx/ac89ad0ef3ec8ac8bd120eb86d6372ff2375dd61a13839647f1c32a98fee87f5

var bitcoin = require('bitcoinjs-lib');
var bip39 = require('bip39')

const martex = {
    messagePrefix: '\x18MarteX Signed Message:\n',
    bip32: {
       public: 0x0488b21e,
       private: 0x0488ade4
    },
    networkMagic: 0x2d3fa2f5,
    pubKeyHash: 0x32,
    scriptHash: 0x05,
    wif: 0xb2
}

const mnemonic = 'relax intact edit invite split foot taste proud army young tone color bright control ramp raduke worth gold pulse trust jungle clarify oppose six'
const seed = bip39.mnemonicToSeedSync(mnemonic)
const hdMaster = bitcoin.bip32.fromSeed(seed, martex)
const keyPair = hdMaster.derivePath("m/44'/180'/0'/0/0")
const privateKeyBuffer = Buffer.from(keyPair.privateKey)
const privateKey = privateKeyBuffer.toString('hex')
const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: martex })
console.log('Master Priv ->', keyPair.toBase58())
console.log('privateKey  -> ', privateKey.toString("base64"))
console.log('publicKey  -> ', address.toString("base64"))

const txid = '01000000000000000470ea51b0481fb057ee98ccc0814aeb531292a5120536c5910c2e36714b2f7f820100000049483045022100a15ffd35f22c96bc20ea8dc170be2cc86eecc14d7c133efc5f6f5bc4ad133876022027d288a281662590ed76aa883aac63dcd3c3de0a55e4db19ddf1ab14ee49df4101ffffffffc49f206cd6074e61969cd339f4abdff1c5105b089dceb427446efb8cda6516c0030000008a473044022070a1323e67a83ce9cfa1c686074ea5f5872533e3fd1dc0afff5f9a99df975f9a02207811165f5d48540f90404b8ca2450729dae96e812361f5ca721ce4742e444abb0141047e86214867ff3ad73d1c2bc52a1643b104245bf8ed54c62c6a65f8f37393a0f17911a301f36a07e672e901f1489b10728582a7fca273a200c7643c6e29b1cce5ffffffffd384a38dd2c160f42188ac7f1e18a0d6f88830429ccabf66f571f7f998a8a426030000008a473044022023d11376f45b8f552d6a0857976d581050e56726e72c9fed279bc14eef4f451f022045ca09e781acffbc9dc11c8ef42aaada43028228f108b0fa0ad4016ec3674f140141047e86214867ff3ad73d1c2bc52a1643b104245bf8ed54c62c6a65f8f37393a0f17911a301f36a07e672e901f1489b10728582a7fca273a200c7643c6e29b1cce5ffffffffed47817403084dcc0e84ec833fd159faffac69e46e28783cfb8dd65ebe4e6a2c030000008a47304402204dd20f3ba747ced86ee91e9f572f54cbfb02dbc63ba62c3b5def700969345ffa02201c6f640116b2e8fb87c721d75b26cb44c58a108de104b0d84208d542f6eef0100141047e86214867ff3ad73d1c2bc52a1643b104245bf8ed54c62c6a65f8f37393a0f17911a301f36a07e672e901f1489b10728582a7fca273a200c7643c6e29b1cce5ffffffff02a6420f00000000001976a914b2b449f10ac4527ac71bf7a97e31de16c0bc844d88ac00a3e111000000001976a914067e413c5dc68df7e515f71c811fd0452272c25a88ac00000000';
var Psbt = new bitcoin.Psbt({ martex })
Psbt.addInput({
    hash: 'ac89ad0ef3ec8ac8bd120eb86d6372ff2375dd61a13839647f1c32a98fee87f5',
    index: 1,
    nonWitnessUtxo: Buffer.from(txid, 'hex'),   // <---- error on this line  
    network: martex,
  });
Psbt.addOutput({
    address: 'M8VVXvLMzcHYjwQyVzQvUj5qiudkVhzrKu',
    value: 1,
    network: martex,
  });
Psbt.signInput(0, keyPair)
Psbt.finalizeInput(0)
Psbt.extractTransaction(true);
console.log(Psbt.toHex());
junderw commented 3 years ago

MarteXcoin has extra data in the serialized transaction called nTime Bitcoin does not have that data, so the Transaction parser for Bitcoin chokes on the nTime value.

It would be easy to fork bitcoinjs-lib to support MarteXcoin, but unfortunately, this library is Bitcoin-only, and the network interface is merely a convenience feature for other altcoins that have the same transaction structure as Bitcoin to use.

However, we do not officially "support" these coins, and if they add / remove some feature we will not change to match them.

MarteXcoin does not have the same transaction structure as Bitcoin, so it is not possible to use bitcoinjs-lib without forking.

I think this should work, but I haven't tested. From a quick look at the source for your coin, it only puts time in the serialization and never uses it for sighash.

Also, it seems like Segwit is not supported for your coin... so just don't use any segwit functions.

I won't help any further, you can figure it out on your own.

Good luck!

diff --git a/src/transaction.js b/src/transaction.js
index b47cfe90..dd420190 100644
--- a/src/transaction.js
+++ b/src/transaction.js
@@ -41,6 +41,7 @@ function isOutput(out) {
 class Transaction {
   constructor() {
     this.version = 1;
+    this.time = 0;
     this.locktime = 0;
     this.ins = [];
     this.outs = [];
@@ -49,6 +50,7 @@ class Transaction {
     const bufferReader = new bufferutils_1.BufferReader(buffer);
     const tx = new Transaction();
     tx.version = bufferReader.readInt32();
+    tx.time = bufferReader.readInt32();
     const marker = bufferReader.readUInt8();
     const flag = bufferReader.readUInt8();
     let hasWitnesses = false;
@@ -153,10 +155,14 @@ class Transaction {
   virtualSize() {
     return Math.ceil(this.weight() / 4);
   }
-  byteLength(_ALLOW_WITNESS = true) {
+  byteLength(_ALLOW_WITNESS = true, _INCLUDE_TIME = true) {
     const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses();
+    let base = hasWitnesses ? 10 : 8;
+    if (_INCLUDE_TIME) {
+      base += 4;
+    }
     return (
-      (hasWitnesses ? 10 : 8) +
+      base +
       varuint.encodingLength(this.ins.length) +
       varuint.encodingLength(this.outs.length) +
       this.ins.reduce((sum, input) => {
@@ -252,9 +258,9 @@ class Transaction {
       txTmp.ins[inIndex].script = ourScript;
     }
     // serialize and hash
-    const buffer = Buffer.allocUnsafe(txTmp.byteLength(false) + 4);
+    const buffer = Buffer.allocUnsafe(txTmp.byteLength(false, false) + 4);
     buffer.writeInt32LE(hashType, buffer.length - 4);
-    txTmp.__toBuffer(buffer, 0, false);
+    txTmp.__toBuffer(buffer, 0, false, false);
     return bcrypto.hash256(buffer);
   }
   hashForWitnessV0(inIndex, prevOutScript, value, hashType) {
@@ -352,13 +358,21 @@ class Transaction {
     typeforce(types.tuple(types.Number, [types.Buffer]), arguments);
     this.ins[index].witness = witness;
   }
-  __toBuffer(buffer, initialOffset, _ALLOW_WITNESS = false) {
+  __toBuffer(
+    buffer,
+    initialOffset,
+    _ALLOW_WITNESS = false,
+    _WRITE_TIME = true,
+  ) {
     if (!buffer) buffer = Buffer.allocUnsafe(this.byteLength(_ALLOW_WITNESS));
     const bufferWriter = new bufferutils_1.BufferWriter(
       buffer,
       initialOffset || 0,
     );
     bufferWriter.writeInt32(this.version);
+    if (_WRITE_TIME) {
+      bufferWriter.writeInt32(this.time);
+    }
     const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses();
     if (hasWitnesses) {
       bufferWriter.writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER);
diff --git a/ts_src/transaction.ts b/ts_src/transaction.ts
index 561ee8ae..061b912a 100644
--- a/ts_src/transaction.ts
+++ b/ts_src/transaction.ts
@@ -71,6 +71,7 @@ export class Transaction {

     const tx = new Transaction();
     tx.version = bufferReader.readInt32();
+    tx.time = bufferReader.readInt32();

     const marker = bufferReader.readUInt8();
     const flag = bufferReader.readUInt8();
@@ -136,6 +137,7 @@ export class Transaction {
   }

   version: number = 1;
+  time: number = 0;
   locktime: number = 0;
   ins: Input[] = [];
   outs: Output[] = [];
@@ -206,11 +208,19 @@ export class Transaction {
     return Math.ceil(this.weight() / 4);
   }

-  byteLength(_ALLOW_WITNESS: boolean = true): number {
+  byteLength(
+    _ALLOW_WITNESS: boolean = true,
+    _INCLUDE_TIME: boolean = true,
+  ): number {
     const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses();

+    let base = hasWitnesses ? 10 : 8;
+    if (_INCLUDE_TIME) {
+      base += 4;
+    }
+
     return (
-      (hasWitnesses ? 10 : 8) +
+      base +
       varuint.encodingLength(this.ins.length) +
       varuint.encodingLength(this.outs.length) +
       this.ins.reduce((sum, input) => {
@@ -329,9 +339,11 @@ export class Transaction {
     }

     // serialize and hash
-    const buffer: Buffer = Buffer.allocUnsafe(txTmp.byteLength(false) + 4);
+    const buffer: Buffer = Buffer.allocUnsafe(
+      txTmp.byteLength(false, false) + 4,
+    );
     buffer.writeInt32LE(hashType, buffer.length - 4);
-    txTmp.__toBuffer(buffer, 0, false);
+    txTmp.__toBuffer(buffer, 0, false, false);

     return bcrypto.hash256(buffer);
   }
@@ -465,6 +477,7 @@ export class Transaction {
     buffer?: Buffer,
     initialOffset?: number,
     _ALLOW_WITNESS: boolean = false,
+    _WRITE_TIME: boolean = true,
   ): Buffer {
     if (!buffer)
       buffer = Buffer.allocUnsafe(this.byteLength(_ALLOW_WITNESS)) as Buffer;
@@ -472,6 +485,9 @@ export class Transaction {
     const bufferWriter = new BufferWriter(buffer, initialOffset || 0);

     bufferWriter.writeInt32(this.version);
+    if (_WRITE_TIME) {
+      bufferWriter.writeInt32(this.time);
+    }

     const hasWitnesses = _ALLOW_WITNESS && this.hasWitnesses();

diff --git a/types/transaction.d.ts b/types/transaction.d.ts
index 6846ea52..6a504e85 100644
--- a/types/transaction.d.ts
+++ b/types/transaction.d.ts
@@ -21,6 +21,7 @@ export declare class Transaction {
     static fromHex(hex: string): Transaction;
     static isCoinbaseHash(buffer: Buffer): boolean;
     version: number;
+    time: number;
     locktime: number;
     ins: Input[];
     outs: Output[];
@@ -30,7 +31,7 @@ export declare class Transaction {
     hasWitnesses(): boolean;
     weight(): number;
     virtualSize(): number;
-    byteLength(_ALLOW_WITNESS?: boolean): number;
+    byteLength(_ALLOW_WITNESS?: boolean, _INCLUDE_TIME?: boolean): number;
     clone(): Transaction;
     /**
      * Hash transaction for signing a specific input.
marcianovc commented 3 years ago

thanks @junderw