Skip to content

Commit f9fed3c

Browse files
committed
TxBuilder: adds fromTransaction impl. and basic tests
1 parent 7f62069 commit f9fed3c

File tree

3 files changed

+146
-34
lines changed

3 files changed

+146
-34
lines changed

src/transaction_builder.js

+104-20
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
var assert = require('assert')
22
var scripts = require('./scripts')
33

4-
var ECKey = require('./eckey')
5-
var Transaction = require('./transaction')
4+
var ECPubKey = require('./ecpubkey')
5+
var ECSignature = require('./ecsignature')
66
var Script = require('./script')
7+
var Transaction = require('./transaction')
78

89
function TransactionBuilder() {
910
this.prevOutMap = {}
@@ -14,7 +15,7 @@ function TransactionBuilder() {
1415
this.tx = new Transaction()
1516
}
1617

17-
TransactionBuilder.prototype.addInput = function(prevTx, index, prevOutScript, sequence) {
18+
TransactionBuilder.prototype.addInput = function(prevTx, index, sequence, prevOutScript) {
1819
var prevOutHash
1920

2021
if (typeof prevTx === 'string') {
@@ -103,7 +104,6 @@ TransactionBuilder.prototype.sign = function(index, privKey, redeemScript, hashT
103104
}
104105

105106
var input = this.signatures[index]
106-
107107
assert.equal(input.hashType, hashType, 'Inconsistent hashType')
108108
assert.deepEqual(input.redeemScript, redeemScript, 'Inconsistent redeemScript')
109109

@@ -130,29 +130,34 @@ TransactionBuilder.prototype.__build = function(allowIncomplete) {
130130

131131
var tx = this.tx.clone()
132132

133+
// Create script signatures from signature meta-data
133134
this.signatures.forEach(function(input, index) {
134135
var scriptSig
136+
var scriptType = input.scriptType
135137

136138
var signatures = input.signatures.map(function(signature) {
137139
return signature.toScriptSignature(input.hashType)
138140
})
139141

140-
if (input.scriptType === 'pubkeyhash') {
141-
var signature = signatures[0]
142-
var publicKey = input.pubKeys[0]
143-
scriptSig = scripts.pubKeyHashInput(signature, publicKey)
144-
145-
} else if (input.scriptType === 'multisig') {
146-
var redeemScript = allowIncomplete ? undefined : input.redeemScript
147-
scriptSig = scripts.multisigInput(signatures, redeemScript)
148-
149-
} else if (input.scriptType === 'pubkey') {
150-
var signature = signatures[0]
151-
scriptSig = scripts.pubKeyInput(signature)
152-
153-
} else {
154-
assert(false, input.scriptType + ' not supported')
155-
142+
switch (scriptType) {
143+
case 'pubkeyhash':
144+
var signature = signatures[0]
145+
var pubKey = input.pubKeys[0]
146+
scriptSig = scripts.pubKeyHashInput(signature, pubKey)
147+
148+
break
149+
case 'multisig':
150+
var redeemScript = allowIncomplete ? undefined : input.redeemScript
151+
scriptSig = scripts.multisigInput(signatures, redeemScript)
152+
153+
break
154+
case 'pubkey':
155+
var signature = signatures[0]
156+
scriptSig = scripts.pubKeyInput(signature)
157+
158+
break
159+
default:
160+
assert(false, scriptType + ' not supported')
156161
}
157162

158163
if (input.redeemScript) {
@@ -165,4 +170,83 @@ TransactionBuilder.prototype.__build = function(allowIncomplete) {
165170
return tx
166171
}
167172

173+
TransactionBuilder.fromTransaction = function(transaction) {
174+
var txb = new TransactionBuilder()
175+
176+
// Extract/add inputs
177+
transaction.ins.forEach(function(txin) {
178+
txb.addInput(txin.hash, txin.index, txin.sequence)
179+
})
180+
181+
// Extract/add outputs
182+
transaction.outs.forEach(function(txout) {
183+
txb.addOutput(txout.script, txout.value)
184+
})
185+
186+
// Extract/add signatures
187+
transaction.ins.forEach(function(txin) {
188+
// Ignore empty scripts
189+
if (txin.script.buffer.length === 0) return
190+
191+
var redeemScript
192+
var scriptSig = txin.script
193+
var scriptType = scripts.classifyInput(scriptSig)
194+
195+
// Re-classify if P2SH
196+
if (scriptType === 'scripthash') {
197+
redeemScript = Script.fromBuffer(scriptSig.chunks.slice(-1)[0])
198+
scriptSig = Script.fromChunks(scriptSig.chunks.slice(0, -1))
199+
200+
scriptType = scripts.classifyInput(scriptSig)
201+
assert.equal(scripts.classifyOutput(redeemScript), scriptType, 'Non-matching scriptSig and scriptPubKey in input')
202+
}
203+
204+
// Extract hashType, pubKeys and signatures
205+
var hashType, pubKeys, signatures
206+
207+
switch (scriptType) {
208+
case 'pubkeyhash':
209+
var parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0])
210+
var pubKey = ECPubKey.fromBuffer(scriptSig.chunks[1])
211+
212+
hashType = parsed.hashType
213+
pubKeys = [pubKey]
214+
signatures = [parsed.signature]
215+
216+
break
217+
case 'multisig':
218+
var scriptSigs = scriptSig.chunks.slice(1) // ignore OP_0
219+
var parsed = scriptSigs.map(function(scriptSig) {
220+
return ECSignature.parseScriptSignature(scriptSig)
221+
})
222+
223+
hashType = parsed[0].hashType
224+
pubKeys = []
225+
signatures = parsed.map(function(p) { return p.signature })
226+
227+
break
228+
case 'pubkey':
229+
var parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0])
230+
231+
hashType = parsed.hashType
232+
pubKeys = []
233+
signatures = [parsed.signature]
234+
235+
break
236+
default:
237+
assert(false, scriptType + ' not supported')
238+
}
239+
240+
txb.signatures[txin.index] = {
241+
hashType: hashType,
242+
pubKeys: pubKeys,
243+
redeemScript: redeemScript,
244+
scriptType: scriptType,
245+
signatures: signatures
246+
}
247+
})
248+
249+
return txb
250+
}
251+
168252
module.exports = TransactionBuilder

test/fixtures/transaction_builder.json

+19-5
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
{
55
"description": "pubKeyHash->pubKeyHash 1:1 transaction",
66
"txid": "bd641f4b0aa8bd70189ab45e935c4762f0e1c49f294b4779d79887937b7cf42e",
7+
"txhex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000006b483045022100a3b254e1c10b5d039f36c05f323995d6e5a367d98dd78a13d5bbc3991b35720e022022fccea3897d594de0689601fbd486588d5bfa6915be2386db0397ee9a6e80b601210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff0110270000000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000",
78
"inputs": [
89
{
910
"index": 0,
1011
"prevTx": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
11-
"privKeys": ["KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn"]
12+
"privKeys": [
13+
"KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn"
14+
]
1215
}
1316
],
1417
"outputs": [
@@ -21,12 +24,15 @@
2124
{
2225
"description": "pubKey->pubKeyHash 1:1 transaction",
2326
"txid": "a900dea133a3c51e9fe55d82bf4a4f50a4c3ac6e380c841f93651a076573320c",
27+
"txhex": "0100000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000494830450221009833abb3ab49d7004c06bcc79eafd6905ada3eee91f3376ad388548034acd9a702202e84dda6ef2678c82256afcfc459aaa68e179b2bb0e6b2dc3f1410e132c5e6c301ffffffff0100f90295000000001976a914aa4d7985c57e011a8b3dd8e0e5a73aaef41629c588ac00000000",
2428
"inputs": [
2529
{
2630
"index": 0,
2731
"prevTx": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
2832
"prevTxScript": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798 OP_CHECKSIG",
29-
"privKeys": ["KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn"]
33+
"privKeys": [
34+
"KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn"
35+
]
3036
}
3137
],
3238
"outputs": [
@@ -39,11 +45,15 @@
3945
{
4046
"description": "2-of-2 P2SH multisig -> pubKeyHash 1:1 Transaction",
4147
"txid": "8c500ce6eef6c78a10de923b68394cf31120151bdc4600e4b12de865defa9d24",
48+
"txhex": "0100000001cff58855426469d0ef16442ee9c644c4fb13832467bcbc3173168a7916f0714900000000fd1a0100473044022040039a3d0a806d6c2c0ac8a62f2467c979c897c945f3f11905b9c5ea76b4a88002200976f187f852f7d186e8e8aa39332092aa8a504b63a7ae3d0eca09ebea1497fd0147304402205522d1949d13347054bd5ea86cdcad2344f49628a935faaee8f5e744bd3ef87e022063a28ab077817222ccd7d5a70e77ed7274840b9ba8db5dd93a33bdd41813d548014c8752410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b84104c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a52aeffffffff0110270000000000001976a914faf1d99bf040ea9c7f8cc9f14ac6733ad75ce24688ac00000000",
4249
"inputs": [
4350
{
4451
"index": 0,
4552
"prevTx": "4971f016798a167331bcbc67248313fbc444c6e92e4416efd06964425588f5cf",
46-
"privKeys": ["91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx", "91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT"],
53+
"privKeys": [
54+
"91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx",
55+
"91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT"
56+
],
4757
"redeemScript": "OP_2 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 04c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee51ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a OP_2 OP_CHECKMULTISIG"
4858
}
4959
],
@@ -101,7 +111,9 @@
101111
{
102112
"index": 0,
103113
"prevTx": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
104-
"privKeys": ["KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn"]
114+
"privKeys": [
115+
"KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn"
116+
]
105117
},
106118
{
107119
"index": 1,
@@ -123,7 +135,9 @@
123135
"index": 0,
124136
"prevTx": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
125137
"prevTxScript": "OP_RETURN 06deadbeef03f895a2ad89fb6d696497af486cb7c644a27aa568c7a18dd06113401115185474",
126-
"privKeys": ["KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn"]
138+
"privKeys": [
139+
"KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn"
140+
]
127141
}
128142
],
129143
"outputs": [

test/transaction_builder.js

+23-9
Original file line numberDiff line numberDiff line change
@@ -33,33 +33,36 @@ describe('TransactionBuilder', function() {
3333
})
3434

3535
describe('addInput', function() {
36-
it('accepts a txHash and index', function() {
37-
var vin = txb.addInput(prevTxHash, 1)
36+
it('accepts a txHash, index [and sequence number]', function() {
37+
var vin = txb.addInput(prevTxHash, 1, 54)
3838
assert.equal(vin, 0)
3939

4040
var txin = txb.tx.ins[0]
4141
assert.equal(txin.hash, prevTxHash)
4242
assert.equal(txin.index, 1)
43+
assert.equal(txin.sequence, 54)
4344
assert.equal(txb.prevOutScripts[0], undefined)
4445
})
4546

46-
it('accepts a txHash, index and scriptPubKey', function() {
47-
var vin = txb.addInput(prevTxHash, 1, prevTx.outs[1].script)
47+
it('accepts a txHash, index [, sequence number and scriptPubKey]', function() {
48+
var vin = txb.addInput(prevTxHash, 1, 54, prevTx.outs[1].script)
4849
assert.equal(vin, 0)
4950

5051
var txin = txb.tx.ins[0]
5152
assert.equal(txin.hash, prevTxHash)
5253
assert.equal(txin.index, 1)
54+
assert.equal(txin.sequence, 54)
5355
assert.equal(txb.prevOutScripts[0], prevTx.outs[1].script)
5456
})
5557

56-
it('accepts a prevTx and index', function() {
57-
var vin = txb.addInput(prevTx, 1)
58+
it('accepts a prevTx, index [and sequence number]', function() {
59+
var vin = txb.addInput(prevTx, 1, 54)
5860
assert.equal(vin, 0)
5961

6062
var txin = txb.tx.ins[0]
6163
assert.deepEqual(txin.hash, prevTxHash)
6264
assert.equal(txin.index, 1)
65+
assert.equal(txin.sequence, 54)
6366
assert.equal(txb.prevOutScripts[0], prevTx.outs[1].script)
6467
})
6568

@@ -70,7 +73,7 @@ describe('TransactionBuilder', function() {
7073

7174
it('throws if prevOutScript is not supported', function() {
7275
assert.throws(function() {
73-
txb.addInput(prevTxHash, 0, Script.EMPTY)
76+
txb.addInput(prevTxHash, 0, undefined, Script.EMPTY)
7477
}, /PrevOutScript not supported \(nonstandard\)/)
7578
})
7679

@@ -153,7 +156,7 @@ describe('TransactionBuilder', function() {
153156
prevTxScript = Script.fromASM(input.prevTxScript)
154157
}
155158

156-
txb.addInput(input.prevTx, input.index, prevTxScript)
159+
txb.addInput(input.prevTx, input.index, input.sequence, prevTxScript)
157160
})
158161

159162
f.outputs.forEach(function(output) {
@@ -191,7 +194,7 @@ describe('TransactionBuilder', function() {
191194
prevTxScript = Script.fromASM(input.prevTxScript)
192195
}
193196

194-
txb.addInput(input.prevTx, input.index, prevTxScript)
197+
txb.addInput(input.prevTx, input.index, input.sequence, prevTxScript)
195198
})
196199

197200
f.outputs.forEach(function(output) {
@@ -220,4 +223,15 @@ describe('TransactionBuilder', function() {
220223
})
221224
})
222225
})
226+
227+
describe('fromTransaction', function() {
228+
fixtures.valid.build.forEach(function(f) {
229+
it('builds the correct TransactionBuilder for ' + f.description, function() {
230+
var tx = Transaction.fromHex(f.txhex)
231+
var txb = TransactionBuilder.fromTransaction(tx)
232+
233+
assert.equal(txb.build().toHex(), f.txhex)
234+
})
235+
})
236+
})
223237
})

0 commit comments

Comments
 (0)