smartledger-bsv 3.0.1 → 3.0.2
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/CHANGELOG.md +111 -0
- package/bsv.min.js +8 -8
- package/index.js +10 -0
- package/lib/crypto/ecdsa.js +57 -38
- package/lib/crypto/smartledger_verify.js +42 -11
- package/lib/script/interpreter.js +8 -8
- package/lib/smartminer.js +169 -0
- package/lib/smartutxo.js +200 -0
- package/lib/transaction/transaction.js +39 -0
- package/package.json +13 -2
- package/utilities/README.md +132 -0
- package/utilities/blockchain-state.js +332 -0
- package/utilities/blockchain-state.json +41 -0
- package/utilities/miner-simulator.js +620 -0
- package/utilities/mock-utxo-generator.js +149 -0
- package/utilities/raw-tx-examples.js +213 -0
- package/utilities/success-demo.js +193 -0
- package/utilities/transaction-examples.js +328 -0
- package/utilities/utxo-manager.js +162 -0
- package/utilities/wallet-setup.js +167 -0
- package/utilities/wallet.json +30 -0
- package/utilities/working-signature-demo.js +181 -0
- package/validation_test.js +97 -0
package/index.js
CHANGED
|
@@ -97,5 +97,15 @@ bsv.SmartLedger = {
|
|
|
97
97
|
bsv.SmartVerify = require('./lib/crypto/smartledger_verify')
|
|
98
98
|
bsv.EllipticFixed = require('./lib/crypto/elliptic-fixed')
|
|
99
99
|
|
|
100
|
+
// SmartLedger Development & Testing Tools (Node.js only)
|
|
101
|
+
if (typeof window === 'undefined' && typeof require === 'function') {
|
|
102
|
+
try {
|
|
103
|
+
bsv.SmartUTXO = require('./lib/smartutxo')
|
|
104
|
+
bsv.SmartMiner = require('./lib/smartminer')
|
|
105
|
+
} catch (e) {
|
|
106
|
+
// Browser environment - these tools not available
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
100
110
|
// Internal usage, exposed for testing/advanced tweaking
|
|
101
111
|
bsv.Transaction.sighash = require('./lib/transaction/sighash')
|
package/lib/crypto/ecdsa.js
CHANGED
|
@@ -23,7 +23,22 @@ ECDSA.prototype.set = function (obj) {
|
|
|
23
23
|
this.endian = obj.endian || this.endian // the endianness of hashbuf
|
|
24
24
|
this.privkey = obj.privkey || this.privkey
|
|
25
25
|
this.pubkey = obj.pubkey || (this.privkey ? this.privkey.publicKey : this.pubkey)
|
|
26
|
-
|
|
26
|
+
|
|
27
|
+
// === SmartLedger Signature Handling ===
|
|
28
|
+
// Auto-parse DER buffers to Signature objects for compatibility
|
|
29
|
+
if (obj.sig) {
|
|
30
|
+
if (Buffer.isBuffer(obj.sig)) {
|
|
31
|
+
// Parse DER buffer to Signature object
|
|
32
|
+
var Signature = require('./signature')
|
|
33
|
+
this.sig = Signature.fromDER(obj.sig)
|
|
34
|
+
} else {
|
|
35
|
+
// Already a Signature object
|
|
36
|
+
this.sig = obj.sig
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
this.sig = this.sig
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
this.k = obj.k || this.k
|
|
28
43
|
this.verified = obj.verified || this.verified
|
|
29
44
|
return this
|
|
@@ -159,53 +174,57 @@ ECDSA.prototype.sigError = function () {
|
|
|
159
174
|
|
|
160
175
|
// === SmartLedger Security Patches ===
|
|
161
176
|
// Apply security validation during cryptographic verification
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
r: this.sig.r,
|
|
166
|
-
s: this.sig.s
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Apply security patches to the copy
|
|
170
|
-
var n = Point.getN()
|
|
177
|
+
var r = this.sig.r
|
|
178
|
+
var s = this.sig.s
|
|
179
|
+
var n = Point.getN()
|
|
171
180
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
181
|
+
try {
|
|
182
|
+
// Fix 1 & 2: Enhanced range validation
|
|
183
|
+
if (r.isZero() || s.isZero() || r.gte(n) || s.gte(n)) {
|
|
175
184
|
return 'r and s not in range'
|
|
176
185
|
}
|
|
177
186
|
|
|
178
|
-
// Fix 3:
|
|
187
|
+
// Fix 3: Handle canonicalization properly
|
|
188
|
+
// For verification, we need to try both the original s and canonical s
|
|
189
|
+
// This handles both pre-canonicalized signatures and non-canonical ones
|
|
179
190
|
var nh = n.shrn(1) // n/2
|
|
180
|
-
|
|
181
|
-
|
|
191
|
+
var canonicalS = s.gt(nh) ? n.sub(s) : s
|
|
192
|
+
|
|
193
|
+
var e = BN.fromBuffer(this.hashbuf, this.endian ? {
|
|
194
|
+
endian: this.endian
|
|
195
|
+
} : undefined)
|
|
196
|
+
|
|
197
|
+
// Try verification with canonical s
|
|
198
|
+
var sinv = canonicalS.invm(n)
|
|
199
|
+
var u1 = sinv.mul(e).umod(n)
|
|
200
|
+
var u2 = sinv.mul(r).umod(n)
|
|
201
|
+
|
|
202
|
+
var p = Point.getG().mulAdd(u1, this.pubkey.point, u2)
|
|
203
|
+
if (p.isInfinity()) {
|
|
204
|
+
return 'p is infinity'
|
|
182
205
|
}
|
|
183
206
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
207
|
+
if (p.getX().umod(n).cmp(r) !== 0) {
|
|
208
|
+
// If canonical verification failed and s was already canonical,
|
|
209
|
+
// try with the non-canonical version (for backwards compatibility)
|
|
210
|
+
if (s.lte(nh)) {
|
|
211
|
+
var nonCanonicalS = n.sub(s)
|
|
212
|
+
sinv = nonCanonicalS.invm(n)
|
|
213
|
+
u1 = sinv.mul(e).umod(n)
|
|
214
|
+
u2 = sinv.mul(r).umod(n)
|
|
215
|
+
|
|
216
|
+
p = Point.getG().mulAdd(u1, this.pubkey.point, u2)
|
|
217
|
+
if (!p.isInfinity() && p.getX().umod(n).cmp(r) === 0) {
|
|
218
|
+
return false // Verification succeeded with non-canonical s
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return 'Invalid signature'
|
|
222
|
+
} else {
|
|
223
|
+
return false // Verification succeeded with canonical s
|
|
224
|
+
}
|
|
187
225
|
} catch (error) {
|
|
188
226
|
return 'Signature security validation failed: ' + error.message
|
|
189
227
|
}
|
|
190
|
-
|
|
191
|
-
var e = BN.fromBuffer(this.hashbuf, this.endian ? {
|
|
192
|
-
endian: this.endian
|
|
193
|
-
} : undefined)
|
|
194
|
-
// Use canonical s for verification
|
|
195
|
-
var sinv = canonicalS.invm(n)
|
|
196
|
-
var u1 = sinv.mul(e).umod(n)
|
|
197
|
-
var u2 = sinv.mul(r).umod(n)
|
|
198
|
-
|
|
199
|
-
var p = Point.getG().mulAdd(u1, this.pubkey.point, u2)
|
|
200
|
-
if (p.isInfinity()) {
|
|
201
|
-
return 'p is infinity'
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (p.getX().umod(n).cmp(r) !== 0) {
|
|
205
|
-
return 'Invalid signature'
|
|
206
|
-
} else {
|
|
207
|
-
return false
|
|
208
|
-
}
|
|
209
228
|
}
|
|
210
229
|
|
|
211
230
|
ECDSA.toLowS = function (s) {
|
|
@@ -16,7 +16,7 @@ const nh = n.shrn(1) // n / 2
|
|
|
16
16
|
/**
|
|
17
17
|
* Hardened signature verification with canonicalization
|
|
18
18
|
* @param {Buffer} msgHash - 32-byte message hash
|
|
19
|
-
* @param {Signature} sig - Signature object with r,s components
|
|
19
|
+
* @param {Signature|Buffer} sig - Signature object with r,s components or DER buffer
|
|
20
20
|
* @param {PublicKey} pubkey - Public key for verification
|
|
21
21
|
* @returns {boolean} - true if signature is valid and canonical
|
|
22
22
|
*/
|
|
@@ -26,13 +26,28 @@ function smartVerify (msgHash, sig, pubkey) {
|
|
|
26
26
|
throw new Error('Invalid message hash: must be 32-byte buffer')
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
if (!sig
|
|
29
|
+
if (!sig) {
|
|
30
|
+
return false
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Parse DER buffer to Signature object if needed
|
|
34
|
+
let sigObj = sig
|
|
35
|
+
if (Buffer.isBuffer(sig)) {
|
|
36
|
+
try {
|
|
37
|
+
const Signature = require('./signature')
|
|
38
|
+
sigObj = Signature.fromDER(sig)
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return false
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!sigObj || !sigObj.r || !sigObj.s) {
|
|
30
45
|
return false
|
|
31
46
|
}
|
|
32
47
|
|
|
33
48
|
// Ensure r and s are BN instances
|
|
34
|
-
const r = BN.isBN(
|
|
35
|
-
const s = BN.isBN(
|
|
49
|
+
const r = BN.isBN(sigObj.r) ? sigObj.r : new BN(sigObj.r)
|
|
50
|
+
const s = BN.isBN(sigObj.s) ? sigObj.s : new BN(sigObj.s)
|
|
36
51
|
|
|
37
52
|
// Reject zero values
|
|
38
53
|
if (r.isZero() || s.isZero()) {
|
|
@@ -50,27 +65,43 @@ function smartVerify (msgHash, sig, pubkey) {
|
|
|
50
65
|
canonicalS = n.sub(s)
|
|
51
66
|
}
|
|
52
67
|
|
|
53
|
-
// Create canonicalized signature object
|
|
54
|
-
const
|
|
68
|
+
// Create canonicalized signature object for ECDSA verify
|
|
69
|
+
const Signature = require('./signature')
|
|
70
|
+
const canonicalSig = new Signature({
|
|
55
71
|
r: r,
|
|
56
72
|
s: canonicalS
|
|
57
|
-
}
|
|
73
|
+
})
|
|
58
74
|
|
|
59
|
-
// Use BSV's
|
|
75
|
+
// Use BSV's ECDSA verify with canonical signature object
|
|
60
76
|
return ECDSA.verify(msgHash, canonicalSig, pubkey)
|
|
61
77
|
}
|
|
62
78
|
|
|
63
79
|
/**
|
|
64
80
|
* Check if signature is in canonical form (s <= n/2)
|
|
65
|
-
* @param {Object} sig - Signature with r,s components
|
|
81
|
+
* @param {Object|Buffer} sig - Signature with r,s components or DER buffer
|
|
66
82
|
* @returns {boolean} - true if signature is canonical
|
|
67
83
|
*/
|
|
68
84
|
function isCanonical (sig) {
|
|
69
|
-
if (!sig
|
|
85
|
+
if (!sig) {
|
|
70
86
|
return false
|
|
71
87
|
}
|
|
72
88
|
|
|
73
|
-
|
|
89
|
+
// Parse DER buffer to Signature object if needed
|
|
90
|
+
let sigObj = sig
|
|
91
|
+
if (Buffer.isBuffer(sig)) {
|
|
92
|
+
try {
|
|
93
|
+
const Signature = require('./signature')
|
|
94
|
+
sigObj = Signature.fromDER(sig)
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!sigObj || !sigObj.s) {
|
|
101
|
+
return false
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const s = BN.isBN(sigObj.s) ? sigObj.s : new BN(sigObj.s)
|
|
74
105
|
return s.lte(nh)
|
|
75
106
|
}
|
|
76
107
|
|
|
@@ -655,19 +655,21 @@ Interpreter.prototype.step = function () {
|
|
|
655
655
|
switch (opcode) {
|
|
656
656
|
case Opcode.OP_2MUL:
|
|
657
657
|
case Opcode.OP_2DIV:
|
|
658
|
-
|
|
659
|
-
// Disabled opcodes.
|
|
658
|
+
// Permanently disabled opcodes.
|
|
660
659
|
return true
|
|
661
660
|
|
|
662
661
|
case Opcode.OP_INVERT:
|
|
663
662
|
case Opcode.OP_MUL:
|
|
664
663
|
case Opcode.OP_LSHIFT:
|
|
665
664
|
case Opcode.OP_RSHIFT:
|
|
666
|
-
//
|
|
665
|
+
// Magnetic opcodes - still require flag for backwards compatibility
|
|
667
666
|
if ((self.flags & Interpreter.SCRIPT_ENABLE_MAGNETIC_OPCODES) === 0) {
|
|
668
667
|
return true
|
|
669
668
|
}
|
|
670
669
|
break
|
|
670
|
+
|
|
671
|
+
// Monolith opcodes are now enabled by default in SmartLedger BSV
|
|
672
|
+
// These were activated in May 2018 and are part of standard BSV consensus
|
|
671
673
|
case Opcode.OP_DIV:
|
|
672
674
|
case Opcode.OP_MOD:
|
|
673
675
|
case Opcode.OP_SPLIT:
|
|
@@ -677,11 +679,9 @@ Interpreter.prototype.step = function () {
|
|
|
677
679
|
case Opcode.OP_XOR:
|
|
678
680
|
case Opcode.OP_BIN2NUM:
|
|
679
681
|
case Opcode.OP_NUM2BIN:
|
|
680
|
-
//
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
break
|
|
682
|
+
// These opcodes are now always enabled - no flag required
|
|
683
|
+
return false
|
|
684
|
+
|
|
685
685
|
default:
|
|
686
686
|
break
|
|
687
687
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SmartLedger Miner Simulator
|
|
5
|
+
* Provides BSV blockchain mining simulation for testing and development
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Comprehensive BSV Miner Simulator for development and testing
|
|
10
|
+
*/
|
|
11
|
+
class SmartMiner {
|
|
12
|
+
constructor(bsv, options = {}) {
|
|
13
|
+
this.bsv = bsv
|
|
14
|
+
this.options = {
|
|
15
|
+
difficulty: options.difficulty || 1,
|
|
16
|
+
blockTime: options.blockTime || 10000, // 10 seconds for testing
|
|
17
|
+
validateScripts: options.validateScripts !== false, // default true
|
|
18
|
+
logLevel: options.logLevel || 'info',
|
|
19
|
+
...options
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.currentBlock = {
|
|
23
|
+
height: 0,
|
|
24
|
+
transactions: [],
|
|
25
|
+
timestamp: Date.now()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
this.mempool = []
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Add a transaction to the mempool for mining
|
|
33
|
+
* @param {Transaction} transaction - BSV transaction object
|
|
34
|
+
* @returns {boolean} - true if accepted, false if rejected
|
|
35
|
+
*/
|
|
36
|
+
acceptTransaction(transaction) {
|
|
37
|
+
try {
|
|
38
|
+
// Validate transaction if script validation is enabled
|
|
39
|
+
if (this.options.validateScripts) {
|
|
40
|
+
const isValid = this.validateTransactionSignatures(transaction)
|
|
41
|
+
if (!isValid) {
|
|
42
|
+
this.log('warn', '❌ Transaction rejected: Invalid signatures')
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Add to mempool
|
|
48
|
+
this.mempool.push(transaction)
|
|
49
|
+
this.log('info', `✅ Transaction accepted into mempool: ${transaction.id || 'unknown'}`)
|
|
50
|
+
return true
|
|
51
|
+
|
|
52
|
+
} catch (error) {
|
|
53
|
+
this.log('error', `❌ Transaction rejected: ${error.message}`)
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Validate transaction signatures using BSV library
|
|
60
|
+
* @param {Transaction} transaction - BSV transaction
|
|
61
|
+
* @returns {boolean} - true if all signatures are valid
|
|
62
|
+
*/
|
|
63
|
+
validateTransactionSignatures(transaction) {
|
|
64
|
+
try {
|
|
65
|
+
// Use BSV's built-in transaction verification
|
|
66
|
+
const result = transaction.verify()
|
|
67
|
+
this.log('debug', `📊 Transaction verification result: ${result}`)
|
|
68
|
+
return result === true
|
|
69
|
+
} catch (error) {
|
|
70
|
+
this.log('warn', `⚠️ Signature validation error: ${error.message}`)
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Mine a new block with transactions from mempool
|
|
77
|
+
* @param {number} maxTransactions - Maximum transactions per block
|
|
78
|
+
* @returns {Object} - Mined block object
|
|
79
|
+
*/
|
|
80
|
+
mineBlock(maxTransactions = 10) {
|
|
81
|
+
const transactions = this.mempool.splice(0, maxTransactions)
|
|
82
|
+
|
|
83
|
+
const block = {
|
|
84
|
+
height: this.currentBlock.height + 1,
|
|
85
|
+
timestamp: Date.now(),
|
|
86
|
+
transactions: transactions,
|
|
87
|
+
transactionCount: transactions.length,
|
|
88
|
+
previousBlockHash: this.currentBlock.hash || '0000000000000000000000000000000000000000000000000000000000000000'
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Simple block hash simulation
|
|
92
|
+
const blockData = JSON.stringify({
|
|
93
|
+
height: block.height,
|
|
94
|
+
timestamp: block.timestamp,
|
|
95
|
+
previousBlockHash: block.previousBlockHash,
|
|
96
|
+
transactions: transactions.map(tx => tx.id || tx.toString())
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
block.hash = this.bsv.crypto.Hash.sha256(Buffer.from(blockData)).toString('hex')
|
|
100
|
+
|
|
101
|
+
this.currentBlock = block
|
|
102
|
+
|
|
103
|
+
this.log('info', `⛏️ Mined block ${block.height} with ${transactions.length} transactions`)
|
|
104
|
+
this.log('debug', `📦 Block hash: ${block.hash}`)
|
|
105
|
+
|
|
106
|
+
return block
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get mempool status
|
|
111
|
+
* @returns {Object} - Mempool statistics
|
|
112
|
+
*/
|
|
113
|
+
getMempoolStats() {
|
|
114
|
+
return {
|
|
115
|
+
transactionCount: this.mempool.length,
|
|
116
|
+
transactions: this.mempool.map(tx => ({
|
|
117
|
+
id: tx.id || 'unknown',
|
|
118
|
+
size: tx.toBuffer ? tx.toBuffer().length : 0,
|
|
119
|
+
inputs: tx.inputs ? tx.inputs.length : 0,
|
|
120
|
+
outputs: tx.outputs ? tx.outputs.length : 0
|
|
121
|
+
}))
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get current blockchain status
|
|
127
|
+
* @returns {Object} - Blockchain statistics
|
|
128
|
+
*/
|
|
129
|
+
getBlockchainStats() {
|
|
130
|
+
return {
|
|
131
|
+
currentHeight: this.currentBlock.height,
|
|
132
|
+
currentBlockHash: this.currentBlock.hash,
|
|
133
|
+
currentBlockTimestamp: this.currentBlock.timestamp,
|
|
134
|
+
mempoolSize: this.mempool.length,
|
|
135
|
+
difficulty: this.options.difficulty,
|
|
136
|
+
blockTime: this.options.blockTime
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Reset the miner state
|
|
142
|
+
*/
|
|
143
|
+
reset() {
|
|
144
|
+
this.currentBlock = {
|
|
145
|
+
height: 0,
|
|
146
|
+
transactions: [],
|
|
147
|
+
timestamp: Date.now()
|
|
148
|
+
}
|
|
149
|
+
this.mempool = []
|
|
150
|
+
this.log('info', '🔄 Miner reset to genesis state')
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Logging utility
|
|
155
|
+
* @param {string} level - Log level
|
|
156
|
+
* @param {string} message - Log message
|
|
157
|
+
*/
|
|
158
|
+
log(level, message) {
|
|
159
|
+
const levels = { error: 0, warn: 1, info: 2, debug: 3 }
|
|
160
|
+
const currentLevel = levels[this.options.logLevel] || 2
|
|
161
|
+
|
|
162
|
+
if (levels[level] <= currentLevel) {
|
|
163
|
+
const timestamp = new Date().toISOString()
|
|
164
|
+
console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}`)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = SmartMiner
|
package/lib/smartutxo.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SmartLedger UTXO Management System
|
|
5
|
+
* Provides blockchain state management and UTXO tracking for testing and development
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Browser-compatible imports
|
|
9
|
+
let fs, path, crypto, blockchainState
|
|
10
|
+
|
|
11
|
+
// Only require Node.js modules in Node.js environment
|
|
12
|
+
if (typeof window === 'undefined' && typeof require === 'function') {
|
|
13
|
+
try {
|
|
14
|
+
fs = require('fs')
|
|
15
|
+
path = require('path')
|
|
16
|
+
crypto = require('crypto')
|
|
17
|
+
blockchainState = require('../utilities/blockchain-state')
|
|
18
|
+
} catch (e) {
|
|
19
|
+
// Fallback for environments where these modules aren't available
|
|
20
|
+
console.warn('SmartUTXO: Running in browser mode - some features may be limited')
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Comprehensive UTXO Management System for BSV development
|
|
26
|
+
*/
|
|
27
|
+
class SmartUTXOManager {
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
this.options = options || {}
|
|
30
|
+
|
|
31
|
+
// Initialize blockchain state - this creates the file if needed
|
|
32
|
+
this.loadState()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Load blockchain state from file (initializes if needed)
|
|
37
|
+
*/
|
|
38
|
+
loadState() {
|
|
39
|
+
try {
|
|
40
|
+
const state = blockchainState.loadBlockchainState()
|
|
41
|
+
return state
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.log('⚠️ Could not load blockchain state:', error.message)
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Save blockchain state to file
|
|
50
|
+
*/
|
|
51
|
+
saveState() {
|
|
52
|
+
try {
|
|
53
|
+
const state = blockchainState.loadBlockchainState()
|
|
54
|
+
blockchainState.saveBlockchainState(state)
|
|
55
|
+
const utxoCount = Object.keys(state.globalUTXOSet || {}).length
|
|
56
|
+
console.log(`💾 Saved blockchain state with ${utxoCount} UTXOs`)
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.log('⚠️ Could not save blockchain state:', error.message)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get all UTXOs for a given address
|
|
64
|
+
* @param {string} address - Bitcoin address
|
|
65
|
+
* @returns {Array} Array of UTXO objects
|
|
66
|
+
*/
|
|
67
|
+
getUTXOsForAddress(address) {
|
|
68
|
+
try {
|
|
69
|
+
const state = blockchainState.loadBlockchainState()
|
|
70
|
+
|
|
71
|
+
// Check if wallet exists
|
|
72
|
+
if (!state.wallets || !state.wallets[address]) {
|
|
73
|
+
return []
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Return the wallet's UTXOs
|
|
77
|
+
return state.wallets[address].utxos || []
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.log('⚠️ Error getting UTXOs:', error.message)
|
|
80
|
+
return []
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Add a new UTXO to the system
|
|
86
|
+
* @param {Object} utxo - UTXO object {txid, vout, address, satoshis, script}
|
|
87
|
+
*/
|
|
88
|
+
addUTXO(utxo) {
|
|
89
|
+
try {
|
|
90
|
+
// Use the correct API: addUTXO(utxo, ownerAddress)
|
|
91
|
+
blockchainState.addUTXO(utxo, utxo.address)
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.log('⚠️ Error adding UTXO:', error.message)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Spend UTXOs (remove from available set)
|
|
99
|
+
* @param {Array} inputs - Array of input objects {txid, vout}
|
|
100
|
+
* @param {string} spentInTx - Optional transaction ID where UTXO was spent
|
|
101
|
+
*/
|
|
102
|
+
spendUTXOs(inputs, spentInTx = 'manual-spend') {
|
|
103
|
+
try {
|
|
104
|
+
for (const input of inputs) {
|
|
105
|
+
// Use the correct API: spendUTXO(txid, vout, spentInTx)
|
|
106
|
+
blockchainState.spendUTXO(input.txid, input.vout, spentInTx)
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.log('⚠️ Error spending UTXOs:', error.message)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create mock UTXOs for testing
|
|
115
|
+
* @param {string} address - Target address
|
|
116
|
+
* @param {number} count - Number of UTXOs to create
|
|
117
|
+
* @param {number} satoshis - Satoshis per UTXO
|
|
118
|
+
* @returns {Array} Array of created UTXOs
|
|
119
|
+
*/
|
|
120
|
+
createMockUTXOs(address, count = 5, satoshis = 100000) {
|
|
121
|
+
const mockUTXOs = []
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < count; i++) {
|
|
124
|
+
const txid = crypto.randomBytes(32).toString('hex')
|
|
125
|
+
const vout = i
|
|
126
|
+
const script = `76a914${crypto.randomBytes(20).toString('hex')}88ac` // Mock P2PKH script
|
|
127
|
+
|
|
128
|
+
const utxo = {
|
|
129
|
+
txid,
|
|
130
|
+
vout,
|
|
131
|
+
address,
|
|
132
|
+
satoshis,
|
|
133
|
+
script
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
mockUTXOs.push(utxo)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return mockUTXOs
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get total balance for an address
|
|
144
|
+
* @param {string} address - Bitcoin address
|
|
145
|
+
* @returns {number} Total satoshis
|
|
146
|
+
*/
|
|
147
|
+
getBalance(address) {
|
|
148
|
+
try {
|
|
149
|
+
const state = blockchainState.loadBlockchainState()
|
|
150
|
+
|
|
151
|
+
// Check if wallet exists
|
|
152
|
+
if (!state.wallets || !state.wallets[address]) {
|
|
153
|
+
return 0
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Return the wallet's total value
|
|
157
|
+
return state.wallets[address].totalValue || 0
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.log('⚠️ Error getting balance:', error.message)
|
|
160
|
+
return 0
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get blockchain statistics
|
|
166
|
+
* @returns {Object} Stats object
|
|
167
|
+
*/
|
|
168
|
+
getStats() {
|
|
169
|
+
try {
|
|
170
|
+
const state = blockchainState.getBlockchainStats() // This returns the full state
|
|
171
|
+
return {
|
|
172
|
+
totalUTXOs: state.metadata.totalUTXOs,
|
|
173
|
+
totalValue: state.metadata.totalValue,
|
|
174
|
+
totalWallets: state.metadata.totalWallets,
|
|
175
|
+
blockHeight: state.metadata.blockHeight,
|
|
176
|
+
lastUpdated: state.metadata.lastUpdated
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.log('⚠️ Error getting stats:', error.message)
|
|
180
|
+
return { totalUTXOs: 0, totalValue: 0, totalWallets: 0, blockHeight: 0 }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Reset blockchain state
|
|
186
|
+
*/
|
|
187
|
+
reset() {
|
|
188
|
+
try {
|
|
189
|
+
const statePath = path.join(__dirname, '../utilities/blockchain-state.json')
|
|
190
|
+
if (fs.existsSync(statePath)) {
|
|
191
|
+
fs.unlinkSync(statePath)
|
|
192
|
+
console.log('🔄 Blockchain state reset')
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.log('⚠️ Could not reset blockchain state:', error.message)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = SmartUTXOManager
|
|
@@ -24,6 +24,10 @@ var Output = require('./output')
|
|
|
24
24
|
var Script = require('../script')
|
|
25
25
|
var PrivateKey = require('../privatekey')
|
|
26
26
|
var BN = require('../crypto/bn')
|
|
27
|
+
var Interpreter = require('../script/interpreter')
|
|
28
|
+
|
|
29
|
+
// By default, we sign with sighash_forkid
|
|
30
|
+
var DEFAULT_SIGN_FLAGS = Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID
|
|
27
31
|
|
|
28
32
|
/**
|
|
29
33
|
* Represents a transaction, a set of inputs and outputs to change ownership of tokens
|
|
@@ -1205,4 +1209,39 @@ Transaction.prototype.isCoinbase = function () {
|
|
|
1205
1209
|
return (this.inputs.length === 1 && this.inputs[0].isNull())
|
|
1206
1210
|
}
|
|
1207
1211
|
|
|
1212
|
+
/**
|
|
1213
|
+
* Calculate the signature hash for an input
|
|
1214
|
+
*
|
|
1215
|
+
* @param {number} inputIndex - The index of the input to sign
|
|
1216
|
+
* @param {number} sighashType - The signature hash type (optional, defaults to ALL|FORKID)
|
|
1217
|
+
* @param {Script} subscript - The subscript to use (optional, derived from input)
|
|
1218
|
+
* @param {BN} satoshisBN - The amount in satoshis for this input (optional, derived from input)
|
|
1219
|
+
* @param {number} flags - Script verification flags (optional)
|
|
1220
|
+
* @return {Buffer} The signature hash for this input
|
|
1221
|
+
*/
|
|
1222
|
+
Transaction.prototype.sighash = function (inputIndex, sighashType, subscript, satoshisBN, flags) {
|
|
1223
|
+
sighashType = sighashType || (Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID)
|
|
1224
|
+
|
|
1225
|
+
// Get the input we're signing for
|
|
1226
|
+
var input = this.inputs[inputIndex]
|
|
1227
|
+
if (!input) {
|
|
1228
|
+
throw new Error('Input index ' + inputIndex + ' does not exist')
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// If subscript not provided, derive it from the input
|
|
1232
|
+
if (!subscript && input.output) {
|
|
1233
|
+
subscript = input.output.script
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// If satoshisBN not provided, derive it from the input
|
|
1237
|
+
if (!satoshisBN && input.output) {
|
|
1238
|
+
satoshisBN = new BN(input.output.satoshis)
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// Use default flags if not provided
|
|
1242
|
+
flags = flags || DEFAULT_SIGN_FLAGS
|
|
1243
|
+
|
|
1244
|
+
return Sighash.sighash(this, sighashType, inputIndex, subscript, satoshisBN, flags)
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1208
1247
|
module.exports = Transaction
|