@bsv/sdk 1.1.22 → 1.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/compat/BSM.js +10 -3
- package/dist/cjs/src/compat/BSM.js.map +1 -1
- package/dist/cjs/src/primitives/Curve.js +7 -7
- package/dist/cjs/src/primitives/Curve.js.map +1 -1
- package/dist/cjs/src/primitives/ECDSA.js +394 -71
- package/dist/cjs/src/primitives/ECDSA.js.map +1 -1
- package/dist/cjs/src/primitives/Point.js +103 -23
- package/dist/cjs/src/primitives/Point.js.map +1 -1
- package/dist/cjs/src/primitives/TransactionSignature.js +4 -3
- package/dist/cjs/src/primitives/TransactionSignature.js.map +1 -1
- package/dist/cjs/src/primitives/utils.js +53 -16
- package/dist/cjs/src/primitives/utils.js.map +1 -1
- package/dist/cjs/src/script/Spend.js +6 -43
- package/dist/cjs/src/script/Spend.js.map +1 -1
- package/dist/cjs/src/script/templates/P2PKH.js +2 -2
- package/dist/cjs/src/script/templates/P2PKH.js.map +1 -1
- package/dist/cjs/src/script/templates/RPuzzle.js +2 -2
- package/dist/cjs/src/script/templates/RPuzzle.js.map +1 -1
- package/dist/cjs/src/transaction/Transaction.js +79 -65
- package/dist/cjs/src/transaction/Transaction.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/src/compat/BSM.js +10 -3
- package/dist/esm/src/compat/BSM.js.map +1 -1
- package/dist/esm/src/primitives/Curve.js +7 -7
- package/dist/esm/src/primitives/Curve.js.map +1 -1
- package/dist/esm/src/primitives/ECDSA.js +394 -71
- package/dist/esm/src/primitives/ECDSA.js.map +1 -1
- package/dist/esm/src/primitives/Point.js +103 -23
- package/dist/esm/src/primitives/Point.js.map +1 -1
- package/dist/esm/src/primitives/TransactionSignature.js +4 -3
- package/dist/esm/src/primitives/TransactionSignature.js.map +1 -1
- package/dist/esm/src/primitives/utils.js +51 -15
- package/dist/esm/src/primitives/utils.js.map +1 -1
- package/dist/esm/src/script/Spend.js +5 -42
- package/dist/esm/src/script/Spend.js.map +1 -1
- package/dist/esm/src/script/templates/P2PKH.js +2 -2
- package/dist/esm/src/script/templates/P2PKH.js.map +1 -1
- package/dist/esm/src/script/templates/RPuzzle.js +2 -2
- package/dist/esm/src/script/templates/RPuzzle.js.map +1 -1
- package/dist/esm/src/transaction/Transaction.js +79 -65
- package/dist/esm/src/transaction/Transaction.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/types/src/compat/BSM.d.ts +3 -2
- package/dist/types/src/compat/BSM.d.ts.map +1 -1
- package/dist/types/src/primitives/ECDSA.d.ts.map +1 -1
- package/dist/types/src/primitives/Point.d.ts +5 -0
- package/dist/types/src/primitives/Point.d.ts.map +1 -1
- package/dist/types/src/primitives/TransactionSignature.d.ts.map +1 -1
- package/dist/types/src/primitives/utils.d.ts +1 -0
- package/dist/types/src/primitives/utils.d.ts.map +1 -1
- package/dist/types/src/script/Spend.d.ts.map +1 -1
- package/dist/types/src/script/templates/P2PKH.d.ts +1 -1
- package/dist/types/src/script/templates/P2PKH.d.ts.map +1 -1
- package/dist/types/src/script/templates/RPuzzle.d.ts +1 -1
- package/dist/types/src/script/templates/RPuzzle.d.ts.map +1 -1
- package/dist/types/src/transaction/Transaction.d.ts.map +1 -1
- package/dist/types/src/transaction/TransactionInput.d.ts +1 -1
- package/dist/types/src/transaction/TransactionInput.d.ts.map +1 -1
- package/dist/types/tsconfig.types.tsbuildinfo +1 -1
- package/docs/compat.md +10 -4
- package/docs/primitives.md +48 -12
- package/docs/script.md +4 -4
- package/docs/transaction.md +2 -1
- package/package.json +1 -1
- package/src/compat/BSM.ts +10 -3
- package/src/compat/__tests/BSM.test.ts +7 -2
- package/src/primitives/Curve.ts +7 -7
- package/src/primitives/ECDSA.ts +485 -75
- package/src/primitives/Point.ts +110 -25
- package/src/primitives/TransactionSignature.ts +4 -3
- package/src/primitives/utils.ts +58 -11
- package/src/script/Spend.ts +5 -48
- package/src/script/templates/P2PKH.ts +7 -7
- package/src/script/templates/RPuzzle.ts +8 -8
- package/src/transaction/Transaction.ts +93 -68
- package/src/transaction/TransactionInput.ts +1 -1
- package/src/transaction/__tests/Transaction.benchmarks.test.ts +222 -0
package/src/primitives/Point.ts
CHANGED
|
@@ -17,6 +17,10 @@ import ReductionContext from './ReductionContext.js'
|
|
|
17
17
|
* @property inf - Flag to record if the point is at infinity in the Elliptic Curve.
|
|
18
18
|
*/
|
|
19
19
|
export default class Point extends BasePoint {
|
|
20
|
+
private static readonly red: any = new ReductionContext('k256')
|
|
21
|
+
private static readonly a: BigNumber = new BigNumber(0).toRed(Point.red)
|
|
22
|
+
private static readonly b: BigNumber = new BigNumber(7).toRed(Point.red)
|
|
23
|
+
private static readonly zero: BigNumber = new BigNumber(0).toRed(Point.red)
|
|
20
24
|
x: BigNumber | null
|
|
21
25
|
y: BigNumber | null
|
|
22
26
|
inf: boolean
|
|
@@ -59,7 +63,7 @@ export default class Point extends BasePoint {
|
|
|
59
63
|
|
|
60
64
|
return res
|
|
61
65
|
} else if ((bytes[0] === 0x02 || bytes[0] === 0x03) &&
|
|
62
|
-
|
|
66
|
+
bytes.length - 1 === len) {
|
|
63
67
|
return Point.fromX(bytes.slice(1, 1 + len), bytes[0] === 0x03)
|
|
64
68
|
}
|
|
65
69
|
throw new Error('Unknown point format')
|
|
@@ -87,6 +91,13 @@ export default class Point extends BasePoint {
|
|
|
87
91
|
return Point.fromDER(bytes)
|
|
88
92
|
}
|
|
89
93
|
|
|
94
|
+
static redSqrtOptimized (y2: BigNumber): BigNumber {
|
|
95
|
+
const red = Point.red
|
|
96
|
+
const p = red.m // The modulus
|
|
97
|
+
const exponent = p.addn(1).iushrn(2) // (p + 1) / 4
|
|
98
|
+
return y2.redPow(exponent)
|
|
99
|
+
}
|
|
100
|
+
|
|
90
101
|
/**
|
|
91
102
|
* Generates a point from an x coordinate and a boolean indicating whether the corresponding
|
|
92
103
|
* y coordinate is odd.
|
|
@@ -102,34 +113,108 @@ export default class Point extends BasePoint {
|
|
|
102
113
|
* const xCoordinate = new BigNumber('10');
|
|
103
114
|
* const point = Point.fromX(xCoordinate, true);
|
|
104
115
|
*/
|
|
105
|
-
|
|
106
116
|
static fromX (x: BigNumber | number | number[] | string, odd: boolean): Point {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
117
|
+
if (typeof BigInt === 'function') {
|
|
118
|
+
function mod (a: bigint, n: bigint): bigint {
|
|
119
|
+
return ((a % n) + n) % n
|
|
120
|
+
}
|
|
121
|
+
function modPow (base: bigint, exponent: bigint, modulus: bigint): bigint {
|
|
122
|
+
let result = BigInt(1)
|
|
123
|
+
base = mod(base, modulus)
|
|
124
|
+
while (exponent > BigInt(0)) {
|
|
125
|
+
if ((exponent & BigInt(1)) === BigInt(1)) {
|
|
126
|
+
result = mod(result * base, modulus)
|
|
127
|
+
}
|
|
128
|
+
exponent >>= BigInt(1)
|
|
129
|
+
base = mod(base * base, modulus)
|
|
130
|
+
}
|
|
131
|
+
return result
|
|
132
|
+
}
|
|
133
|
+
function sqrtMod (a: bigint, p: bigint): bigint | null {
|
|
134
|
+
const exponent = (p + BigInt(1)) >> BigInt(2) // Precomputed exponent
|
|
135
|
+
const sqrtCandidate = modPow(a, exponent, p)
|
|
136
|
+
if (mod(sqrtCandidate * sqrtCandidate, p) === mod(a, p)) {
|
|
137
|
+
return sqrtCandidate
|
|
138
|
+
} else {
|
|
139
|
+
// No square root exists
|
|
140
|
+
return null
|
|
141
|
+
}
|
|
142
|
+
}
|
|
118
143
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
144
|
+
// Curve parameters for secp256k1
|
|
145
|
+
const p = BigInt(
|
|
146
|
+
'0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F'
|
|
147
|
+
)
|
|
148
|
+
const a = BigInt(0)
|
|
149
|
+
const b = BigInt(7)
|
|
150
|
+
|
|
151
|
+
// Convert x to BigInt
|
|
152
|
+
let xBigInt: bigint
|
|
153
|
+
if (x instanceof BigNumber) {
|
|
154
|
+
xBigInt = BigInt('0x' + x.toString(16))
|
|
155
|
+
} else if (typeof x === 'string') {
|
|
156
|
+
xBigInt = BigInt('0x' + x)
|
|
157
|
+
} else if (Array.isArray(x)) {
|
|
158
|
+
xBigInt = BigInt(
|
|
159
|
+
'0x' +
|
|
160
|
+
Buffer.from(x).toString('hex').padStart(64, '0')
|
|
161
|
+
)
|
|
162
|
+
} else if (typeof x === 'number') {
|
|
163
|
+
xBigInt = BigInt(x)
|
|
164
|
+
} else {
|
|
165
|
+
throw new Error('Invalid x-coordinate type')
|
|
166
|
+
}
|
|
124
167
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const isOdd = y.fromRed().isOdd()
|
|
128
|
-
if ((odd && !isOdd) || (!odd && isOdd)) {
|
|
129
|
-
y = y.redNeg()
|
|
130
|
-
}
|
|
168
|
+
// Ensure x is within field range
|
|
169
|
+
xBigInt = mod(xBigInt, p)
|
|
131
170
|
|
|
132
|
-
|
|
171
|
+
// Compute y^2 = x^3 + a x + b mod p
|
|
172
|
+
const y2 = mod(modPow(xBigInt, BigInt(3), p) + b, p)
|
|
173
|
+
|
|
174
|
+
// Compute modular square root y = sqrt(y2) mod p
|
|
175
|
+
let y = sqrtMod(y2, p)
|
|
176
|
+
|
|
177
|
+
if (y === null) {
|
|
178
|
+
throw new Error('Invalid point')
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Adjust y to match the oddness
|
|
182
|
+
const isYOdd = (y % BigInt(2)) === BigInt(1)
|
|
183
|
+
if ((odd && !isYOdd) || (!odd && isYOdd)) {
|
|
184
|
+
y = p - y
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Convert x and y to BigNumber
|
|
188
|
+
const xBN = new BigNumber(xBigInt.toString(16), 16)
|
|
189
|
+
const yBN = new BigNumber(y.toString(16), 16)
|
|
190
|
+
return new Point(xBN, yBN)
|
|
191
|
+
} else {
|
|
192
|
+
const red = new ReductionContext('k256')
|
|
193
|
+
const a = new BigNumber(0).toRed(red)
|
|
194
|
+
const b = new BigNumber(7).toRed(red)
|
|
195
|
+
const zero = new BigNumber(0).toRed(red)
|
|
196
|
+
if (!BigNumber.isBN(x)) {
|
|
197
|
+
x = new BigNumber(x as number, 16)
|
|
198
|
+
}
|
|
199
|
+
x = x as BigNumber
|
|
200
|
+
if (x.red == null) {
|
|
201
|
+
x = x.toRed(red)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const y2 = x.redSqr().redMul(x).redIAdd(x.redMul(a)).redIAdd(b)
|
|
205
|
+
let y = y2.redSqrt()
|
|
206
|
+
if (y.redSqr().redSub(y2).cmp(zero) !== 0) {
|
|
207
|
+
throw new Error('invalid point')
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// XXX Is there any way to tell if the number is odd without converting it
|
|
211
|
+
// to non-red form?
|
|
212
|
+
const isOdd = y.fromRed().isOdd()
|
|
213
|
+
if ((odd && !isOdd) || (!odd && isOdd)) {
|
|
214
|
+
y = y.redNeg()
|
|
215
|
+
}
|
|
216
|
+
return new Point(x, y)
|
|
217
|
+
}
|
|
133
218
|
}
|
|
134
219
|
|
|
135
220
|
/**
|
|
@@ -40,7 +40,7 @@ export default class TransactionSignature extends Signature {
|
|
|
40
40
|
const writer = new Writer()
|
|
41
41
|
for (const input of inputs) {
|
|
42
42
|
if (typeof input.sourceTXID === 'undefined') {
|
|
43
|
-
writer.
|
|
43
|
+
writer.write(input.sourceTransaction.hash() as number[])
|
|
44
44
|
} else {
|
|
45
45
|
writer.writeReverse(toArray(input.sourceTXID, 'hex'))
|
|
46
46
|
}
|
|
@@ -122,8 +122,9 @@ export default class TransactionSignature extends Signature {
|
|
|
122
122
|
writer.writeUInt32LE(params.sourceOutputIndex)
|
|
123
123
|
|
|
124
124
|
// scriptCode of the input (serialized as scripts inside CTxOuts)
|
|
125
|
-
|
|
126
|
-
writer.
|
|
125
|
+
const subscriptBin = params.subscript.toBinary()
|
|
126
|
+
writer.writeVarIntNum(subscriptBin.length)
|
|
127
|
+
writer.write(subscriptBin)
|
|
127
128
|
|
|
128
129
|
// value of the output spent by this input (8-byte little endian)
|
|
129
130
|
writer.writeUInt64LE(params.sourceSatoshis)
|
package/src/primitives/utils.ts
CHANGED
|
@@ -320,9 +320,13 @@ export class Writer {
|
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
toArray (): number[] {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
323
|
+
const totalLength = this.getLength()
|
|
324
|
+
const ret = new Array(totalLength)
|
|
325
|
+
let offset = 0
|
|
326
|
+
for (const buf of this.bufs) {
|
|
327
|
+
for (let i = 0; i < buf.length; i++) {
|
|
328
|
+
ret[offset++] = buf[i]
|
|
329
|
+
}
|
|
326
330
|
}
|
|
327
331
|
return ret
|
|
328
332
|
}
|
|
@@ -515,18 +519,18 @@ export class Reader {
|
|
|
515
519
|
}
|
|
516
520
|
|
|
517
521
|
public read (len = this.bin.length): number[] {
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
|
|
522
|
+
const start = this.pos
|
|
523
|
+
const end = this.pos + len
|
|
524
|
+
this.pos = end
|
|
525
|
+
return this.bin.slice(start, end)
|
|
521
526
|
}
|
|
522
527
|
|
|
523
528
|
public readReverse (len = this.bin.length): number[] {
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
for (let i = 0; i < buf2.length; i++) {
|
|
528
|
-
buf2[i] = bin[bin.length - 1 - i]
|
|
529
|
+
const buf2 = new Array(len)
|
|
530
|
+
for (let i = 0; i < len; i++) {
|
|
531
|
+
buf2[i] = this.bin[this.pos + len - 1 - i]
|
|
529
532
|
}
|
|
533
|
+
this.pos += len
|
|
530
534
|
return buf2
|
|
531
535
|
}
|
|
532
536
|
|
|
@@ -662,3 +666,46 @@ export class Reader {
|
|
|
662
666
|
}
|
|
663
667
|
}
|
|
664
668
|
}
|
|
669
|
+
|
|
670
|
+
export const minimallyEncode = (buf: number[]): number[] => {
|
|
671
|
+
if (buf.length === 0) {
|
|
672
|
+
return buf
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// If the last byte is not 0x00 or 0x80, we are minimally encoded.
|
|
676
|
+
const last = buf[buf.length - 1]
|
|
677
|
+
if ((last & 0x7f) !== 0) {
|
|
678
|
+
return buf
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// If the script is one byte long, then we have a zero, which encodes as an
|
|
682
|
+
// empty array.
|
|
683
|
+
if (buf.length === 1) {
|
|
684
|
+
return []
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// If the next byte has it sign bit set, then we are minimaly encoded.
|
|
688
|
+
if ((buf[buf.length - 2] & 0x80) !== 0) {
|
|
689
|
+
return buf
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// We are not minimally encoded, we need to figure out how much to trim.
|
|
693
|
+
for (let i = buf.length - 1; i > 0; i--) {
|
|
694
|
+
// We found a non zero byte, time to encode.
|
|
695
|
+
if (buf[i - 1] !== 0) {
|
|
696
|
+
if ((buf[i - 1] & 0x80) !== 0) {
|
|
697
|
+
// We found a byte with it sign bit set so we need one more
|
|
698
|
+
// byte.
|
|
699
|
+
buf[i++] = last
|
|
700
|
+
} else {
|
|
701
|
+
// the sign bit is clear, we can use it.
|
|
702
|
+
buf[i - 1] |= last
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return buf.slice(0, i)
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// If we found the whole thing is zeros, then we have a zero.
|
|
710
|
+
return []
|
|
711
|
+
}
|
package/src/script/Spend.ts
CHANGED
|
@@ -4,7 +4,7 @@ import Script from './Script.js'
|
|
|
4
4
|
import BigNumber from '../primitives/BigNumber.js'
|
|
5
5
|
import OP from './OP.js'
|
|
6
6
|
import ScriptChunk from './ScriptChunk.js'
|
|
7
|
-
import { toHex } from '../primitives/utils.js'
|
|
7
|
+
import { toHex, minimallyEncode } from '../primitives/utils.js'
|
|
8
8
|
import * as Hash from '../primitives/Hash.js'
|
|
9
9
|
import TransactionSignature from '../primitives/TransactionSignature.js'
|
|
10
10
|
import PublicKey from '../primitives/PublicKey.js'
|
|
@@ -206,53 +206,10 @@ export default class Spend {
|
|
|
206
206
|
return true
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
-
const minimallyEncode = (buf: number[]): number[] => {
|
|
210
|
-
if (buf.length === 0) {
|
|
211
|
-
return buf
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// If the last byte is not 0x00 or 0x80, we are minimally encoded.
|
|
215
|
-
const last = buf[buf.length - 1]
|
|
216
|
-
if ((last & 0x7f) !== 0) {
|
|
217
|
-
return buf
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// If the script is one byte long, then we have a zero, which encodes as an
|
|
221
|
-
// empty array.
|
|
222
|
-
if (buf.length === 1) {
|
|
223
|
-
return []
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// If the next byte has it sign bit set, then we are minimaly encoded.
|
|
227
|
-
if ((buf[buf.length - 2] & 0x80) !== 0) {
|
|
228
|
-
return buf
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// We are not minimally encoded, we need to figure out how much to trim.
|
|
232
|
-
for (let i = buf.length - 1; i > 0; i--) {
|
|
233
|
-
// We found a non zero byte, time to encode.
|
|
234
|
-
if (buf[i - 1] !== 0) {
|
|
235
|
-
if ((buf[i - 1] & 0x80) !== 0) {
|
|
236
|
-
// We found a byte with it sign bit set so we need one more
|
|
237
|
-
// byte.
|
|
238
|
-
buf[i++] = last
|
|
239
|
-
} else {
|
|
240
|
-
// the sign bit is clear, we can use it.
|
|
241
|
-
buf[i - 1] |= last
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return buf.slice(0, i)
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// If we found the whole thing is zeros, then we have a zero.
|
|
249
|
-
return []
|
|
250
|
-
}
|
|
251
|
-
|
|
252
209
|
const padDataToSize = (buf: number[], len: number): number[] => {
|
|
253
|
-
|
|
210
|
+
const b = buf
|
|
254
211
|
while (b.length < len) {
|
|
255
|
-
b
|
|
212
|
+
b.unshift(0)
|
|
256
213
|
}
|
|
257
214
|
return b
|
|
258
215
|
}
|
|
@@ -828,7 +785,7 @@ export default class Spend {
|
|
|
828
785
|
shifted = bn1.ushrn(n)
|
|
829
786
|
}
|
|
830
787
|
const bufShifted = padDataToSize(
|
|
831
|
-
|
|
788
|
+
shifted.toArray().slice(buf1.length * -1),
|
|
832
789
|
buf1.length
|
|
833
790
|
)
|
|
834
791
|
this.stack.push(bufShifted)
|
|
@@ -1059,7 +1016,7 @@ export default class Spend {
|
|
|
1059
1016
|
|
|
1060
1017
|
try {
|
|
1061
1018
|
sig = TransactionSignature.fromChecksigFormat(bufSig)
|
|
1062
|
-
pubkey = PublicKey.
|
|
1019
|
+
pubkey = PublicKey.fromDER(bufPubkey)
|
|
1063
1020
|
fSuccess = verifySignature(sig, pubkey, subscript)
|
|
1064
1021
|
} catch (e) {
|
|
1065
1022
|
// invalid sig or pubkey
|
|
@@ -21,7 +21,7 @@ export default class P2PKH implements ScriptTemplate {
|
|
|
21
21
|
* @param {number[] | string} pubkeyhash or address - An array or address representing the public key hash.
|
|
22
22
|
* @returns {LockingScript} - A P2PKH locking script.
|
|
23
23
|
*/
|
|
24
|
-
lock
|
|
24
|
+
lock(pubkeyhash: string | number[]): LockingScript {
|
|
25
25
|
let data: number[]
|
|
26
26
|
if (typeof pubkeyhash === 'string') {
|
|
27
27
|
const hash = fromBase58Check(pubkeyhash)
|
|
@@ -54,16 +54,16 @@ export default class P2PKH implements ScriptTemplate {
|
|
|
54
54
|
* @param {Script} lockingScript - Optional. The lockinScript. Otherwise the input.sourceTransaction is required.
|
|
55
55
|
* @returns {Object} - An object containing the `sign` and `estimateLength` functions.
|
|
56
56
|
*/
|
|
57
|
-
unlock
|
|
57
|
+
unlock(
|
|
58
58
|
privateKey: PrivateKey,
|
|
59
59
|
signOutputs: 'all' | 'none' | 'single' = 'all',
|
|
60
60
|
anyoneCanPay: boolean = false,
|
|
61
61
|
sourceSatoshis?: number,
|
|
62
62
|
lockingScript?: Script
|
|
63
63
|
): {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>
|
|
65
|
+
estimateLength: () => Promise<108>
|
|
66
|
+
} {
|
|
67
67
|
return {
|
|
68
68
|
sign: async (tx: Transaction, inputIndex: number) => {
|
|
69
69
|
let signatureScope = TransactionSignature.SIGHASH_FORKID
|
|
@@ -130,9 +130,9 @@ export default class P2PKH implements ScriptTemplate {
|
|
|
130
130
|
])
|
|
131
131
|
},
|
|
132
132
|
estimateLength: async () => {
|
|
133
|
-
// public key (1+33) + signature (1+
|
|
133
|
+
// public key (1+33) + signature (1+73)
|
|
134
134
|
// Note: We add 1 to each element's length because of the associated OP_PUSH
|
|
135
|
-
return
|
|
135
|
+
return 108
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
}
|
|
@@ -23,7 +23,7 @@ export default class RPuzzle implements ScriptTemplate {
|
|
|
23
23
|
*
|
|
24
24
|
* @param {'raw'|'SHA1'|'SHA256'|'HASH256'|'RIPEMD160'|'HASH160'} type Denotes the type of puzzle to create
|
|
25
25
|
*/
|
|
26
|
-
constructor
|
|
26
|
+
constructor(type: 'raw' | 'SHA1' | 'SHA256' | 'HASH256' | 'RIPEMD160' | 'HASH160' = 'raw') {
|
|
27
27
|
this.type = type
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -33,7 +33,7 @@ export default class RPuzzle implements ScriptTemplate {
|
|
|
33
33
|
* @param {number[]} value - An array representing the R value or its hash.
|
|
34
34
|
* @returns {LockingScript} - An R puzzle locking script.
|
|
35
35
|
*/
|
|
36
|
-
lock
|
|
36
|
+
lock(value: number[]): LockingScript {
|
|
37
37
|
const chunks: ScriptChunk[] = [
|
|
38
38
|
{ op: OP.OP_OVER },
|
|
39
39
|
{ op: OP.OP_3 },
|
|
@@ -70,15 +70,15 @@ export default class RPuzzle implements ScriptTemplate {
|
|
|
70
70
|
* @param {boolean} anyoneCanPay - Flag indicating if the signature allows for other inputs to be added later.
|
|
71
71
|
* @returns {Object} - An object containing the `sign` and `estimateLength` functions.
|
|
72
72
|
*/
|
|
73
|
-
unlock
|
|
73
|
+
unlock(
|
|
74
74
|
k: BigNumber,
|
|
75
75
|
privateKey: PrivateKey,
|
|
76
76
|
signOutputs: 'all' | 'none' | 'single' = 'all',
|
|
77
77
|
anyoneCanPay: boolean = false
|
|
78
78
|
): {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
sign: (tx: Transaction, inputIndex: number) => Promise<UnlockingScript>
|
|
80
|
+
estimateLength: () => Promise<108>
|
|
81
|
+
} {
|
|
82
82
|
return {
|
|
83
83
|
sign: async (tx: Transaction, inputIndex: number) => {
|
|
84
84
|
if (typeof privateKey === 'undefined') {
|
|
@@ -131,9 +131,9 @@ export default class RPuzzle implements ScriptTemplate {
|
|
|
131
131
|
])
|
|
132
132
|
},
|
|
133
133
|
estimateLength: async () => {
|
|
134
|
-
// public key (1+33) + signature (1+
|
|
134
|
+
// public key (1+33) + signature (1+73)
|
|
135
135
|
// Note: We add 1 to each element's length because of the associated OP_PUSH
|
|
136
|
-
return
|
|
136
|
+
return 108
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
}
|
|
@@ -593,9 +593,8 @@ export default class Transaction {
|
|
|
593
593
|
}
|
|
594
594
|
if (enc === 'hex') {
|
|
595
595
|
return toHex(hash)
|
|
596
|
-
} else {
|
|
597
|
-
return hash
|
|
598
596
|
}
|
|
597
|
+
return hash
|
|
599
598
|
}
|
|
600
599
|
|
|
601
600
|
/**
|
|
@@ -635,84 +634,110 @@ export default class Transaction {
|
|
|
635
634
|
*
|
|
636
635
|
* @example tx.verify(new WhatsOnChain(), new SatoshisPerKilobyte(1))
|
|
637
636
|
*/
|
|
638
|
-
async verify (
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
return true
|
|
651
|
-
}
|
|
637
|
+
async verify (
|
|
638
|
+
chainTracker: ChainTracker | 'scripts only' = defaultChainTracker(),
|
|
639
|
+
feeModel?: FeeModel
|
|
640
|
+
): Promise<boolean> {
|
|
641
|
+
const verifiedTxids = new Set<string>()
|
|
642
|
+
const txQueue: Transaction[] = [this]
|
|
643
|
+
|
|
644
|
+
while (txQueue.length > 0) {
|
|
645
|
+
const tx = txQueue.shift()
|
|
646
|
+
const txid = tx.id('hex')
|
|
647
|
+
if (verifiedTxids.has(txid)) {
|
|
648
|
+
continue
|
|
652
649
|
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
if (typeof feeModel !== 'undefined') {
|
|
656
|
-
const cpTx = Transaction.fromHexEF(this.toHexEF())
|
|
657
|
-
delete cpTx.outputs[0].satoshis
|
|
658
|
-
cpTx.outputs[0].change = true
|
|
659
|
-
await cpTx.fee(feeModel)
|
|
660
|
-
if (this.getFee() < cpTx.getFee()) throw new Error(`Verification failed because the transaction ${this.id('hex')} has an insufficient fee and has not been mined.`)
|
|
661
|
-
}
|
|
662
650
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
651
|
+
// If the transaction has a valid merkle path, verification is complete.
|
|
652
|
+
if (typeof tx.merklePath === 'object') {
|
|
653
|
+
if (chainTracker === 'scripts only') {
|
|
654
|
+
verifiedTxids.add(txid)
|
|
655
|
+
continue
|
|
656
|
+
} else {
|
|
657
|
+
const proofValid = await tx.merklePath.verify(
|
|
658
|
+
txid,
|
|
659
|
+
chainTracker
|
|
660
|
+
)
|
|
661
|
+
// If the proof is valid, no need to verify inputs.
|
|
662
|
+
if (proofValid) {
|
|
663
|
+
verifiedTxids.add(txid)
|
|
664
|
+
continue
|
|
665
|
+
}
|
|
666
|
+
}
|
|
673
667
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
668
|
+
|
|
669
|
+
// Verify fee if feeModel is provided
|
|
670
|
+
if (typeof feeModel !== 'undefined') {
|
|
671
|
+
const cpTx = Transaction.fromEF(tx.toEF())
|
|
672
|
+
delete cpTx.outputs[0].satoshis
|
|
673
|
+
cpTx.outputs[0].change = true
|
|
674
|
+
await cpTx.fee(feeModel)
|
|
675
|
+
if (tx.getFee() < cpTx.getFee()) {
|
|
676
|
+
throw new Error(`Verification failed because the transaction ${txid} has an insufficient fee and has not been mined.`)
|
|
677
|
+
}
|
|
679
678
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
679
|
+
|
|
680
|
+
// Verify each input transaction and evaluate the spend events.
|
|
681
|
+
// Also, keep a total of the input amounts for later.
|
|
682
|
+
let inputTotal = 0
|
|
683
|
+
for (let i = 0; i < tx.inputs.length; i++) {
|
|
684
|
+
const input = tx.inputs[i]
|
|
685
|
+
if (typeof input.sourceTransaction !== 'object') {
|
|
686
|
+
throw new Error(`Verification failed because the input at index ${i} of transaction ${txid} is missing an associated source transaction. This source transaction is required for transaction verification because there is no merkle proof for the transaction spending a UTXO it contains.`)
|
|
687
|
+
}
|
|
688
|
+
if (typeof input.unlockingScript !== 'object') {
|
|
689
|
+
throw new Error(`Verification failed because the input at index ${i} of transaction ${txid} is missing an associated unlocking script. This script is required for transaction verification because there is no merkle proof for the transaction spending the UTXO.`)
|
|
690
|
+
}
|
|
691
|
+
const sourceOutput = input.sourceTransaction.outputs[input.sourceOutputIndex]
|
|
692
|
+
inputTotal += sourceOutput.satoshis
|
|
693
|
+
|
|
694
|
+
const sourceTxid = input.sourceTransaction.id('hex')
|
|
695
|
+
if (!verifiedTxids.has(sourceTxid)) {
|
|
696
|
+
txQueue.push(input.sourceTransaction)
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const otherInputs = tx.inputs.filter((_, idx) => idx !== i)
|
|
700
|
+
if (typeof input.sourceTXID === 'undefined') {
|
|
701
|
+
input.sourceTXID = sourceTxid
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const spend = new Spend({
|
|
705
|
+
sourceTXID: input.sourceTXID,
|
|
706
|
+
sourceOutputIndex: input.sourceOutputIndex,
|
|
707
|
+
lockingScript: sourceOutput.lockingScript,
|
|
708
|
+
sourceSatoshis: sourceOutput.satoshis,
|
|
709
|
+
transactionVersion: tx.version,
|
|
710
|
+
otherInputs,
|
|
711
|
+
unlockingScript: input.unlockingScript,
|
|
712
|
+
inputSequence: input.sequence,
|
|
713
|
+
inputIndex: i,
|
|
714
|
+
outputs: tx.outputs,
|
|
715
|
+
lockTime: tx.lockTime
|
|
716
|
+
})
|
|
717
|
+
const spendValid = spend.validate()
|
|
718
|
+
|
|
719
|
+
if (!spendValid) {
|
|
720
|
+
return false
|
|
721
|
+
}
|
|
684
722
|
}
|
|
685
723
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
inputSequence: input.sequence,
|
|
695
|
-
inputIndex: i,
|
|
696
|
-
outputs: this.outputs,
|
|
697
|
-
lockTime: this.lockTime
|
|
698
|
-
})
|
|
699
|
-
const spendValid = spend.validate()
|
|
724
|
+
// Total the outputs to ensure they don't amount to more than the inputs
|
|
725
|
+
let outputTotal = 0
|
|
726
|
+
for (const out of tx.outputs) {
|
|
727
|
+
if (typeof out.satoshis !== 'number') {
|
|
728
|
+
throw new Error('Every output must have a defined amount during transaction verification.')
|
|
729
|
+
}
|
|
730
|
+
outputTotal += out.satoshis
|
|
731
|
+
}
|
|
700
732
|
|
|
701
|
-
if (
|
|
733
|
+
if (outputTotal > inputTotal) {
|
|
702
734
|
return false
|
|
703
735
|
}
|
|
704
|
-
}
|
|
705
736
|
|
|
706
|
-
|
|
707
|
-
let outputTotal = 0
|
|
708
|
-
for (const out of this.outputs) {
|
|
709
|
-
if (typeof out.satoshis !== 'number') {
|
|
710
|
-
throw new Error('Every output must have a defined amount during transaction verification.')
|
|
711
|
-
}
|
|
712
|
-
outputTotal += out.satoshis
|
|
737
|
+
verifiedTxids.add(txid)
|
|
713
738
|
}
|
|
714
739
|
|
|
715
|
-
return
|
|
740
|
+
return true
|
|
716
741
|
}
|
|
717
742
|
|
|
718
743
|
/**
|