UNPKG

nerve-sdk-js

Version:
728 lines (724 loc) 31.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const tbc = __importStar(require("tbc-lib-js")); const FT = require("./ft"); class MultiSig { /** * Create a multi-signature transaction * @param address_from The address from which the transaction is sent * @param pubKeys An array of public keys involved in the multi-signature * @param signatureCount The number of signatures required to authorize the transaction * @param publicKeyCount The total number of public keys in the multi-signature * @param amount_tbc The amount to be sent in TBC * @param utxos An array of unspent transaction outputs to be used as inputs * @param privateKey The private key used to sign the transaction * @returns The raw serialized transaction string */ static createMultiSigWallet(address_from, pubKeys, signatureCount, publicKeyCount, tbc_amount, utxos, privateKey) { const address = MultiSig.getMultiSigAddress(pubKeys, signatureCount, publicKeyCount); const script_asm = MultiSig.getMultiSigLockScript(address); const tx = new tbc.Transaction(); tx.from(utxos); tx.addOutput(new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm), satoshis: Math.floor(tbc_amount * 1e6), })); for (let i = 0; i < publicKeyCount; i++) { tx.addOutput(new tbc.Transaction.Output({ script: MultiSig.buildHoldScript(pubKeys[i]), satoshis: 200, })); } tx.addOutput(new tbc.Transaction.Output({ script: MultiSig.buildTapeScript(address, pubKeys), satoshis: 0, })).change(address_from); const txSize = tx.getEstimateSize(); if (txSize < 1000) { tx.fee(80); } else { tx.feePerKb(100); } tx.sign(privateKey).seal(); const raw = tx.uncheckedSerialize(); return raw; } /** * Create a P2PKH to multi-signature transaction * @param address_from The address from which the transaction is sent * @param address_to The address to which the transaction is sent * @param amount_tbc The amount to be sent in TBC * @param utxos An array of unspent transaction outputs to be used as inputs * @param privateKey The private key used to sign the transaction * @returns The raw serialized transaction string */ static p2pkhToMultiSig_sendTBC(address_from, address_to, amount_tbc, utxos, privateKey, additionalInfo) { const script_asm = MultiSig.getMultiSigLockScript(address_to); const amount_satoshis = Math.floor(amount_tbc * Math.pow(10, 6)); const tx = new tbc.Transaction() .from(utxos) .addOutput(new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm), satoshis: amount_satoshis, })); if (additionalInfo) { //Additional infromation output let additionalInfoScript = tbc.Script.fromASM('OP_FALSE OP_RETURN'); additionalInfoScript = additionalInfoScript.add(additionalInfo); tx.addOutput(new tbc.Transaction.Output({ script: additionalInfoScript, satoshis: 0 })); } tx.change(address_from); const txSize = tx.getEstimateSize(); if (txSize < 1000) { tx.fee(80); } else { tx.feePerKb(100); } tx.sign(privateKey).seal(); const raw = tx.uncheckedSerialize(); return raw; } /** * Build a multi-signature transaction * @param address_from The address from which the transaction is sent * @param address_to The address to which the transaction is sent * @param amount_tbc The amount to be sent in TBC * @param utxos An array of unspent transaction outputs to be used as inputs * @returns The raw serialized transaction string */ static buildMultiSigTransaction_sendTBC(address_from, address_to, amount_tbc, utxos, additionalInfo) { const script_asm_from = MultiSig.getMultiSigLockScript(address_from); const amount_satoshis = Math.floor(amount_tbc * Math.pow(10, 6)); let count = 0; let amounts = []; for (let i = 0; i < utxos.length; i++) { count += utxos[i].satoshis; amounts.push(utxos[i].satoshis); } const tx = new tbc.Transaction().from(utxos); tx.addOutput(new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm_from), satoshis: count - amount_satoshis - 1000, })) .to(address_to, amount_satoshis); //Additional infromation output if (additionalInfo) { let additionalInfoScript = tbc.Script.fromASM('OP_FALSE OP_RETURN'); additionalInfoScript = additionalInfoScript.add(additionalInfo); tx.addOutput(new tbc.Transaction.Output({ script: additionalInfoScript, satoshis: 0 })); } const txraw = tx.uncheckedSerialize(); return { txraw, amounts }; } /** * Build a multi-signature transaction * @param address_from The address from which the transaction is sent * @param address_to The address to which the transaction is sent * @param amount_tbc The amount to be sent in TBC * @param utxos An array of unspent transaction outputs to be used as inputs * @returns The raw serialized transaction string */ static buildMultiSigTransaction_sendTBCToMultiSig(address_from, address_to, amount_tbc, utxos, additionalInfo) { const script_asm_from = MultiSig.getMultiSigLockScript(address_from); const script_asm_to = MultiSig.getMultiSigLockScript(address_to); const amount_satoshis = Math.floor(amount_tbc * Math.pow(10, 6)); let count = 0; let amounts = []; for (let i = 0; i < utxos.length; i++) { count += utxos[i].satoshis; amounts.push(utxos[i].satoshis); } const tx_source = new tbc.Transaction().from(utxos); tx_source.addOutput(new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm_to), satoshis: amount_satoshis, })); tx_source .addOutput(new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm_from), satoshis: count - amount_satoshis - 1000, })); //Additional infromation output if (additionalInfo) { let additionalInfoScript = tbc.Script.fromASM('OP_FALSE OP_RETURN'); additionalInfoScript = additionalInfoScript.add(additionalInfo); tx_source.addOutput(new tbc.Transaction.Output({ script: additionalInfoScript, satoshis: 0 })); } const tx_source_raw = tx_source.uncheckedSerialize(); if (count - amount_satoshis - 1100 > 80) { const tx = new tbc.Transaction(); tx.addInputFromPrevTx(tx_source, 1) .addOutput(new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm_from), satoshis: count - amount_satoshis - 1100, })); const tx_raw = tx.uncheckedSerialize(); return [ { txraw: tx_source_raw, amounts }, { txraw: tx_raw, amounts: [count - amount_satoshis - 1000] }, ]; } else { return [{ txraw: tx_source_raw, amounts }]; } } /** * Sign a multi-signature transaction * @param address_from The address from which the transaction is sent * @param multiSigTxraw The raw serialized transaction string * @param privateKey The private key used to sign the transaction * @returns An array of signatures */ static signMultiSigTransaction_sendTBC(address_from, multiSigTxraw, privateKey) { const script_asm = MultiSig.getMultiSigLockScript(address_from); const { txraw, amounts } = multiSigTxraw; const tx = new tbc.Transaction(txraw); for (let i = 0; i < amounts.length; i++) { tx.inputs[i].output = new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm), satoshis: amounts[i], }); } let sigs = []; for (let i = 0; i < amounts.length; i++) { sigs[i] = tx.getSignature(i, privateKey); } return sigs; } /** * Sign multi-signature transactions * @param address_from The address from which the transaction is sent * @param multiSigTxraws The raw serialized transaction string * @param privateKey The private key used to sign the transaction * @returns An array of signatures */ static batchSignMultiSigTransaction_sendTBC(address_from, multiSigTxraws, privateKey) { let allSigs = []; for (let txIndex = 0; txIndex < multiSigTxraws.length; txIndex++) { const sigs = MultiSig.signMultiSigTransaction_sendTBC(address_from, multiSigTxraws[txIndex], privateKey); allSigs.push(sigs); } return allSigs; } /** * Finish a multi-signature transaction * @param txraw The raw serialized transaction string * @param sigs An array of signatures * @param pubkeys An array of public keys * @returns The raw serialized transaction string */ static finishMultiSigTransaction_sendTBC(txraw, sigs, pubKeys) { let multiPubKeys = ""; for (let i = 0; i < pubKeys.length; i++) { multiPubKeys = multiPubKeys + pubKeys[i]; } const tx = new tbc.Transaction(txraw); for (let j = 0; j < sigs.length; j++) { tx.setInputScript({ inputIndex: j, }, (tx) => { let signature = ""; for (let i = 0; i < sigs[j].length; i++) { if (i < sigs[j].length - 1) { signature = signature + sigs[j][i] + " "; } else { signature = signature + sigs[j][i]; } } const unlockingScript = tbc.Script.fromASM(`OP_0 ${signature} ${multiPubKeys}`); return unlockingScript; }); } return tx.uncheckedSerialize(); } /** * Finish multi-signature transactions * @param txraws The raw serialized transaction string * @param sigs An array of signatures * @param pubkeys An array of public keys * @returns The raw serialized transaction string */ static batchFinishMultiSigTransaction_sendTBC(txraws, sigs, pubKeys) { let finishedTxs = []; for (let txIndex = 0; txIndex < txraws.length; txIndex++) { const finishedTx = MultiSig.finishMultiSigTransaction_sendTBC(txraws[txIndex], sigs[txIndex], pubKeys); finishedTxs.push(finishedTx); } return finishedTxs; } /** * Transfer FT from a multi-signature address to another address * @param address_from The address from which the transaction is sent * @param address_to The address to which the transaction is sent * @param ft The FT contract * @param ft_amount The amount to be sent in FT * @param utxo The UTXO to be used as input * @param ftutxos An array of UTXOs to be used as inputs * @param preTX An array of previous transactions * @param prepreTxData An array of previous transaction data * @param privateKey The private key used to sign the transaction * @returns The raw serialized transaction string */ static p2pkhToMultiSig_transferFT(address_from, address_to, ft, ft_amount, utxo, ftutxos, preTXs, prepreTxDatas, privateKey, tbc_amount, additionalInfo) { const code = ft.codeScript; const tape = ft.tapeScript; const decimal = ft.decimal; const tapeAmountSetIn = []; if (ft_amount < 0) { throw new Error("Invalid amount"); } const amountbn = BigInt(Math.floor(ft_amount * Math.pow(10, decimal))); let tapeAmountSum = BigInt(0); for (let i = 0; i < ftutxos.length; i++) { tapeAmountSetIn.push(ftutxos[i].ftBalance); tapeAmountSum += BigInt(tapeAmountSetIn[i]); } if (amountbn > tapeAmountSum) { throw new Error("Insufficient balance, please add more FT UTXOs"); } if (decimal > 18) { throw new Error("The maximum value for decimal cannot exceed 18"); } const maxAmount = Math.floor(Math.pow(10, 18 - decimal)); if (ft_amount > maxAmount) { throw new Error(`When decimal is ${decimal}, the maximum amount cannot exceed ${maxAmount}`); } const { amountHex, changeHex } = FT.buildTapeAmount(amountbn, tapeAmountSetIn); const script_asm = MultiSig.getMultiSigLockScript(address_to); const tx = new tbc.Transaction().from(ftutxos).from(utxo); if (tbc_amount && Number(tbc_amount) > 0) { const amount_satoshis = Math.floor(tbc_amount * Math.pow(10, 6)); tx.addOutput(new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm), satoshis: amount_satoshis, })); } const hash = tbc.crypto.Hash.sha256ripemd160(tbc.crypto.Hash.sha256(tbc.Script.fromASM(script_asm).toBuffer())).toString("hex"); const codeScript = FT.buildFTtransferCode(code, hash); tx.addOutput(new tbc.Transaction.Output({ script: codeScript, satoshis: 2000, })); const tapeScript = FT.buildFTtransferTape(tape, amountHex); tx.addOutput(new tbc.Transaction.Output({ script: tapeScript, satoshis: 0, })); if (amountbn < tapeAmountSum) { const changeCodeScript = FT.buildFTtransferCode(code, address_from); tx.addOutput(new tbc.Transaction.Output({ script: changeCodeScript, satoshis: 2000, })); const changeTapeScript = FT.buildFTtransferTape(tape, changeHex); tx.addOutput(new tbc.Transaction.Output({ script: changeTapeScript, satoshis: 0, })); } if (additionalInfo) { //Additional infromation output let additionalInfoScript = tbc.Script.fromASM('OP_FALSE OP_RETURN'); additionalInfoScript = additionalInfoScript.add(additionalInfo); tx.addOutput(new tbc.Transaction.Output({ script: additionalInfoScript, satoshis: 0 })); } tx.change(address_from); const txSize = tx.getEstimateSize(); if (txSize < 1000) { tx.fee(80); } else { tx.feePerKb(100); } for (let i = 0; i < ftutxos.length; i++) { tx.setInputScript({ inputIndex: i, }, (tx) => { const unlockingScript = ft.getFTunlock(privateKey, tx, preTXs[i], prepreTxDatas[i], i, ftutxos[i].outputIndex); return unlockingScript; }); } tx.sign(privateKey).seal(); return tx.uncheckedSerialize(); } /** * Build a multi-signature transaction for transferring FT * @param address_from The address from which the transaction is sent * @param address_to The address to which the transaction is sent * @param ft The FT contract * @param ft_amount The amount to be sent in FT * @param utxo The UTXO to be used as input * @param ftutxos An array of UTXOs to be used as inputs * @param preTX An array of previous transactions * @param prepreTxData An array of previous transaction data * @param privateKey The private key used to sign the transaction * @returns The raw serialized transaction string */ static buildMultiSigTransaction_transferFT(address_from, address_to, ft, ft_amount, utxo, ftutxos, preTXs, prepreTxDatas, contractTX, privateKey, tbc_amount, additionalInfo) { const code = ft.codeScript; const tape = ft.tapeScript; const decimal = ft.decimal; const tapeAmountSetIn = []; const script_asm_from = MultiSig.getMultiSigLockScript(address_from); const hash_from = tbc.crypto.Hash.sha256ripemd160(tbc.crypto.Hash.sha256(tbc.Script.fromASM(script_asm_from).toBuffer())).toString("hex"); const amountbn = BigInt(Math.floor(ft_amount * Math.pow(10, decimal))); let tapeAmountSum = BigInt(0); for (let i = 0; i < ftutxos.length; i++) { tapeAmountSetIn.push(ftutxos[i].ftBalance); tapeAmountSum += BigInt(tapeAmountSetIn[i]); } if (amountbn > tapeAmountSum) { throw new Error("Insufficient balance, please add more FT UTXOs"); } if (decimal > 18) { throw new Error("The maximum value for decimal cannot exceed 18"); } const maxAmount = Math.floor(Math.pow(10, 18 - decimal)); if (ft_amount > maxAmount) { throw new Error(`When decimal is ${decimal}, the maximum amount cannot exceed ${maxAmount}`); } const { amountHex, changeHex } = FT.buildTapeAmount(amountbn, tapeAmountSetIn, 1); const tx = new tbc.Transaction().from(utxo).from(ftutxos); let amount_satoshis = 0; if (tbc_amount) { amount_satoshis = Math.floor(tbc_amount * Math.pow(10, 6)); } switch (ftutxos.length) { case 1: tx.addOutput(new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm_from), satoshis: utxo.satoshis - amount_satoshis - 4000, })); break; case 2: tx.addOutput(new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm_from), satoshis: utxo.satoshis - amount_satoshis - 5500, })); break; case 3: tx.addOutput(new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm_from), satoshis: utxo.satoshis - amount_satoshis - 7000, })); break; case 4: tx.addOutput(new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm_from), satoshis: utxo.satoshis - amount_satoshis - 8500, })); break; case 5: tx.addOutput(new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm_from), satoshis: utxo.satoshis - amount_satoshis - 10000, })); break; } let codeScript; if (address_to.startsWith("1")) { codeScript = FT.buildFTtransferCode(code, address_to); } else { const hash_to = tbc.crypto.Hash.sha256ripemd160(tbc.crypto.Hash.sha256(tbc.Script.fromASM(MultiSig.getMultiSigLockScript(address_to)).toBuffer())).toString("hex"); codeScript = FT.buildFTtransferCode(code, hash_to); } tx.addOutput(new tbc.Transaction.Output({ script: codeScript, satoshis: 2000, })); const tapeScript = FT.buildFTtransferTape(tape, amountHex); tx.addOutput(new tbc.Transaction.Output({ script: tapeScript, satoshis: 0, })); if (amountbn < tapeAmountSum) { const changeCodeScript = FT.buildFTtransferCode(code, hash_from); tx.addOutput(new tbc.Transaction.Output({ script: changeCodeScript, satoshis: 2000, })); const changeTapeScript = FT.buildFTtransferTape(tape, changeHex); tx.addOutput(new tbc.Transaction.Output({ script: changeTapeScript, satoshis: 0, })); } if (tbc_amount) { tx.addOutput(new tbc.Transaction.Output({ script: tbc.Script.buildPublicKeyHashOut(address_to), satoshis: amount_satoshis, })); } //Additional infromation output if (additionalInfo) { let additionalInfoScript = tbc.Script.fromASM('OP_FALSE OP_RETURN'); additionalInfoScript = additionalInfoScript.add(additionalInfo); tx.addOutput(new tbc.Transaction.Output({ script: additionalInfoScript, satoshis: 0 })); } for (let i = 0; i < ftutxos.length; i++) { tx.setInputScript({ inputIndex: i + 1, }, (tx_source) => { const unlockingScript = ft.getFTunlockSwap(privateKey, tx_source, preTXs[i], prepreTxDatas[i], contractTX, i + 1, ftutxos[i].outputIndex); return unlockingScript; }); } const txraw = tx.uncheckedSerialize(); return { txraw, amounts: [utxo.satoshis] }; } /** * Sign a multi-signature transaction for transferring FT * @param address_from The address from which the transaction is sent * @param multiSigTxraw The raw serialized transaction string * @param privateKey The private key used to sign the transaction * @returns An array of signatures */ static signMultiSigTransaction_transferFT(multiSig_address, multiSigTxraw, privateKey) { const script_asm = MultiSig.getMultiSigLockScript(multiSig_address); const { txraw, amounts } = multiSigTxraw; const tx = new tbc.Transaction(txraw); tx.inputs[0].output = new tbc.Transaction.Output({ script: tbc.Script.fromASM(script_asm), satoshis: amounts[0], }); let sigs = []; sigs[0] = tx.getSignature(0, privateKey); return sigs; } /** * Sign multi-signature transactions for transferring FT * @param multiSig_address The multi-signature address * @param multiSigTxraws The raw serialized transaction strings * @param privateKey The private key used to sign the transaction * @returns An array of signatures */ static batchSignMultiSigTransaction_transferFT(multiSig_address, multiSigTxraws, privateKey) { let allSigs = []; for (let txIndex = 0; txIndex < multiSigTxraws.length; txIndex++) { const sigs = MultiSig.signMultiSigTransaction_transferFT(multiSig_address, multiSigTxraws[txIndex], privateKey); allSigs.push(sigs); } return allSigs; } /** * Finish a multi-signature transaction for transferring FT * @param txraw The raw serialized transaction string * @param sigs An array of signatures * @param pubkeys The public keys * @returns The raw serialized transaction string */ static finishMultiSigTransaction_transferFT(txraw, sigs, pubKeys) { let multiPubKeys = ""; for (let i = 0; i < pubKeys.length; i++) { multiPubKeys = multiPubKeys + pubKeys[i]; } const tx = new tbc.Transaction(txraw); tx.setInputScript({ inputIndex: 0, }, (tx) => { let signature = ""; for (let i = 0; i < sigs[0].length; i++) { if (i < sigs[0].length - 1) { signature = signature + sigs[0][i] + " "; } else { signature = signature + sigs[0][i]; } } const unlockingScript = tbc.Script.fromASM(`OP_0 ${signature} ${multiPubKeys}`); return unlockingScript; }); return tx.uncheckedSerialize(); } /** * Finish multi-signature transactions for transferring FT * @param txraws The raw serialized transaction strings * @param sigs An array of signatures * @param pubkeys The public keys * @returns The raw serialized transaction strings */ static batchFinishMultiSigTransaction_transferFT(txraws, sigs, pubKeys) { let finishedTxs = []; for (let txIndex = 0; txIndex < txraws.length; txIndex++) { const finishedTx = MultiSig.finishMultiSigTransaction_transferFT(txraws[txIndex], sigs[txIndex], pubKeys); finishedTxs.push(finishedTx); } return finishedTxs; } /** * Get multi-signature address * @param pubkeys Public keys * @param signatureCount Number of signatures * @param publicKeyCount Number of public keys * @returns Multi-signature address */ static getMultiSigAddress(pubKeys, signatureCount, publicKeyCount) { if (signatureCount < 1 || signatureCount > 6) { throw new Error("Invalid signatureCount."); } else if (publicKeyCount < 3 || publicKeyCount > 10) { throw new Error("Invalid publicKeyCount."); } else if (signatureCount > publicKeyCount) { throw new Error("SignatureCount must be less than publicKeyCount."); } const hash = MultiSig.getHash(pubKeys); const prefix = (signatureCount << 4) | (publicKeyCount & 0x0f); const versionBuffer = Buffer.from([prefix]); const addressBuffer = Buffer.concat([versionBuffer, hash]); const addressHash = tbc.crypto.Hash.sha256sha256(addressBuffer); const checksum = addressHash.subarray(0, 4); const addressWithChecksum = Buffer.concat([addressBuffer, checksum]); return tbc.encoding.Base58.encode(addressWithChecksum); } /** * Get the signature and public key count from a multi-signature address * @param address Multi-signature address * @returns Signature and public key count */ static getSignatureAndPublicKeyCount(address) { const buf = Buffer.from(tbc.encoding.Base58.decode(address)); const prefix = buf[0]; const signatureCount = (prefix >> 4) & 0x0f; const publicKeyCount = prefix & 0x0f; return { signatureCount, publicKeyCount }; } /** * Verify a multi-signature address * @param pubkeys Public keys * @param address Multi-signature address * @returns True if the address is valid, false otherwise */ static verifyMultiSigAddress(pubKeys, address) { const hash_from_pubkeys = MultiSig.getHash(pubKeys).toString("hex"); const buf = Buffer.from(tbc.encoding.Base58.decode(address)); const hash_from_address = Buffer.from(buf.subarray(1, 21)).toString("hex"); return hash_from_pubkeys === hash_from_address; } /** * Generate a multi-signature lock script(script_asm) from a multi-signature address * @param address Multi-signature address * @returns Lock script for the multi-signature contract * @throws Error if signature count or public key count is invalid * * The generated lock script performs the following: * 1. Splits the input public keys * 2. Duplicates and concatenates the public keys * 3. Verifies the hash matches the address * 4. Checks that the required number of signatures are valid */ static getMultiSigLockScript(address) { const buf = Buffer.from(tbc.encoding.Base58.decode(address)); const { signatureCount, publicKeyCount } = MultiSig.getSignatureAndPublicKeyCount(address); if (signatureCount < 1 || signatureCount > 6) { throw new Error("Invalid signatureCount."); } else if (publicKeyCount < 3 || publicKeyCount > 10) { throw new Error("Invalid publicKeyCount."); } else if (signatureCount > publicKeyCount) { throw new Error("SignatureCount must be less than publicKeyCount."); } const hash = Buffer.from(buf.subarray(1, 21)).toString("hex"); let lockScriptPrefix = ""; for (let i = 0; i < publicKeyCount - 1; i++) { lockScriptPrefix = lockScriptPrefix + "21 OP_SPLIT "; } for (let i = 0; i < publicKeyCount; i++) { lockScriptPrefix = lockScriptPrefix + `OP_${publicKeyCount - 1} OP_PICK `; } for (let i = 0; i < publicKeyCount - 1; i++) { lockScriptPrefix = lockScriptPrefix + `OP_CAT `; } const script_asm = `OP_${signatureCount} OP_SWAP ` + lockScriptPrefix + `OP_HASH160 ${hash} OP_EQUALVERIFY OP_${publicKeyCount} OP_CHECKMULTISIG`; return script_asm; } /** * Get the combine hash from a multi-signature address * @param address Multi-signature address * @returns Combine hash */ static getCombineHash(address) { const combine_hash = tbc.crypto.Hash.sha256ripemd160(tbc.crypto.Hash.sha256(tbc.Script.fromASM(MultiSig.getMultiSigLockScript(address)).toBuffer())).toString("hex") + "01"; return combine_hash; } static getHash(pubKeys) { let multiPubKeys = ""; for (let i = 0; i < pubKeys.length; i++) { multiPubKeys = multiPubKeys + pubKeys[i]; } const buf = Buffer.from(multiPubKeys, "hex"); const hash = tbc.crypto.Hash.sha256ripemd160(buf); return hash; } static buildHoldScript(pubKey) { const publicKeyHash = tbc.crypto.Hash.sha256ripemd160(Buffer.from(pubKey, "hex")).toString("hex"); return new tbc.Script("OP_DUP OP_HASH160" + " 0x14 0x" + publicKeyHash + " OP_EQUALVERIFY OP_CHECKSIG OP_RETURN 0x08 0x6d756c7469736967"); } static buildTapeScript(address, pubKeys) { const data = { address: address, pubkeys: pubKeys, }; const dataHex = Buffer.from(JSON.stringify(data)).toString("hex"); return tbc.Script.fromASM("OP_FALSE OP_RETURN " + dataHex + " 4d54617065"); } } module.exports = MultiSig;