UNPKG

@secux/app-btc

Version:
18 lines (15 loc) 13 kB
"use strict"; /*! Copyright 2022 SecuX Technology Inc Copyright Chen Wei-En Copyright Wu Tsung-Yu 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. */Object.defineProperty(exports,"__esModule",{value:!0}),exports.varSliceSize=exports.Transaction=void 0;const hash_js_1=require("hash.js"),varuint=require("varuint-bitcoin"),bufferutils_1=require("./bufferutils"),Script=require("./script"),coindef_1=require("./coindef"),utility_1=require("@secux/utility"),logger=null===utility_1.Logger||void 0===utility_1.Logger?void 0:utility_1.Logger.child({id:"transaction"}),ZERO=Buffer.from("0000000000000000000000000000000000000000000000000000000000000000","hex"),ONE=Buffer.from("0000000000000000000000000000000000000000000000000000000000000001","hex"),EMPTY=Buffer.alloc(0),VALUE_UINT64_MAX=Buffer.from("ffffffffffffffff","hex"),BLANK_OUTPUT={script:EMPTY,valueBuffer:VALUE_UINT64_MAX,value:0};class Transaction{constructor(){this.ins=[],this.outs=[],this.version=2,this.locktime=0}static fromBuffer(buffer){const bufferReader=new bufferutils_1.BufferReader(buffer),tx=new Transaction;tx.version=bufferReader.readInt32();const marker=bufferReader.readUInt8(),flag=bufferReader.readUInt8();let hasWitnesses=!1;marker===Transaction.ADVANCED_TRANSACTION_MARKER&&flag===Transaction.ADVANCED_TRANSACTION_FLAG?hasWitnesses=!0:bufferReader.offset-=2;const vinLen=bufferReader.readVarInt();for(let i=0;i<vinLen;++i)tx.ins.push({hash:bufferReader.readSlice(32),index:bufferReader.readUInt32(),script:bufferReader.readVarSlice(),sequence:bufferReader.readUInt32(),witness:[]});const voutLen=bufferReader.readVarInt();for(let i=0;i<voutLen;++i)tx.outs.push({value:bufferReader.readUInt64(),script:bufferReader.readVarSlice()});if(hasWitnesses){for(let i=0;i<vinLen;++i)tx.ins[i].witness=bufferReader.readVector();if(!tx.hasWitnesses())throw new Error("Transaction has superfluous witness data")}if(tx.locktime=bufferReader.readUInt32(),bufferReader.offset!==buffer.length)throw new Error("Transaction has unexpected data");return tx}static dataForSignature(trans,inIndex,prevOutScript,hashType){if(inIndex>=trans.ins.length)return ONE;const ourScript=Script.compile(Script.decompile(prevOutScript).filter((x=>x!==coindef_1.OPCODES.OP_CODESEPARATOR))),txTmp=trans.clone();if((31&hashType)===Transaction.SIGHASH_NONE)txTmp.outs=[],txTmp.ins.forEach(((input,i)=>{i!==inIndex&&(input.sequence=0)}));else if((31&hashType)===Transaction.SIGHASH_SINGLE){if(inIndex>=trans.outs.length)return ONE;txTmp.outs.length=inIndex+1;for(let i=0;i<inIndex;i++)txTmp.outs[i]=BLANK_OUTPUT;txTmp.ins.forEach(((input,y)=>{y!==inIndex&&(input.sequence=0)}))}hashType&Transaction.SIGHASH_ANYONECANPAY?(txTmp.ins=[txTmp.ins[inIndex]],txTmp.ins[0].script=ourScript):(txTmp.ins.forEach((input=>{input.script=EMPTY})),txTmp.ins[inIndex].script=ourScript);const buffer=Buffer.allocUnsafe(txTmp.byteLength(!1)+4);return buffer.writeInt32LE(hashType,buffer.length-4),txTmp.toBuffer(buffer,0,!1),buffer}static dataForWitnessV0(trans,inIndex,prevOutScript,value,hashType){let bufferWriter,tbuffer=Buffer.from([]),hashOutputs=ZERO,hashPrevouts=ZERO,hashSequence=ZERO;if(null==logger||logger.debug("begin dataForWitnessV0"),null==logger||logger.debug(`hashType: 0x${hashType.toString(16)}`),hashType&Transaction.SIGHASH_ANYONECANPAY||(tbuffer=Buffer.allocUnsafe(36*trans.ins.length),bufferWriter=new bufferutils_1.BufferWriter(tbuffer,0),trans.ins.forEach((txIn=>{bufferWriter.writeSlice(txIn.hash),bufferWriter.writeUInt32(txIn.index)})),hashPrevouts=hash256(tbuffer)),hashType&Transaction.SIGHASH_ANYONECANPAY||(31&hashType)===Transaction.SIGHASH_SINGLE||(31&hashType)===Transaction.SIGHASH_NONE||(tbuffer=Buffer.allocUnsafe(4*trans.ins.length),bufferWriter=new bufferutils_1.BufferWriter(tbuffer,0),trans.ins.forEach((txIn=>{bufferWriter.writeUInt32(txIn.sequence)})),hashSequence=hash256(tbuffer)),(31&hashType)!==Transaction.SIGHASH_SINGLE&&(31&hashType)!==Transaction.SIGHASH_NONE){const txOutsSize=trans.outs.reduce(((sum,output)=>sum+8+varSliceSize(output.script)),0);tbuffer=Buffer.allocUnsafe(txOutsSize),bufferWriter=new bufferutils_1.BufferWriter(tbuffer,0),trans.outs.forEach((out=>{bufferWriter.writeUInt64(out.value),bufferWriter.writeVarSlice(out.script)})),null==logger||logger.debug(`outputs: ${tbuffer.toString("hex")}`),hashOutputs=hash256(tbuffer)}else if((31&hashType)===Transaction.SIGHASH_SINGLE&&inIndex<trans.outs.length){const output=trans.outs[inIndex];tbuffer=Buffer.allocUnsafe(8+varSliceSize(output.script)),bufferWriter=new bufferutils_1.BufferWriter(tbuffer,0),bufferWriter.writeUInt64(output.value),bufferWriter.writeVarSlice(output.script),null==logger||logger.debug(`single output: ${tbuffer.toString("hex")}`),hashOutputs=hash256(tbuffer)}tbuffer=Buffer.allocUnsafe(156+varSliceSize(prevOutScript)),bufferWriter=new bufferutils_1.BufferWriter(tbuffer,0);const input=trans.ins[inIndex];return bufferWriter.writeUInt32(trans.version),bufferWriter.writeSlice(hashPrevouts),bufferWriter.writeSlice(hashSequence),bufferWriter.writeSlice(input.hash),bufferWriter.writeUInt32(input.index),bufferWriter.writeVarSlice(prevOutScript),bufferWriter.writeUInt64(value),bufferWriter.writeUInt32(input.sequence),bufferWriter.writeSlice(hashOutputs),bufferWriter.writeUInt32(trans.locktime),bufferWriter.writeUInt32(hashType),null==logger||logger.debug("end dataForWitnessV0"),tbuffer}static dataForWitnessV1(trans,inIndex,prevOutScripts,values,hashType,leafHash,annex){if(values.length!==trans.ins.length||prevOutScripts.length!==trans.ins.length)throw new Error("Must supply prevout script and value for all inputs");const outputType=hashType===Transaction.SIGHASH_DEFAULT?Transaction.SIGHASH_ALL:hashType&Transaction.SIGHASH_OUTPUT_MASK,isAnyoneCanPay=(hashType&Transaction.SIGHASH_INPUT_MASK)===Transaction.SIGHASH_ANYONECANPAY,isNone=outputType===Transaction.SIGHASH_NONE,isSingle=outputType===Transaction.SIGHASH_SINGLE;let hashPrevouts=EMPTY,hashAmounts=EMPTY,hashScriptPubKeys=EMPTY,hashSequences=EMPTY,hashOutputs=EMPTY;if(null==logger||logger.debug("begin dataForWitnessV1"),null==logger||logger.debug(`hashType: 0x${hashType.toString(16)}`),!isAnyoneCanPay){let buf=Buffer.alloc(36*trans.ins.length),bufferWriter=new bufferutils_1.BufferWriter(buf);trans.ins.forEach((txIn=>{bufferWriter.writeSlice(txIn.hash),bufferWriter.writeUInt32(txIn.index)})),hashPrevouts=_sha256(buf),buf=Buffer.alloc(8*trans.ins.length),bufferWriter=new bufferutils_1.BufferWriter(buf),values.forEach((value=>bufferWriter.writeUInt64(value))),hashAmounts=_sha256(buf),buf=Buffer.alloc(prevOutScripts.map(varSliceSize).reduce(((a,b)=>a+b))),bufferWriter=new bufferutils_1.BufferWriter(buf),prevOutScripts.forEach((prevOutScript=>bufferWriter.writeVarSlice(prevOutScript))),hashScriptPubKeys=_sha256(buf),buf=Buffer.alloc(4*trans.ins.length),bufferWriter=new bufferutils_1.BufferWriter(buf),trans.ins.forEach((txIn=>bufferWriter.writeUInt32(txIn.sequence))),hashSequences=_sha256(buf)}if(isNone||isSingle){if(isSingle&&inIndex<trans.outs.length){const output=trans.outs[inIndex],buf=Buffer.alloc(8+varSliceSize(output.script)),bufferWriter=new bufferutils_1.BufferWriter(buf);bufferWriter.writeUInt64(output.value),bufferWriter.writeVarSlice(output.script),null==logger||logger.debug(`single output: ${buf.toString("hex")}`),hashOutputs=_sha256(buf)}}else{const txOutsSize=trans.outs.map((output=>8+varSliceSize(output.script))).reduce(((a,b)=>a+b)),buf=Buffer.alloc(txOutsSize),bufferWriter=new bufferutils_1.BufferWriter(buf);trans.outs.forEach((out=>{bufferWriter.writeUInt64(out.value),bufferWriter.writeVarSlice(out.script)})),null==logger||logger.debug(`outputs: ${buf.toString("hex")}`),hashOutputs=_sha256(buf)}const spendType=(leafHash?2:0)+(annex?1:0),sigMsgSize=174-(isAnyoneCanPay?49:0)-(isNone?32:0)+(annex?32:0)+(leafHash?37:0),buf=Buffer.alloc(sigMsgSize),sigMsgWriter=new bufferutils_1.BufferWriter(buf);if(sigMsgWriter.writeUInt8(hashType),sigMsgWriter.writeInt32(trans.version),sigMsgWriter.writeUInt32(trans.locktime),sigMsgWriter.writeSlice(hashPrevouts),sigMsgWriter.writeSlice(hashAmounts),sigMsgWriter.writeSlice(hashScriptPubKeys),sigMsgWriter.writeSlice(hashSequences),isNone||isSingle||sigMsgWriter.writeSlice(hashOutputs),sigMsgWriter.writeUInt8(spendType),isAnyoneCanPay){const input=trans.ins[inIndex];sigMsgWriter.writeSlice(input.hash),sigMsgWriter.writeUInt32(input.index),sigMsgWriter.writeUInt64(values[inIndex]),sigMsgWriter.writeVarSlice(prevOutScripts[inIndex]),sigMsgWriter.writeUInt32(input.sequence)}else sigMsgWriter.writeUInt32(inIndex);if(annex){const buf=Buffer.alloc(varSliceSize(annex));new bufferutils_1.BufferWriter(buf).writeVarSlice(annex),sigMsgWriter.writeSlice(_sha256(buf))}return isSingle&&sigMsgWriter.writeSlice(hashOutputs),leafHash&&(sigMsgWriter.writeSlice(leafHash),sigMsgWriter.writeUInt8(0),sigMsgWriter.writeUInt32(4294967295)),null==logger||logger.debug("end dataForWitnessV1"),Buffer.concat([Buffer.from([0]),buf])}static getHash(trans,forWitness){return forWitness&&trans.isCoinbase()?Buffer.alloc(32,0):hash256(trans.toBuffer(void 0,void 0,forWitness))}static getId(trans){return this.getHash(trans).reverse().toString("hex")}addInput(hash,index,sequence,scriptSig){sequence&&0!==sequence||(sequence=Transaction.DEFAULT_SEQUENCE),this.ins.push({hash,index,script:null!=scriptSig?scriptSig:EMPTY,sequence,witness:[]})}addOutput(scriptPubKey,value,path){this.outs.push({script:scriptPubKey,value,path})}toBuffer(buffer,initialOffset,_ALLOW_WITNESS=!1){buffer||(buffer=Buffer.allocUnsafe(this.byteLength(_ALLOW_WITNESS)));const bufferWriter=new bufferutils_1.BufferWriter(buffer,null!=initialOffset?initialOffset:0);bufferWriter.writeInt32(this.version);const hasWitnesses=_ALLOW_WITNESS&&this.hasWitnesses();return hasWitnesses&&(bufferWriter.writeUInt8(Transaction.ADVANCED_TRANSACTION_MARKER),bufferWriter.writeUInt8(Transaction.ADVANCED_TRANSACTION_FLAG)),bufferWriter.writeVarInt(this.ins.length),this.ins.forEach((txIn=>{bufferWriter.writeSlice(txIn.hash),bufferWriter.writeUInt32(txIn.index),bufferWriter.writeVarSlice(txIn.script),bufferWriter.writeUInt32(txIn.sequence)})),bufferWriter.writeVarInt(this.outs.length),this.outs.forEach((txOut=>{void 0!==txOut.value?bufferWriter.writeUInt64(txOut.value):bufferWriter.writeSlice(txOut.valueBuffer),bufferWriter.writeVarSlice(txOut.script)})),hasWitnesses&&this.ins.forEach((input=>{bufferWriter.writeVector(input.witness)})),bufferWriter.writeUInt32(this.locktime),void 0!==initialOffset?buffer.slice(initialOffset,bufferWriter.offset):buffer}toHex(){return this.toBuffer(void 0,void 0,!0).toString("hex")}hasWitnesses(){return this.ins.some((x=>0!==x.witness.length))}isCoinbase(){if(1!==this.ins.length)return!1;const hash=this.ins[0].hash;for(let i=0;i<32;++i)if(0!==hash[i])return!1;return!0}clone(){const newTx=new Transaction;return newTx.version=this.version,newTx.locktime=this.locktime,newTx.ins=this.ins.map((txIn=>({hash:txIn.hash,index:txIn.index,script:txIn.script,sequence:txIn.sequence,witness:txIn.witness}))),newTx.outs=this.outs.map((txOut=>({script:txOut.script,value:txOut.value}))),newTx}weight(){return 3*this.byteLength(!1)+this.byteLength(!0)}virtualSize(){return Math.ceil(this.weight()/4)}byteLength(_ALLOW_WITNESS=!0){const hasWitnesses=_ALLOW_WITNESS&&this.hasWitnesses();return(hasWitnesses?10:8)+varuint.encodingLength(this.ins.length)+varuint.encodingLength(this.outs.length)+this.ins.reduce(((sum,input)=>sum+40+varSliceSize(input.script)),0)+this.outs.reduce(((sum,output)=>sum+8+varSliceSize(output.script)),0)+(hasWitnesses?this.ins.reduce(((sum,input)=>sum+function(someVector){const length=someVector.length;return varuint.encodingLength(length)+someVector.reduce(((sum,witness)=>sum+varSliceSize(witness)),0)}(input.witness)),0):0)}}function _sha256(data){return Buffer.from((0,hash_js_1.sha256)().update(data).digest())}function hash256(data){const hash1=(0,hash_js_1.sha256)().update(data).digest(),hash2=(0,hash_js_1.sha256)().update(hash1).digest();return Buffer.from(hash2)}function varSliceSize(someScript){const length=someScript.length;return varuint.encodingLength(length)+length}exports.Transaction=Transaction,Transaction.DEFAULT_SEQUENCE=4294967295,Transaction.SIGHASH_DEFAULT=0,Transaction.SIGHASH_ALL=1,Transaction.SIGHASH_NONE=2,Transaction.SIGHASH_SINGLE=3,Transaction.SIGHASH_ANYONECANPAY=128,Transaction.SIGHASH_OUTPUT_MASK=3,Transaction.SIGHASH_INPUT_MASK=128,Transaction.ADVANCED_TRANSACTION_MARKER=0,Transaction.ADVANCED_TRANSACTION_FLAG=1,exports.varSliceSize=varSliceSize;