diff --git a/packages/crypto-wallet-core/src/transactions/bch/index.ts b/packages/crypto-wallet-core/src/transactions/bch/index.ts index fdb3314ab3e..b1d7740035f 100644 --- a/packages/crypto-wallet-core/src/transactions/bch/index.ts +++ b/packages/crypto-wallet-core/src/transactions/bch/index.ts @@ -1,24 +1,24 @@ import BitcoreLibCash from '@bitpay-labs/bitcore-lib-cash'; -import { BTCTxProvider } from '../btc'; +import { BTCTxProvider, EveryUtxoType } from '../btc'; export class BCHTxProvider extends BTCTxProvider { lib = BitcoreLibCash; - create({ recipients, utxos = [], change, fee = 20000, isSweep }) { - const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos.map(utxo => { - const btcUtxo = Object.assign({}, utxo, { - amount: utxo.value / 1e8, - txid: utxo.mintTxid, - outputIndex: utxo.mintIndex - }); - return new this.lib.Transaction.UnspentOutput(btcUtxo); - }); + create(params: { + recipients: Array<{ address: string; amount: number }>; + utxos?: EveryUtxoType[]; + change?: string; + fee?: number | string; + isSweep?: boolean; + }): string { + const { recipients, utxos = [], change, fee = 20000, isSweep } = params; + const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, Number(fee)); + const btcUtxos = filteredUtxos.map(utxo => this.standardizeUtxo(utxo)); const tx = new this.lib.Transaction().from(btcUtxos).feePerByte(Number(fee) + 2); if (change) { tx.change(change); } for (const recipient of recipients) { - tx.to(recipient.address, parseInt(recipient.amount)); + tx.to(recipient.address, Number(recipient.amount)); } return tx.uncheckedSerialize(); } diff --git a/packages/crypto-wallet-core/src/transactions/btc/index.ts b/packages/crypto-wallet-core/src/transactions/btc/index.ts index e172e8ad8a0..7a8492392ed 100644 --- a/packages/crypto-wallet-core/src/transactions/btc/index.ts +++ b/packages/crypto-wallet-core/src/transactions/btc/index.ts @@ -15,42 +15,62 @@ export class BTCTxProvider { lib = BitcoreLib; selectCoins( - recipients: Array<{ amount: number }>, - utxos: Array<{ - value: number; - mintHeight: number; - txid?: string; - mintTxid?: string; - mintIndex?: number; - }>, - fee: number - ) { - utxos = utxos.sort(function(a, b) { - return a.mintHeight - b.mintHeight; - }); + recipients: Array<{ amount: number | string }>, + utxos: EveryUtxoType[], + fee?: number + ): EveryUtxoType[] { + // Only sort by block height if utxos are bitcore-node style + if (utxos.length > 0 && utxos[0].mintHeight != undefined) { + utxos = utxos.sort(function(a, b) { + return a.mintHeight - b.mintHeight; + }); + } let index = 0; let utxoSum = 0; const recepientSum = recipients.reduce((sum, cur) => sum + Number(cur.amount), fee || 0); while (utxoSum < recepientSum) { + assert(index < utxos.length, 'insufficient funds'); const utxo = utxos[index]; - utxoSum += Number(utxo.value); + utxoSum += Number(utxo.value ?? utxo.satoshis ?? this.lib.Unit.fromBTC(utxo.amount).toSatoshis()); index += 1; } const filteredUtxos = utxos.slice(0, index); return filteredUtxos; } - create({ recipients, utxos = [], change, feeRate, fee, isSweep, replaceByFee, lockUntilDate, lockUntilBlock }) { - const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos.map(utxo => { - const btcUtxo = Object.assign({}, utxo, { - amount: utxo.value / 1e8, - txid: utxo.mintTxid, - outputIndex: utxo.mintIndex - }); - return new this.lib.Transaction.UnspentOutput(btcUtxo); - }); + /** + * Standardize utxo for internal funcionality. + * Accepts either a bitcore-node or a lib (bitcore-lib, bitcore-lib-cash, etc.) utxo. + * Handles both lib style utxos: UnspentOutput properties and UnspentOutput.toObject properties. + * + * @param utxos either a bitcore-node or lib utxo + * @returns utxo in the standard, internaly used format + */ + standardizeUtxo(utxo: EveryUtxoType): UtxoType { + return { + satoshis: Number(utxo.satoshis ?? utxo.value ?? this.lib.Unit.fromBTC(utxo.amount ?? 0).toSatoshis()), + txId: utxo.txId ?? utxo.mintTxid ?? utxo.txid, + outputIndex: Number(utxo.outputIndex ?? utxo.mintIndex ?? utxo.vout ?? 0), + script: utxo.scriptPubKey ?? new this.lib.Script(utxo.script).toHex(), + address: utxo.address != undefined ? new this.lib.Address(utxo.address).toString() : undefined + }; + } + + create(params: { + recipients: Array<{ address: string; amount: number | string }>; + utxos?: EveryUtxoType[]; + change?: string; + feeRate?: number | string; + fee?: number | string; + isSweep?: boolean; + replaceByFee?: boolean; + lockUntilDate?: number; + lockUntilBlock?: number; + }): string { + const { recipients, utxos = [], change, feeRate, fee, isSweep, replaceByFee, lockUntilDate, lockUntilBlock } = params; + const filteredUtxos = isSweep ? utxos : this.selectCoins(recipients, utxos, Number(fee)); + const btcUtxos = filteredUtxos.map(utxo => this.standardizeUtxo(utxo)); const tx = new this.lib.Transaction().from(btcUtxos); if (fee) { tx.fee(fee); @@ -62,7 +82,7 @@ export class BTCTxProvider { tx.change(change); } for (const recipient of recipients) { - tx.to(recipient.address, parseInt(recipient.amount)); + tx.to(recipient.address, Number(recipient.amount)); } if (replaceByFee && typeof tx.enableRBF === 'function') { tx.enableRBF(); @@ -79,7 +99,7 @@ export class BTCTxProvider { throw new Error('function getSignature not implemented for UTXO coins'); } - transformSignatureObject(params: { obj: any; sigtype?: number }) { + transformSignatureObject(params: { obj: any; sigtype?: number }): string { const { obj, sigtype } = params; const { v } = obj; let { r, s, i, nhashtype } = obj; @@ -107,7 +127,12 @@ export class BTCTxProvider { return new this.lib.crypto.Signature({ r, s, i, nhashtype }).toString(); } - applySignature(params: { tx: BitcoreLib.Transaction; signature: SignatureType; index: number; sigtype?: number }) { + applySignature(params: { + tx: BitcoreLib.Transaction; + signature: SignatureType; + index: number; + sigtype?: number; + }): BitcoreLib.Transaction { const { index, sigtype, tx } = params; let { signature } = params; assert(tx instanceof this.lib.Transaction, 'tx must be an instance of Transaction'); @@ -128,63 +153,75 @@ export class BTCTxProvider { return tx; } - getHash(params: { tx: string }) { + getHash(params: { tx: TransactionType }): string { const bitcoreTx = new this.lib.Transaction(params.tx); return bitcoreTx.hash; } - sign(params: { tx: string; keys: Array; utxos: any[]; pubkeys?: any[]; threshold?: number; opts: any }) { - const { tx, keys, pubkeys, threshold, opts } = params; + sign(params: { + tx: TransactionType; + keys: Key[]; + utxos: EveryUtxoType[]; + sigtype?: number; + pubkeys?: any[]; + threshold?: number; + opts: any; + }): string { + const { tx, keys, pubkeys, sigtype, threshold, opts } = params; const utxos = params.utxos || []; const bitcoreTx = new this.lib.Transaction(tx); + const btcUtxos = utxos.map(utxo => this.standardizeUtxo(utxo)); const applicableUtxos = this.getRelatedUtxos({ outputs: bitcoreTx.inputs, - utxos + utxos: btcUtxos }); - bitcoreTx.associateInputs(applicableUtxos, pubkeys, threshold, opts); + bitcoreTx.associateInputs(applicableUtxos.map(utxo => new this.lib.Transaction.UnspentOutput(utxo)), pubkeys, threshold, opts); const uniqePrivKeys = Object.values(keys.reduce((map, key) => { // Need to preserve (un)compressed property, so don't use key.privKey.toString(); const pk = new this.lib.PrivateKey(key.privKey); map[pk.publicKey.toString()] = pk; return map; }, {})); - const signedTx = bitcoreTx.sign(uniqePrivKeys).toString(); + const signedTx = bitcoreTx.sign(uniqePrivKeys, sigtype).toString(); return signedTx; } - getRelatedUtxos({ outputs, utxos }) { + getRelatedUtxos(params: { + outputs: BitcoreLib.Transaction.Input[]; + utxos: UtxoType[]; + }): UtxoType[] { + const { outputs, utxos } = params; const txids = outputs.map(output => output.toObject().prevTxId); - const applicableUtxos = utxos.filter(utxo => txids.includes(utxo.txid || utxo.mintTxid)); - return applicableUtxos.map(utxo => { - const btcUtxo = Object.assign({}, utxo, { - amount: utxo.value / Math.pow(10, 8), - txid: utxo.mintTxid, - outputIndex: utxo.mintIndex - }); - return new this.lib.Transaction.UnspentOutput(btcUtxo); - }); + return utxos.filter(utxo => txids.includes(utxo.txId)); } - getOutputsFromTx({ tx }) { - return tx.outputs.map(({ script, satoshis }) => { + getOutputsFromTx(params: { + tx: BitcoreLib.Transaction; + }): Array<{ address: string | BitcoreLib.Script; satoshis: number }> { + return params.tx.outputs.map(({ script, satoshis }) => { const address = script; return { address, satoshis }; }); } - getSigningAddresses({ tx, utxos }): string[] { + getSigningAddresses(params: { + tx: TransactionType; + utxos: EveryUtxoType[]; + }): (string | undefined)[] { + const { tx, utxos } = params; const bitcoreTx = new this.lib.Transaction(tx); + const btcUtxos = utxos.map(utxo => this.standardizeUtxo(utxo)); const applicableUtxos = this.getRelatedUtxos({ outputs: bitcoreTx.inputs, - utxos + utxos: btcUtxos }); return applicableUtxos.map(utxo => utxo.address); } getSighash(params: { - tx: string | BitcoreLib.Transaction; + tx: TransactionType; index: number; - utxos?: BitcoreLib.Transaction.UnspentOutput[]; + utxos?: EveryUtxoType[]; pubKey?: string | BitcoreLib.PublicKey | BitcoreLib.HDPublicKey; path?: string; sigtype?: number; @@ -204,7 +241,8 @@ export class BTCTxProvider { tx = new this.lib.Transaction(tx); } if (utxos) { - tx.associateInputs(utxos.map(this.lib.Transaction.UnspentOutput), pubKeys, threshold, opts); + const btcUtxos = utxos.map(utxo => this.standardizeUtxo(utxo)); + tx.associateInputs(btcUtxos.map(utxo => new this.lib.Transaction.UnspentOutput(utxo)), pubKeys, threshold, opts); } $.checkState(tx.inputs[index].output instanceof this.lib.Transaction.Output, 'Input must have all utxo info'); @@ -224,4 +262,41 @@ export class BTCTxProvider { } } -type SignatureType = BitcoreLib.Transaction.Signature | BitcoreLib.crypto.Signature | TssSig; \ No newline at end of file +type SignatureType = BitcoreLib.Transaction.Signature | BitcoreLib.crypto.Signature | TssSig; + +/** Transaction data that can be converted into a Transaction via Transaction(tx) */ +type TransactionType = BitcoreLib.Transaction | string | Buffer | object; + +/** + * Standard utxo type use for internal processing. + * Property names are from bitcore-lib's UnspentOutput. + * Note, UnspentOutput addresses and scripts are Address and Script classes respectively, + * here they are both strings. + */ +export type UtxoType = { + txId: string; + outputIndex: number; + satoshis: number; + script: string; + address?: string; +}; + +/** + * Utxo type for functions were the received utxo type is unknown. + * Could either be in the format of UnspentOutput, UnspentOutput.toObject, or from bitcore-node. + */ +export type EveryUtxoType = Partial; diff --git a/packages/crypto-wallet-core/src/transactions/doge/index.ts b/packages/crypto-wallet-core/src/transactions/doge/index.ts index b4e97280056..b8e38d9de8a 100644 --- a/packages/crypto-wallet-core/src/transactions/doge/index.ts +++ b/packages/crypto-wallet-core/src/transactions/doge/index.ts @@ -1,18 +1,18 @@ import BitcoreLibDoge from '@bitpay-labs/bitcore-lib-doge'; -import { BTCTxProvider } from '../btc'; +import { BTCTxProvider, EveryUtxoType } from '../btc'; export class DOGETxProvider extends BTCTxProvider { lib = BitcoreLibDoge; - create({ recipients, utxos = [], change, feeRate, fee = 20000 }) { - const filteredUtxos = this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos.map(utxo => { - const btcUtxo = Object.assign({}, utxo, { - amount: utxo.value / 1e8, - txid: utxo.mintTxid, - outputIndex: utxo.mintIndex - }); - return new this.lib.Transaction.UnspentOutput(btcUtxo); - }); + create(params: { + recipients: Array<{ address: string; amount: number }>; + utxos?: EveryUtxoType[]; + change?: string; + feeRate?: number | string; + fee?: number | string; + }): string { + const { recipients, utxos = [], change, feeRate, fee = 20000 } = params; + const filteredUtxos = this.selectCoins(recipients, utxos, Number(fee)); + const btcUtxos = filteredUtxos.map(utxo => this.standardizeUtxo(utxo)); const tx = new this.lib.Transaction().from(btcUtxos); if (fee) { tx.fee(fee); @@ -24,7 +24,7 @@ export class DOGETxProvider extends BTCTxProvider { tx.change(change); } for (const recipient of recipients) { - tx.to(recipient.address, parseInt(recipient.amount)); + tx.to(recipient.address, Number(recipient.amount)); } return tx.uncheckedSerialize(); } diff --git a/packages/crypto-wallet-core/src/transactions/ltc/index.ts b/packages/crypto-wallet-core/src/transactions/ltc/index.ts index 41cd1a5ef8b..2fdc4840592 100644 --- a/packages/crypto-wallet-core/src/transactions/ltc/index.ts +++ b/packages/crypto-wallet-core/src/transactions/ltc/index.ts @@ -1,18 +1,18 @@ import BitcoreLibLtc from '@bitpay-labs/bitcore-lib-ltc'; -import { BTCTxProvider } from '../btc'; +import { BTCTxProvider, EveryUtxoType } from '../btc'; export class LTCTxProvider extends BTCTxProvider { lib = BitcoreLibLtc; - create({ recipients, utxos = [], change, feeRate, fee = 20000 }) { - const filteredUtxos = this.selectCoins(recipients, utxos, fee); - const btcUtxos = filteredUtxos.map(utxo => { - const btcUtxo = Object.assign({}, utxo, { - amount: utxo.value / 1e8, - txid: utxo.mintTxid, - outputIndex: utxo.mintIndex - }); - return new this.lib.Transaction.UnspentOutput(btcUtxo); - }); + create(params: { + recipients: Array<{ address: string; amount: number }>; + utxos?: EveryUtxoType[]; + change?: string; + feeRate?: number | string; + fee?: number | string; + }): string { + const { recipients, utxos = [], change, feeRate, fee = 20000 } = params; + const filteredUtxos = this.selectCoins(recipients, utxos, Number(fee)); + const btcUtxos = filteredUtxos.map(utxo => this.standardizeUtxo(utxo)); const tx = new this.lib.Transaction().from(btcUtxos); if (fee) { tx.fee(fee); @@ -24,7 +24,7 @@ export class LTCTxProvider extends BTCTxProvider { tx.change(change); } for (const recipient of recipients) { - tx.to(recipient.address, parseInt(recipient.amount)); + tx.to(recipient.address, Number(recipient.amount)); } return tx.uncheckedSerialize(); } diff --git a/packages/crypto-wallet-core/test/transactions.test.ts b/packages/crypto-wallet-core/test/transactions.test.ts index 52f12d405d8..d6bf78d8d26 100644 --- a/packages/crypto-wallet-core/test/transactions.test.ts +++ b/packages/crypto-wallet-core/test/transactions.test.ts @@ -6,7 +6,13 @@ import bitcoreLibDoge from '@bitpay-labs/bitcore-lib-doge'; import bitcoreLibLtc from '@bitpay-labs/bitcore-lib-ltc'; import { Constants, Transactions } from '../src'; -describe('Transaction', function() { +describe('Transaction', function () { + const libs = { + BTC: bitcoreLib, + BCH: bitcoreLibCash, + DOGE: bitcoreLibDoge, + LTC: bitcoreLibLtc + }; describe('create', () => { it('should create a BTC tx', () => { const recipients = [{ address: 'mpNpzMoprLnSBu8CWDunNCYeJq3Mzdk59V', amount: 1e8 }]; @@ -45,6 +51,170 @@ describe('Transaction', function() { expect(signed).to.eq(expected); }); + describe('every utxo type: bitcore-node, UnspentOutput, and UnspentOutput.toObject', () => { + const keys = [{ + address: 'mnfnJx2xWWptYmBzck3rdE851Dtu9GaZ3F', + privKey: 'cSFjiifSbZ2hU4jTFwE993LCe2rkZGULCTGWTDWXzHvuXRKxpnc1' + }]; + + const bitcoreNodeUtxos = + [ + { + mintTxid: '643ec66d6c4cad4cbdb8ed2166b8078975e0af9bb7ff7e30d394f43b0d9f18ab', + mintIndex: 1, + mintHeight: 100, + value: 90_000, + script: '76a9144e744a19a009a9dd43a23a7c12045c83e82ac9d288ac', + address: 'mnfnJx2xWWptYmBzck3rdE851Dtu9GaZ3F', + sequenceNumber: 4294967294 + }, + { + mintTxid: '643ec66d6c4cad4cbdb8ed2166b8078975e0af9bb7ff7e30d394f43b0d9f18ab', + mintIndex: 0, + mintHeight: 100, + value: 30_000, + script: '76a9144e744a19a009a9dd43a23a7c12045c83e82ac9d288ac', + address: 'mnfnJx2xWWptYmBzck3rdE851Dtu9GaZ3F', + sequenceNumber: 4294967294 + } + ]; + const unspentOutputUtxos = [ + { + txId: '643ec66d6c4cad4cbdb8ed2166b8078975e0af9bb7ff7e30d394f43b0d9f18ab', + outputIndex: 1, + satoshis: 90_000, + script: '76a9144e744a19a009a9dd43a23a7c12045c83e82ac9d288ac' + }, + { + txId: '643ec66d6c4cad4cbdb8ed2166b8078975e0af9bb7ff7e30d394f43b0d9f18ab', + outputIndex: 0, + satoshis: 30_000, + script: '76a9144e744a19a009a9dd43a23a7c12045c83e82ac9d288ac' + } + ]; + const unspentOutputToObjectUtxos = [ + { + txid: '643ec66d6c4cad4cbdb8ed2166b8078975e0af9bb7ff7e30d394f43b0d9f18ab', + outputIndex: 1, + amount: 0.0009, + scriptPubKey: '76a9144e744a19a009a9dd43a23a7c12045c83e82ac9d288ac' + }, + { + txid: '643ec66d6c4cad4cbdb8ed2166b8078975e0af9bb7ff7e30d394f43b0d9f18ab', + vout: 0, + amount: 0.0003, + scriptPubKey: '76a9144e744a19a009a9dd43a23a7c12045c83e82ac9d288ac' + } + ]; + const utxoSet = [ + bitcoreNodeUtxos, + unspentOutputUtxos, + unspentOutputToObjectUtxos + ]; + + const recipients = [{ address: 'moVnNJpHHfssYJEnMTS5xXyGV8RhRQNRz5', amount: 100_000 }]; + for (const chain of ['BTC', 'BCH', 'DOGE', 'LTC']) { + let tx: string; + const lib = libs[chain]; + it(`should create a tx with every utxo type for ${chain}`, () => { + const txs = utxoSet.map(utxos => Transactions.create({ + chain, + recipients, + utxos + })); + + tx = txs[0]; + for (const _tx of txs.slice(1)) { + expect(tx, 'all transactions should be the same regardless of the utxo format').to.equal(_tx); + } + + let expectedTx: string; + if (chain === 'DOGE') { + expectedTx = '0100000002ab189f0d3bf494d3307effb79bafe0758907b86621edb8bd4cad4c6c6dc63e640100000000ffffffffab189f0d3bf494d3307effb79bafe0758907b86621edb8bd4cad4c6c6dc63e640000000000ffffffff01a0860100000000001976a91457884dcfe2ab46d3354a42d97333c95e5b80cf0188ac00000000'; + } else { + expectedTx = '0200000002ab189f0d3bf494d3307effb79bafe0758907b86621edb8bd4cad4c6c6dc63e640100000000ffffffffab189f0d3bf494d3307effb79bafe0758907b86621edb8bd4cad4c6c6dc63e640000000000ffffffff01a0860100000000001976a91457884dcfe2ab46d3354a42d97333c95e5b80cf0188ac00000000'; + } + expect(tx).to.equal(expectedTx); + }); + + it(`should sign a tx with every utxo type ${chain}`, () => { + const signedTxs = utxoSet.map(utxos => Transactions.sign({ + chain, + tx, + utxos, + keys, + sigtype: lib.crypto.Signature.SIGHASH_ALL + })); + const signedTx = signedTxs[0]; + for (const _signedTx of signedTxs.slice(1)) { + expect(signedTx, 'signed transactions should all be the same regardless of utxo type').to.equal(_signedTx); + } + + let expectedTx: string; + if (chain === 'DOGE') { + expectedTx = '0100000002ab189f0d3bf494d3307effb79bafe0758907b86621edb8bd4cad4c6c6dc63e64010000006a473044022015ceee1da23792e26cd1215c8327a35978c8b0494de9e69bab6d31ab3d0b56ad02205f9299162591672c6cde3d92d5819a11e1ad9f353a25d7a3ce494877ef27468901210321f2f13aed42db7257b64f77d574071a6e81e460ab3693eefb7482c12d1ff697ffffffffab189f0d3bf494d3307effb79bafe0758907b86621edb8bd4cad4c6c6dc63e64000000006b4830450221009f0b5dd0b4c0bd9bcf804e71020705eb9bda99a31cb2a4d123c364bdd394adeb022016939a51fb03c9efea4b7d53a7b90d7a3667fff71cd8365e366c81b1fdd0d3e001210321f2f13aed42db7257b64f77d574071a6e81e460ab3693eefb7482c12d1ff697ffffffff01a0860100000000001976a91457884dcfe2ab46d3354a42d97333c95e5b80cf0188ac00000000'; + } else { + expectedTx = '0200000002ab189f0d3bf494d3307effb79bafe0758907b86621edb8bd4cad4c6c6dc63e64010000006b483045022100c74ab4fe359e8efc73fc28dce989e28da47d3b45eb06ee305dfc0e675da8700802201076a067337a3c6b90fad2077a55154aa150460cb842bc86373026eb071a7ac901210321f2f13aed42db7257b64f77d574071a6e81e460ab3693eefb7482c12d1ff697ffffffffab189f0d3bf494d3307effb79bafe0758907b86621edb8bd4cad4c6c6dc63e64000000006b483045022100ed22907cc96b5367ef469225f7f718efa8fb4eb2dc5fbf3909777c5f024cf5e902206480082ad9b413a8a76128ab147287da30d907cfb1a159be814392fa1138012801210321f2f13aed42db7257b64f77d574071a6e81e460ab3693eefb7482c12d1ff697ffffffff01a0860100000000001976a91457884dcfe2ab46d3354a42d97333c95e5b80cf0188ac00000000'; + } + expect(signedTx).to.equal(expectedTx); + }); + + it(`should create valid sighashes for all utxo types ${chain}`, () => { + const bitcoreTx = lib.Transaction() + .from(unspentOutputUtxos) + .to(recipients[0].address, recipients[0].amount); + const signedTx = lib.Transaction() + .from(unspentOutputUtxos) + .to(recipients[0].address, recipients[0].amount); + + for (let index = 0; index < unspentOutputUtxos.length; index++) { + const sighashes: string[] = []; + for (const utxos of utxoSet) { + sighashes.push(Transactions.getSighash({ + chain, + tx, + utxos, + index, + sigtype: lib.crypto.Signature.SIGHASH_ALL + })); + } + + // getSighash should work without utxos if a complete lib transaction is provided + sighashes.push(Transactions.getSighash({ + chain, + tx: bitcoreTx, + index, + sigtype: lib.crypto.Signature.SIGHASH_ALL + })); + + const sighash = sighashes[0]; + for (const hash of sighashes) { + expect(sighash).to.equal(hash); + } + expect(sighash.length).to.equal(64); + const privateKey = new lib.PrivateKey(keys[0].privKey); + const publicKey = privateKey.toPublicKey(); + const signature = lib.crypto.ECDSA.sign(Buffer.from(sighash, 'hex'), privateKey); + signature.pubKey = publicKey; + + Transactions.applySignature({ + chain, + tx: signedTx, + signature, + index + }); + } + if (chain === 'DOGE') { + const serializedSignedTx = '0100000002ab189f0d3bf494d3307effb79bafe0758907b86621edb8bd4cad4c6c6dc63e64010000006a473044022015ceee1da23792e26cd1215c8327a35978c8b0494de9e69bab6d31ab3d0b56ad02205f9299162591672c6cde3d92d5819a11e1ad9f353a25d7a3ce494877ef27468901210321f2f13aed42db7257b64f77d574071a6e81e460ab3693eefb7482c12d1ff697ffffffffab189f0d3bf494d3307effb79bafe0758907b86621edb8bd4cad4c6c6dc63e64000000006b4830450221009f0b5dd0b4c0bd9bcf804e71020705eb9bda99a31cb2a4d123c364bdd394adeb022016939a51fb03c9efea4b7d53a7b90d7a3667fff71cd8365e366c81b1fdd0d3e001210321f2f13aed42db7257b64f77d574071a6e81e460ab3693eefb7482c12d1ff697ffffffff01a0860100000000001976a91457884dcfe2ab46d3354a42d97333c95e5b80cf0188ac00000000'; + expect(signedTx.serialize({ disableSmallFees: true, disableDustOutputs: true })).to.equal(serializedSignedTx); + } else { + const serializedSignedTx = '0200000002ab189f0d3bf494d3307effb79bafe0758907b86621edb8bd4cad4c6c6dc63e64010000006b483045022100c74ab4fe359e8efc73fc28dce989e28da47d3b45eb06ee305dfc0e675da8700802201076a067337a3c6b90fad2077a55154aa150460cb842bc86373026eb071a7ac901210321f2f13aed42db7257b64f77d574071a6e81e460ab3693eefb7482c12d1ff697ffffffffab189f0d3bf494d3307effb79bafe0758907b86621edb8bd4cad4c6c6dc63e64000000006b483045022100ed22907cc96b5367ef469225f7f718efa8fb4eb2dc5fbf3909777c5f024cf5e902206480082ad9b413a8a76128ab147287da30d907cfb1a159be814392fa1138012801210321f2f13aed42db7257b64f77d574071a6e81e460ab3693eefb7482c12d1ff697ffffffff01a0860100000000001976a91457884dcfe2ab46d3354a42d97333c95e5b80cf0188ac00000000'; + expect(signedTx.serialize()).to.equal(serializedSignedTx); + } + }); + } + }); + it('should sign a BTC opreturn tx', () => { const tx = '0200000001ab189f0d3bf494d3307effb79bafe0758907b86621edb8bd4cad4c6c6dc63e640100000000ffffffff0200000000000000000b6a096a07696f6e3a61626340420f00000000001976a91457884dcfe2ab46d3354a42d97333c95e5b80cf0188ac00000000'; @@ -1887,4 +2057,4 @@ describe('Transaction', function() { }); }); -}); \ No newline at end of file +});