Skip to content

Commit f9a739e

Browse files
committed
add payments p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh
1 parent 7756c5d commit f9a739e

File tree

18 files changed

+2696
-2
lines changed

18 files changed

+2696
-2
lines changed

src/payments/index.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const p2ms = require('./p2ms')
2+
const p2pk = require('./p2pk')
3+
const p2pkh = require('./p2pkh')
4+
const p2sh = require('./p2sh')
5+
const p2wpkh = require('./p2wpkh')
6+
const p2wsh = require('./p2wsh')
7+
8+
module.exports = {
9+
p2ms: p2ms,
10+
p2pk: p2pk,
11+
p2pkh: p2pkh,
12+
p2sh: p2sh,
13+
p2wpkh: p2wpkh,
14+
p2wsh: p2wsh
15+
}
16+
17+
// TODO
18+
// OP_RETURN
19+
// witness commitment

src/payments/lazy.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
function prop (object, name, f) {
2+
Object.defineProperty(object, name, {
3+
configurable: true,
4+
enumerable: true,
5+
get: function () {
6+
let value = f.call(this)
7+
this[name] = value
8+
return value
9+
},
10+
set: function (value) {
11+
Object.defineProperty(this, name, {
12+
configurable: true,
13+
enumerable: true,
14+
value: value,
15+
writable: true
16+
})
17+
}
18+
})
19+
}
20+
21+
function value (f) {
22+
let value
23+
return function () {
24+
if (value !== undefined) return value
25+
value = f()
26+
return value
27+
}
28+
}
29+
30+
module.exports = { prop, value }

src/payments/p2ms.js

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
let lazy = require('./lazy')
2+
let typef = require('typeforce')
3+
let OPS = require('bitcoin-ops')
4+
let ecc = require('tiny-secp256k1')
5+
6+
let bscript = require('../script')
7+
let BITCOIN_NETWORK = require('../networks').bitcoin
8+
let OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
9+
10+
function stacksEqual (a, b) {
11+
if (a.length !== b.length) return false
12+
13+
return a.every(function (x, i) {
14+
return x.equals(b[i])
15+
})
16+
}
17+
18+
// input: OP_0 [signatures ...]
19+
// output: m [pubKeys ...] n OP_CHECKMULTISIG
20+
function p2ms (a, opts) {
21+
if (
22+
!a.output &&
23+
!(a.pubkeys && a.m !== undefined)
24+
) throw new TypeError('Not enough data')
25+
opts = opts || { validate: true }
26+
27+
function isAcceptableSignature (x) {
28+
return bscript.isCanonicalScriptSignature(x) || (opts.allowIncomplete && (x === OPS.OP_0))
29+
}
30+
31+
typef({
32+
network: typef.maybe(typef.Object),
33+
m: typef.maybe(typef.Number),
34+
n: typef.maybe(typef.Number),
35+
output: typef.maybe(typef.Buffer),
36+
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)),
37+
38+
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)),
39+
input: typef.maybe(typef.Buffer)
40+
}, a)
41+
42+
let network = a.network || BITCOIN_NETWORK
43+
let o = { network }
44+
45+
let chunks
46+
let decoded = false
47+
function decode (output) {
48+
if (decoded) return
49+
decoded = true
50+
chunks = bscript.decompile(output)
51+
let om = chunks[0] - OP_INT_BASE
52+
let on = chunks[chunks.length - 2] - OP_INT_BASE
53+
o.m = om
54+
o.n = on
55+
o.pubkeys = chunks.slice(1, -2)
56+
}
57+
58+
lazy.prop(o, 'output', function () {
59+
if (!a.m) return
60+
if (!o.n) return
61+
if (!a.pubkeys) return
62+
return bscript.compile([].concat(
63+
OP_INT_BASE + a.m,
64+
a.pubkeys,
65+
OP_INT_BASE + o.n,
66+
OPS.OP_CHECKMULTISIG
67+
))
68+
})
69+
lazy.prop(o, 'm', function () {
70+
if (!o.output) return
71+
decode(o.output)
72+
return o.m
73+
})
74+
lazy.prop(o, 'n', function () {
75+
if (!o.pubkeys) return
76+
return o.pubkeys.length
77+
})
78+
lazy.prop(o, 'pubkeys', function () {
79+
if (!a.output) return
80+
decode(a.output)
81+
return o.pubkeys
82+
})
83+
lazy.prop(o, 'signatures', function () {
84+
if (!a.input) return
85+
return bscript.decompile(a.input).slice(1)
86+
})
87+
lazy.prop(o, 'input', function () {
88+
if (!a.signatures) return
89+
return bscript.compile([OPS.OP_0].concat(a.signatures))
90+
})
91+
lazy.prop(o, 'witness', function () {
92+
if (!o.input) return
93+
return []
94+
})
95+
96+
// extended validation
97+
if (opts.validate) {
98+
if (a.output) {
99+
decode(a.output)
100+
if (!typef.Number(chunks[0])) throw new TypeError('Output is invalid')
101+
if (!typef.Number(chunks[chunks.length - 2])) throw new TypeError('Output is invalid')
102+
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) throw new TypeError('Output is invalid')
103+
104+
if (
105+
o.m <= 0 ||
106+
o.n > 16 ||
107+
o.m > o.n ||
108+
o.n !== chunks.length - 3) throw new TypeError('Output is invalid')
109+
if (!o.pubkeys.every(x => ecc.isPoint(x))) throw new TypeError('Output is invalid')
110+
111+
if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch')
112+
if (a.n !== undefined && a.n !== o.n) throw new TypeError('n mismatch')
113+
if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys)) throw new TypeError('Pubkeys mismatch')
114+
}
115+
116+
if (a.pubkeys) {
117+
if (a.n !== undefined && a.n !== a.pubkeys.length) throw new TypeError('Pubkey count mismatch')
118+
o.n = a.pubkeys.length
119+
120+
if (o.n < o.m) throw new TypeError('Pubkey count cannot be less than m')
121+
}
122+
123+
if (a.signatures) {
124+
if (a.signatures.length < o.m) throw new TypeError('Not enough signatures provided')
125+
if (a.signatures.length > o.m) throw new TypeError('Too many signatures provided')
126+
}
127+
128+
if (a.input) {
129+
if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid')
130+
if (o.signatures.length === 0 || !o.signatures.every(isAcceptableSignature)) throw new TypeError('Input has invalid signature(s)')
131+
132+
if (a.signatures && !stacksEqual(a.signatures.equals(o.signatures))) throw new TypeError('Signature mismatch')
133+
if (a.m !== undefined && a.m !== a.signatures.length) throw new TypeError('Signature count mismatch')
134+
}
135+
}
136+
137+
return Object.assign(o, a)
138+
}
139+
140+
module.exports = p2ms

src/payments/p2pk.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
let lazy = require('./lazy')
2+
let typef = require('typeforce')
3+
let OPS = require('bitcoin-ops')
4+
let ecc = require('tiny-secp256k1')
5+
6+
let bscript = require('../script')
7+
let BITCOIN_NETWORK = require('../networks').bitcoin
8+
9+
// input: {signature}
10+
// output: {pubKey} OP_CHECKSIG
11+
function p2pk (a, opts) {
12+
if (
13+
!a.output &&
14+
!a.pubkey
15+
) throw new TypeError('Not enough data')
16+
opts = opts || { validate: true }
17+
18+
typef({
19+
network: typef.maybe(typef.Object),
20+
output: typef.maybe(typef.Buffer),
21+
pubkey: typef.maybe(ecc.isPoint),
22+
23+
signature: typef.maybe(bscript.isCanonicalScriptSignature),
24+
input: typef.maybe(typef.Buffer)
25+
}, a)
26+
27+
let _chunks = lazy.value(function () { return bscript.decompile(a.input) })
28+
29+
let network = a.network || BITCOIN_NETWORK
30+
let o = { network }
31+
32+
lazy.prop(o, 'output', function () {
33+
if (!a.pubkey) return
34+
return bscript.compile([
35+
a.pubkey,
36+
OPS.OP_CHECKSIG
37+
])
38+
})
39+
lazy.prop(o, 'pubkey', function () {
40+
if (!a.output) return
41+
return a.output.slice(1, -1)
42+
})
43+
lazy.prop(o, 'signature', function () {
44+
if (!a.input) return
45+
return _chunks()[0]
46+
})
47+
lazy.prop(o, 'input', function () {
48+
if (!a.signature) return
49+
return bscript.compile([a.signature])
50+
})
51+
lazy.prop(o, 'witness', function () {
52+
if (!o.input) return
53+
return []
54+
})
55+
56+
// extended validation
57+
if (opts.validate) {
58+
if (a.pubkey && a.output) {
59+
if (!a.pubkey.equals(o.pubkey)) throw new TypeError('Pubkey mismatch')
60+
}
61+
62+
if (a.output) {
63+
if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) throw new TypeError('Output is invalid')
64+
if (!ecc.isPoint(o.pubkey)) throw new TypeError('Output pubkey is invalid')
65+
}
66+
67+
if (a.signature) {
68+
if (a.input && !a.input.equals(o.input)) throw new TypeError('Input mismatch')
69+
}
70+
71+
if (a.input) {
72+
if (_chunks().length !== 1) throw new TypeError('Input is invalid')
73+
if (!bscript.isCanonicalScriptSignature(_chunks()[0])) throw new TypeError('Input has invalid signature')
74+
}
75+
}
76+
77+
return Object.assign(o, a)
78+
}
79+
80+
module.exports = p2pk

0 commit comments

Comments
 (0)