import { error } from '@coolwallet/core'; import * as bufferUtil from './bufferUtil'; import * as cryptoUtil from './cryptoUtil'; import * as params from '../config/params'; import * as bitcoin from 'bitcoinjs-lib'; import * as types from '../config/types'; export function addressToOutScript(address: string): { scriptType: types.ScriptType; outScript: Buffer; outHash?: Buffer; } { const input = { address: address, network: params.activeNet, }; let scriptType; let payment; if (address.startsWith('L')) { scriptType = types.ScriptType.P2PKH; payment = bitcoin.payments.p2pkh(input); } else if (address.startsWith('M')) { scriptType = types.ScriptType.P2SH_P2WPKH; payment = bitcoin.payments.p2sh(input); } else if (address.startsWith('ltc')) { scriptType = types.ScriptType.P2WPKH; payment = bitcoin.payments.p2wpkh(input); } else { throw new error.SDKError(addressToOutScript.name, `Unsupport Address '${address}'`); } if (!payment.output) throw new error.SDKError(addressToOutScript.name, `No OutScript for Address '${address}'`); const outScript = payment.output; const outHash = payment.hash; return { scriptType, outScript, outHash }; } export function pubkeyToAddressAndOutScript( burPublicKey: Buffer, scriptType: types.ScriptType ): { address: string; outScript: Buffer; hash: Buffer } { const input = { pubkey: burPublicKey, network: params.activeNet, }; let payment; if (scriptType === types.ScriptType.P2PKH) { payment = bitcoin.payments.p2pkh(input); } else if (scriptType === types.ScriptType.P2SH_P2WPKH) { payment = bitcoin.payments.p2sh({ redeem: bitcoin.payments.p2wpkh(input), }); } else if (scriptType === types.ScriptType.P2WPKH) { payment = bitcoin.payments.p2wpkh(input); } else { throw new error.SDKError(pubkeyToAddressAndOutScript.name, `Unsupport ScriptType '${scriptType}'`); } if (!payment.address) throw new error.SDKError(pubkeyToAddressAndOutScript.name, `No Address for ScriptType '${scriptType}'`); if (!payment.output) throw new error.SDKError(pubkeyToAddressAndOutScript.name, `No OutScript for ScriptType '${scriptType}'`); if (!payment.hash) throw new error.SDKError(pubkeyToAddressAndOutScript.name, `No OutScript for ScriptType '${scriptType}'`); return { address: payment.address, outScript: payment.output, hash: payment.hash }; } export function createUnsignedTransactions( scriptType: types.ScriptType, inputs: Array, output: types.Output, change: types.Change | undefined, version = 1, lockTime = 0 ): { preparedData: types.PreparedData; unsignedTransactions: Array; } { const versionBuf = bufferUtil.toUintBuffer(version, 4); const lockTimeBuf = bufferUtil.toUintBuffer(lockTime, 4); const inputsCount = bufferUtil.toVarUintBuffer(inputs.length); const preparedInputs = inputs.map(({ preTxHash, preIndex, preValue, sequence, addressIndex, pubkeyBuf }) => { if (!pubkeyBuf) throw new error.SDKError(createUnsignedTransactions.name, 'Public Key not exists !!'); const preOutPointBuf = Buffer.concat([ Buffer.from(preTxHash, 'hex').reverse(), bufferUtil.toUintBuffer(preIndex, 4), ]); const preValueBuf = bufferUtil.toUintBuffer(preValue, 8); const sequenceBuf = sequence ? bufferUtil.toUintBuffer(sequence, 4) : Buffer.from('ffffffff', 'hex'); return { addressIndex, pubkeyBuf, preOutPointBuf, preValueBuf, sequenceBuf, }; }); const { scriptType: outputType, outScript: outputScript } = addressToOutScript(output.address); const outputScriptLen = bufferUtil.toVarUintBuffer(outputScript.length); const outputArray = [Buffer.concat([bufferUtil.toUintBuffer(output.value, 8), outputScriptLen, outputScript])]; if (change) { if (!change.pubkeyBuf) throw new error.SDKError(createUnsignedTransactions.name, 'Public Key not exists !!'); const changeValue = bufferUtil.toUintBuffer(change.value, 8); const { outScript } = pubkeyToAddressAndOutScript(change.pubkeyBuf, scriptType); const outScriptLen = bufferUtil.toVarUintBuffer(outScript.length); outputArray.push(Buffer.concat([changeValue, outScriptLen, outScript])); } const outputsCount = bufferUtil.toVarUintBuffer(change ? 2 : 1); const outputsBuf = Buffer.concat(outputArray); const hashPrevouts = cryptoUtil.hash256(Buffer.concat(preparedInputs.map((input) => input.preOutPointBuf))); const hashSequence = cryptoUtil.hash256(Buffer.concat(preparedInputs.map((input) => input.sequenceBuf))); const hashOutputs = cryptoUtil.hash256(outputsBuf); const unsignedTransactions = preparedInputs.map(({ pubkeyBuf, preOutPointBuf, preValueBuf, sequenceBuf }) => { if (scriptType === types.ScriptType.P2PKH) { const { outScript } = pubkeyToAddressAndOutScript(pubkeyBuf, scriptType); const outScriptLen = bufferUtil.toVarUintBuffer(outScript.length); return Buffer.concat([ versionBuf, bufferUtil.toVarUintBuffer(1), preOutPointBuf, outScriptLen, // preOutScriptBuf outScript, // preOutScriptBuf sequenceBuf, outputsCount, outputsBuf, lockTimeBuf, Buffer.from('81000000', 'hex'), ]); } else { return Buffer.concat([ versionBuf, hashPrevouts, hashSequence, preOutPointBuf, Buffer.from(`1976a914${cryptoUtil.hash160(pubkeyBuf).toString('hex')}88ac`, 'hex'), // ScriptCode preValueBuf, sequenceBuf, hashOutputs, lockTimeBuf, Buffer.from('01000000', 'hex'), ]); } }); return { preparedData: { versionBuf, inputsCount, preparedInputs, outputType, outputsCount, outputsBuf, lockTimeBuf, }, unsignedTransactions, }; } export function composeFinalTransaction( scriptType: types.ScriptType, preparedData: types.PreparedData, signatures: Array ): Buffer { const { versionBuf, inputsCount, preparedInputs, outputsCount, outputsBuf, lockTimeBuf } = preparedData; if ( scriptType !== types.ScriptType.P2PKH && scriptType !== types.ScriptType.P2WPKH && scriptType !== types.ScriptType.P2SH_P2WPKH ) { throw new error.SDKError(composeFinalTransaction.name, `Unsupport ScriptType : ${scriptType}`); } if (scriptType === types.ScriptType.P2PKH) { const inputsBuf = Buffer.concat( preparedInputs.map((data, i) => { const { pubkeyBuf, preOutPointBuf, sequenceBuf } = data; const signature = signatures[i]; const inScript = Buffer.concat([ Buffer.from((signature.length + 1).toString(16), 'hex'), signature, Buffer.from('81', 'hex'), Buffer.from(pubkeyBuf.length.toString(16), 'hex'), pubkeyBuf, ]); return Buffer.concat([preOutPointBuf, bufferUtil.toVarUintBuffer(inScript.length), inScript, sequenceBuf]); }) ); return Buffer.concat([versionBuf, inputsCount, inputsBuf, outputsCount, outputsBuf, lockTimeBuf]); } else { const flagBuf = Buffer.from('0001', 'hex'); const segwitBuf = Buffer.concat( preparedInputs.map(({ pubkeyBuf }, i) => { const signature = signatures[i]; const segwitScript = Buffer.concat([ Buffer.from((signature.length + 1).toString(16), 'hex'), signature, Buffer.from('01', 'hex'), Buffer.from(pubkeyBuf.length.toString(16), 'hex'), pubkeyBuf, ]); return Buffer.concat([Buffer.from('02', 'hex'), segwitScript]); }) ); const inputsBuf = Buffer.concat( preparedInputs.map(({ pubkeyBuf, preOutPointBuf, sequenceBuf }) => { if (scriptType === types.ScriptType.P2SH_P2WPKH) { const { outScript } = pubkeyToAddressAndOutScript(pubkeyBuf, types.ScriptType.P2WPKH); const inScript = Buffer.concat([Buffer.from(outScript.length.toString(16), 'hex'), outScript]); return Buffer.concat([preOutPointBuf, bufferUtil.toVarUintBuffer(inScript.length), inScript, sequenceBuf]); } return Buffer.concat([preOutPointBuf, Buffer.from('00', 'hex'), sequenceBuf]); }) ); return Buffer.concat([ versionBuf, flagBuf, inputsCount, inputsBuf, outputsCount, outputsBuf, segwitBuf, lockTimeBuf, ]); } }