From 19a33f7da868a65434c9dc486478f732ff771faf Mon Sep 17 00:00:00 2001 From: junderw Date: Thu, 18 Jul 2019 14:20:44 +0900 Subject: [PATCH] Add comments and remove fromTransaction --- src/psbt.js | 76 +++++++++++++++++++++++++++----------- test/fixtures/psbt.json | 6 --- test/psbt.js | 14 ------- ts_src/psbt.ts | 81 ++++++++++++++++++++++++++++------------- types/psbt.d.ts | 37 +++++++++++++++++-- 5 files changed, 144 insertions(+), 70 deletions(-) diff --git a/src/psbt.js b/src/psbt.js index 97e9e9d..a3191b4 100644 --- a/src/psbt.js +++ b/src/psbt.js @@ -11,10 +11,54 @@ const networks_1 = require('./networks'); const payments = require('./payments'); const bscript = require('./script'); const transaction_1 = require('./transaction'); +/** + * These are the default arguments for a Psbt instance. + */ const DEFAULT_OPTS = { + /** + * A bitcoinjs Network object. This is only used if you pass an `address` + * parameter to addOutput. Otherwise it is not needed and can be left default. + */ network: networks_1.bitcoin, + /** + * When extractTransaction is called, the fee rate is checked. + * THIS IS NOT TO BE RELIED ON. + * It is only here as a last ditch effort to prevent sending a 500 BTC fee etc. + */ maximumFeeRate: 5000, }; +/** + * Psbt class can parse and generate a PSBT binary based off of the BIP174. + * There are 6 roles that this class fulfills. (Explained in BIP174) + * + * Creator: This can be done with `new Psbt()` + * Updater: This can be done with `psbt.addInput(input)`, `psbt.addInputs(inputs)`, + * `psbt.addOutput(output)`, `psbt.addOutputs(outputs)` when you are looking to + * add new inputs and outputs to the PSBT, and `psbt.updateGlobal(itemObject)`, + * `psbt.updateInput(itemObject)`, `psbt.updateOutput(itemObject)` + * addInput requires hash: Buffer | string; and index: number; as attributes + * and can also include any attributes that are used in updateInput method. + * addOutput requires script: Buffer; and value: number; and likewise can include + * data for updateOutput. + * For a list of what attributes should be what types. Check the bip174 library. + * Also, check the integration tests for some examples of usage. + * Signer: There are a few methods. sign and signAsync, which will search all input + * information for your pubkey or pubkeyhash, and only sign inputs where it finds + * your info. Or you can explicitly sign a specific input with signInput and + * signInputAsync. For the async methods you can create a SignerAsync object + * and use something like a hardware wallet to sign with. (You must implement this) + * Combiner: psbts can be combined easily with `psbt.combine(psbt2, psbt3, psbt4 ...)` + * the psbt calling combine will always have precedence when a conflict occurs. + * Combine checks if the internal bitcoin transaction is the same, so be sure that + * all sequences, version, locktime, etc. are the same before combining. + * Input Finalizer: This role is fairly important. Not only does it need to construct + * the input scriptSigs and witnesses, but it SHOULD verify the signatures etc. + * Before running `psbt.finalizeAllInputs()` please run `psbt.validateAllSignatures()` + * Running any finalize method will delete any data in the input(s) that are no longer + * needed due to the finalized scripts containing the information. + * Transaction Extractor: This role will perform some checks before returning a + * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. + */ class Psbt { constructor(opts = {}, data = new bip174_1.Psbt(new PsbtTransaction())) { this.data = data; @@ -55,25 +99,6 @@ class Psbt { dpew(this, '__CACHE', false, true); dpew(this, 'opts', false, true); } - static fromTransaction(txBuf, opts = {}) { - const tx = new PsbtTransaction(txBuf); - checkTxEmpty(tx.tx); - const psbtBase = new bip174_1.Psbt(tx); - const psbt = new Psbt(opts, psbtBase); - psbt.__CACHE.__TX = tx.tx; - checkTxForDupeIns(tx.tx, psbt.__CACHE); - let inputCount = tx.tx.ins.length; - let outputCount = tx.tx.outs.length; - while (inputCount > 0) { - psbtBase.inputs.push({}); - inputCount--; - } - while (outputCount > 0) { - psbtBase.outputs.push({}); - outputCount--; - } - return psbt; - } static fromBase64(data, opts = {}) { const buffer = Buffer.from(data, 'base64'); return this.fromBuffer(buffer, opts); @@ -418,13 +443,20 @@ class Psbt { } } exports.Psbt = Psbt; +/** + * This function is needed to pass to the bip174 base class's fromBuffer. + * It takes the "transaction buffer" portion of the psbt buffer and returns a + * Transaction (From the bip174 library) interface. + */ const transactionFromBuffer = buffer => new PsbtTransaction(buffer); +/** + * This class implements the Transaction interface from bip174 library. + * It contains a bitcoinjs-lib Transaction object. + */ class PsbtTransaction { constructor(buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0])) { this.tx = transaction_1.Transaction.fromBuffer(buffer); - if (this.tx.ins.some(input => input.script.length !== 0)) { - throw new Error('Format Error: Transaction ScriptSigs are not empty'); - } + checkTxEmpty(this.tx); Object.defineProperty(this, 'tx', { enumerable: false, writable: true, diff --git a/test/fixtures/psbt.json b/test/fixtures/psbt.json index a29bc8b..8d83e9d 100644 --- a/test/fixtures/psbt.json +++ b/test/fixtures/psbt.json @@ -480,12 +480,6 @@ "result": "cHNidP8BAKYCAAAAAlwKQ3suPWwEJ/zQ9sZsIioOcHKU1KoLMxlMNSXVIkEWAAAAAAD/////YYDJMap+mYgbTrCNAdpWHN+EkKvl+XYao/6co/EQfwMAAAAAAP////8CkF8BAAAAAAAWABRnPBAmVHz2HL+8/1U+QG5L2thjmjhKAAAAAAAAIgAg700yfFRyhWzQnPHIUb/XQqsjlpf4A0uw682pCVWuQ8IAAAAAAAEBKzB1AAAAAAAAIgAgth9oE4cDfC5aV58VgkW5CptHsIxppYzJV8C5kT6aTo8BCG4CSDBFAiEAs7TFGm6o/dpWSb4M/KSu2p7p881KA1uWn0r0wDCa0ckCIAdXlm9k3xWLj2f+j0hIXK+0ew9dc8qoHEL2babYeWliASMhA182CcWcQ9L2zg9j8jlt7OlaIATNDgNFMBKB1J14wzTprAABASuAOAEAAAAAACIAILYfaBOHA3wuWlefFYJFuQqbR7CMaaWMyVfAuZE+mk6PAQhuAkgwRQIhALpc3q2mj2ZiVl6PgFdJKHkPUrpogsF9DhYFZdSshLHrAiBVrEVSzPzLn3EnVXixnWpqsdf2ln4wmYspuXZlJp2KygEjIQNfNgnFnEPS9s4PY/I5bezpWiAEzQ4DRTASgdSdeMM06awAAAA=" } ], - "fromTransaction": [ - { - "transaction": "020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000", - "result": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAAAAAA=" - } - ], "validateSignatures": { "psbt": "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", "index": 0, diff --git a/test/psbt.js b/test/psbt.js index e52fae2..1837760 100644 --- a/test/psbt.js +++ b/test/psbt.js @@ -309,15 +309,6 @@ describe(`Psbt`, () => { }) }) - describe('fromTransaction', () => { - fixtures.fromTransaction.forEach(f => { - it('Creates the expected PSBT from a transaction buffer', () => { - const psbt = Psbt.fromTransaction(Buffer.from(f.transaction, 'hex')) - assert.strictEqual(psbt.toBase64(), f.result) - }) - }) - }) - describe('addInput', () => { fixtures.addInput.checks.forEach(f => { it(f.description, () => { @@ -521,11 +512,6 @@ describe(`Psbt`, () => { }) describe('Method return types', () => { - it('fromTransaction returns Psbt type (not base class)', () => { - const psbt = Psbt.fromTransaction(Buffer.from([2,0,0,0,0,0,0,0,0,0])); - assert.strictEqual(psbt instanceof Psbt, true); - assert.ok(psbt.__CACHE.__TX); - }) it('fromBuffer returns Psbt type (not base class)', () => { const psbt = Psbt.fromBuffer(Buffer.from( '70736274ff01000a01000000000000000000000000', 'hex' //cHNidP8BAAoBAAAAAAAAAAAAAAAA diff --git a/ts_src/psbt.ts b/ts_src/psbt.ts index 0c20bc2..97f615f 100644 --- a/ts_src/psbt.ts +++ b/ts_src/psbt.ts @@ -26,32 +26,56 @@ import * as payments from './payments'; import * as bscript from './script'; import { Output, Transaction } from './transaction'; +/** + * These are the default arguments for a Psbt instance. + */ const DEFAULT_OPTS: PsbtOpts = { + /** + * A bitcoinjs Network object. This is only used if you pass an `address` + * parameter to addOutput. Otherwise it is not needed and can be left default. + */ network: btcNetwork, + /** + * When extractTransaction is called, the fee rate is checked. + * THIS IS NOT TO BE RELIED ON. + * It is only here as a last ditch effort to prevent sending a 500 BTC fee etc. + */ maximumFeeRate: 5000, // satoshi per byte }; +/** + * Psbt class can parse and generate a PSBT binary based off of the BIP174. + * There are 6 roles that this class fulfills. (Explained in BIP174) + * + * Creator: This can be done with `new Psbt()` + * Updater: This can be done with `psbt.addInput(input)`, `psbt.addInputs(inputs)`, + * `psbt.addOutput(output)`, `psbt.addOutputs(outputs)` when you are looking to + * add new inputs and outputs to the PSBT, and `psbt.updateGlobal(itemObject)`, + * `psbt.updateInput(itemObject)`, `psbt.updateOutput(itemObject)` + * addInput requires hash: Buffer | string; and index: number; as attributes + * and can also include any attributes that are used in updateInput method. + * addOutput requires script: Buffer; and value: number; and likewise can include + * data for updateOutput. + * For a list of what attributes should be what types. Check the bip174 library. + * Also, check the integration tests for some examples of usage. + * Signer: There are a few methods. sign and signAsync, which will search all input + * information for your pubkey or pubkeyhash, and only sign inputs where it finds + * your info. Or you can explicitly sign a specific input with signInput and + * signInputAsync. For the async methods you can create a SignerAsync object + * and use something like a hardware wallet to sign with. (You must implement this) + * Combiner: psbts can be combined easily with `psbt.combine(psbt2, psbt3, psbt4 ...)` + * the psbt calling combine will always have precedence when a conflict occurs. + * Combine checks if the internal bitcoin transaction is the same, so be sure that + * all sequences, version, locktime, etc. are the same before combining. + * Input Finalizer: This role is fairly important. Not only does it need to construct + * the input scriptSigs and witnesses, but it SHOULD verify the signatures etc. + * Before running `psbt.finalizeAllInputs()` please run `psbt.validateAllSignatures()` + * Running any finalize method will delete any data in the input(s) that are no longer + * needed due to the finalized scripts containing the information. + * Transaction Extractor: This role will perform some checks before returning a + * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. + */ export class Psbt { - static fromTransaction(txBuf: Buffer, opts: PsbtOptsOptional = {}): Psbt { - const tx = new PsbtTransaction(txBuf); - checkTxEmpty(tx.tx); - const psbtBase = new PsbtBase(tx); - const psbt = new Psbt(opts, psbtBase); - psbt.__CACHE.__TX = tx.tx; - checkTxForDupeIns(tx.tx, psbt.__CACHE); - let inputCount = tx.tx.ins.length; - let outputCount = tx.tx.outs.length; - while (inputCount > 0) { - psbtBase.inputs.push({}); - inputCount--; - } - while (outputCount > 0) { - psbtBase.outputs.push({}); - outputCount--; - } - return psbt; - } - static fromBase64(data: string, opts: PsbtOptsOptional = {}): Psbt { const buffer = Buffer.from(data, 'base64'); return this.fromBuffer(buffer, opts); @@ -356,7 +380,7 @@ export class Psbt { } signAsync( - keyPair: SignerAsync, + keyPair: Signer | SignerAsync, sighashTypes: number[] = [Transaction.SIGHASH_ALL], ): Promise { return new Promise( @@ -419,7 +443,7 @@ export class Psbt { signInputAsync( inputIndex: number, - keyPair: SignerAsync, + keyPair: Signer | SignerAsync, sighashTypes: number[] = [Transaction.SIGHASH_ALL], ): Promise { return new Promise( @@ -524,17 +548,24 @@ interface PsbtOpts { maximumFeeRate: number; } +/** + * This function is needed to pass to the bip174 base class's fromBuffer. + * It takes the "transaction buffer" portion of the psbt buffer and returns a + * Transaction (From the bip174 library) interface. + */ const transactionFromBuffer: TransactionFromBuffer = ( buffer: Buffer, ): ITransaction => new PsbtTransaction(buffer); +/** + * This class implements the Transaction interface from bip174 library. + * It contains a bitcoinjs-lib Transaction object. + */ class PsbtTransaction implements ITransaction { tx: Transaction; constructor(buffer: Buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0])) { this.tx = Transaction.fromBuffer(buffer); - if (this.tx.ins.some(input => input.script.length !== 0)) { - throw new Error('Format Error: Transaction ScriptSigs are not empty'); - } + checkTxEmpty(this.tx); Object.defineProperty(this, 'tx', { enumerable: false, writable: true, diff --git a/types/psbt.d.ts b/types/psbt.d.ts index e537656..c3a12d0 100644 --- a/types/psbt.d.ts +++ b/types/psbt.d.ts @@ -4,9 +4,40 @@ import { KeyValue, PsbtGlobalUpdate, PsbtInputUpdate, PsbtOutputUpdate, Transact import { Signer, SignerAsync } from './ecpair'; import { Network } from './networks'; import { Transaction } from './transaction'; +/** + * Psbt class can parse and generate a PSBT binary based off of the BIP174. + * There are 6 roles that this class fulfills. (Explained in BIP174) + * + * Creator: This can be done with `new Psbt()` + * Updater: This can be done with `psbt.addInput(input)`, `psbt.addInputs(inputs)`, + * `psbt.addOutput(output)`, `psbt.addOutputs(outputs)` when you are looking to + * add new inputs and outputs to the PSBT, and `psbt.updateGlobal(itemObject)`, + * `psbt.updateInput(itemObject)`, `psbt.updateOutput(itemObject)` + * addInput requires hash: Buffer | string; and index: number; as attributes + * and can also include any attributes that are used in updateInput method. + * addOutput requires script: Buffer; and value: number; and likewise can include + * data for updateOutput. + * For a list of what attributes should be what types. Check the bip174 library. + * Also, check the integration tests for some examples of usage. + * Signer: There are a few methods. sign and signAsync, which will search all input + * information for your pubkey or pubkeyhash, and only sign inputs where it finds + * your info. Or you can explicitly sign a specific input with signInput and + * signInputAsync. For the async methods you can create a SignerAsync object + * and use something like a hardware wallet to sign with. (You must implement this) + * Combiner: psbts can be combined easily with `psbt.combine(psbt2, psbt3, psbt4 ...)` + * the psbt calling combine will always have precedence when a conflict occurs. + * Combine checks if the internal bitcoin transaction is the same, so be sure that + * all sequences, version, locktime, etc. are the same before combining. + * Input Finalizer: This role is fairly important. Not only does it need to construct + * the input scriptSigs and witnesses, but it SHOULD verify the signatures etc. + * Before running `psbt.finalizeAllInputs()` please run `psbt.validateAllSignatures()` + * Running any finalize method will delete any data in the input(s) that are no longer + * needed due to the finalized scripts containing the information. + * Transaction Extractor: This role will perform some checks before returning a + * Transaction object. Such as fee rate not being larger than maximumFeeRate etc. + */ export declare class Psbt { readonly data: PsbtBase; - static fromTransaction(txBuf: Buffer, opts?: PsbtOptsOptional): Psbt; static fromBase64(data: string, opts?: PsbtOptsOptional): Psbt; static fromHex(data: string, opts?: PsbtOptsOptional): Psbt; static fromBuffer(buffer: Buffer, opts?: PsbtOptsOptional): Psbt; @@ -32,9 +63,9 @@ export declare class Psbt { validateAllSignatures(): boolean; validateSignatures(inputIndex: number, pubkey?: Buffer): boolean; sign(keyPair: Signer, sighashTypes?: number[]): this; - signAsync(keyPair: SignerAsync, sighashTypes?: number[]): Promise; + signAsync(keyPair: Signer | SignerAsync, sighashTypes?: number[]): Promise; signInput(inputIndex: number, keyPair: Signer, sighashTypes?: number[]): this; - signInputAsync(inputIndex: number, keyPair: SignerAsync, sighashTypes?: number[]): Promise; + signInputAsync(inputIndex: number, keyPair: Signer | SignerAsync, sighashTypes?: number[]): Promise; toBuffer(): Buffer; toHex(): string; toBase64(): string;