@smartledger/bsv 3.1.0 → 3.2.0
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 +123 -1
- package/README.md +233 -277
- package/bsv.bundle.js +39 -0
- package/bsv.min.js +8 -8
- package/docs/ADVANCED_COVENANT_DEVELOPMENT.md +533 -0
- package/docs/COVENANT_DEVELOPMENT_RESOLVED.md +169 -0
- package/docs/CUSTOM_SCRIPT_DEVELOPMENT.md +320 -0
- package/docs/README.md +201 -0
- package/docs/block.md +46 -0
- package/docs/ecies.md +102 -0
- package/docs/index.md +104 -0
- package/docs/nchain.md +958 -0
- package/docs/networks.md +55 -0
- package/docs/preimage.md +126 -0
- package/docs/script.md +139 -0
- package/docs/transaction.md +174 -0
- package/docs/unspentoutput.md +32 -0
- package/examples/README.md +200 -0
- package/examples/basic/transaction-creation.js +534 -0
- package/examples/basic/transaction_signature_api_gap.js +178 -0
- package/examples/covenants/advanced_covenant_demo.js +219 -0
- package/examples/covenants/covenant_interface_demo.js +270 -0
- package/examples/covenants/covenant_manual_signature_resolved.js +212 -0
- package/examples/covenants/covenant_signature_template.js +117 -0
- package/examples/covenants2/covenant_bidirectional_example.js +262 -0
- package/examples/covenants2/covenant_utils_demo.js +120 -0
- package/examples/covenants2/preimage_covenant_utils.js +287 -0
- package/examples/covenants2/production_integration.js +256 -0
- package/examples/data/covenant_utxos.json +28 -0
- package/examples/data/utxos.json +26 -0
- package/examples/preimage/README.md +178 -0
- package/examples/preimage/extract_preimage_bidirectional.js +421 -0
- package/examples/preimage/generate_sample_preimage.js +208 -0
- package/examples/preimage/generate_sighash_examples.js +152 -0
- package/examples/preimage/parse_preimage.js +117 -0
- package/examples/preimage/test_preimage_extractor.js +53 -0
- package/examples/preimage/test_varint_extraction.js +95 -0
- package/examples/scripts/custom_script_helper_example.js +273 -0
- package/examples/scripts/custom_script_signature_test.js +344 -0
- package/examples/scripts/script_interpreter.js +193 -0
- package/examples/smart_contract/complete_workflow_demo.js +343 -0
- package/examples/smart_contract/covenant_builder_demo.js +176 -0
- package/examples/smart_contract/script_testing_integration.js +198 -0
- package/index.js +3 -0
- package/lib/covenant-interface.js +713 -0
- package/lib/opcode.js +14 -7
- package/lib/smart_contract/API_REFERENCE.md +754 -0
- package/lib/smart_contract/DOCUMENTATION_SUMMARY.md +201 -0
- package/lib/smart_contract/EXAMPLES.md +751 -0
- package/lib/smart_contract/QUICK_START.md +549 -0
- package/lib/smart_contract/README.md +395 -0
- package/lib/smart_contract/builder.js +452 -0
- package/lib/smart_contract/covenant.js +336 -0
- package/lib/smart_contract/covenant_builder.js +512 -0
- package/lib/smart_contract/index.js +311 -0
- package/lib/smart_contract/opcode_list.js +30 -0
- package/lib/smart_contract/opcode_map.js +1174 -0
- package/lib/smart_contract/opcodes.md +1173 -0
- package/lib/smart_contract/preimage.js +903 -0
- package/lib/smart_contract/script_tester.js +487 -0
- package/lib/smart_contract/script_utils.js +609 -0
- package/lib/smart_contract/sighash.js +310 -0
- package/lib/smart_contract/smartledger-opcode_review.md +70 -0
- package/lib/smart_contract/test_integration.js +269 -0
- package/lib/smart_contract/utxo_generator.js +367 -0
- package/package.json +43 -10
- package/utilities/blockchain-state.json +20478 -3
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SmartLedger Covenant Interface - Advanced BSV Covenant Development
|
|
3
|
+
*
|
|
4
|
+
* Provides high-level abstractions for covenant development while preserving
|
|
5
|
+
* full access to low-level BSV operations. Works alongside existing BSV API.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Simplified covenant creation and validation
|
|
9
|
+
* - Automatic preimage generation and parsing
|
|
10
|
+
* - Template-based covenant patterns
|
|
11
|
+
* - Full compatibility with existing bsv library
|
|
12
|
+
* - Granular control when needed
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const bsv = require('../index.js');
|
|
16
|
+
|
|
17
|
+
class CovenantInterface {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.bsv = bsv; // Full access to underlying BSV library
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create a covenant-ready transaction with preimage access
|
|
24
|
+
* @param {Object} config - Transaction configuration
|
|
25
|
+
* @returns {CovenantTransaction} Enhanced transaction object
|
|
26
|
+
*/
|
|
27
|
+
createCovenantTransaction(config) {
|
|
28
|
+
const tx = new bsv.Transaction();
|
|
29
|
+
|
|
30
|
+
// Add inputs
|
|
31
|
+
if (config.inputs) {
|
|
32
|
+
config.inputs.forEach(input => tx.from(input));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Add outputs
|
|
36
|
+
if (config.outputs) {
|
|
37
|
+
config.outputs.forEach(output => {
|
|
38
|
+
if (output.script) {
|
|
39
|
+
tx.addOutput(new bsv.Transaction.Output({
|
|
40
|
+
script: output.script,
|
|
41
|
+
satoshis: output.satoshis
|
|
42
|
+
}));
|
|
43
|
+
} else {
|
|
44
|
+
tx.to(output.address, output.satoshis);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Set fee configuration
|
|
50
|
+
if (config.feePerKb) {
|
|
51
|
+
tx.feePerKb(config.feePerKb);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return new CovenantTransaction(tx, this);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create manual signature using correct BSV API
|
|
59
|
+
* @param {Transaction} transaction - BSV transaction
|
|
60
|
+
* @param {PrivateKey} privateKey - Private key to sign with
|
|
61
|
+
* @param {number} inputIndex - Input index
|
|
62
|
+
* @param {Script} lockingScript - Locking script being spent
|
|
63
|
+
* @param {number} satoshis - Amount in satoshis
|
|
64
|
+
* @param {number} sighashType - Signature hash type
|
|
65
|
+
* @returns {Buffer} Complete signature with sighash type
|
|
66
|
+
*/
|
|
67
|
+
createSignature(transaction, privateKey, inputIndex, lockingScript, satoshis, sighashType = null) {
|
|
68
|
+
sighashType = sighashType || (bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID);
|
|
69
|
+
|
|
70
|
+
const signature = bsv.Transaction.sighash.sign(
|
|
71
|
+
transaction,
|
|
72
|
+
privateKey,
|
|
73
|
+
sighashType,
|
|
74
|
+
inputIndex,
|
|
75
|
+
lockingScript,
|
|
76
|
+
new bsv.crypto.BN(satoshis)
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return Buffer.concat([signature.toDER(), Buffer.from([sighashType])]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Get transaction preimage for covenant validation
|
|
84
|
+
* @param {Transaction} transaction - BSV transaction
|
|
85
|
+
* @param {number} inputIndex - Input index
|
|
86
|
+
* @param {Script} lockingScript - Locking script
|
|
87
|
+
* @param {number} satoshis - Amount in satoshis
|
|
88
|
+
* @param {number} sighashType - Signature hash type
|
|
89
|
+
* @returns {CovenantPreimage} Parsed preimage object
|
|
90
|
+
*/
|
|
91
|
+
getPreimage(transaction, inputIndex, lockingScript, satoshis, sighashType = null) {
|
|
92
|
+
sighashType = sighashType || (bsv.crypto.Signature.SIGHASH_ALL | bsv.crypto.Signature.SIGHASH_FORKID);
|
|
93
|
+
|
|
94
|
+
const preimage = bsv.Transaction.sighash.sighash(
|
|
95
|
+
transaction,
|
|
96
|
+
sighashType,
|
|
97
|
+
inputIndex,
|
|
98
|
+
lockingScript,
|
|
99
|
+
new bsv.crypto.BN(satoshis)
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return new CovenantPreimage(preimage);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Covenant Template: State Machine
|
|
107
|
+
* @param {Array} states - Array of state definitions
|
|
108
|
+
* @param {number} currentState - Current state index
|
|
109
|
+
* @param {PublicKey} authorizedKey - Key authorized for state transitions
|
|
110
|
+
* @returns {Script} State machine covenant script
|
|
111
|
+
*/
|
|
112
|
+
createStateMachine(states, currentState, authorizedKey) {
|
|
113
|
+
const script = new bsv.Script();
|
|
114
|
+
|
|
115
|
+
// Check current state
|
|
116
|
+
script.add(Buffer.from([currentState]));
|
|
117
|
+
script.add(bsv.Opcode.OP_EQUAL);
|
|
118
|
+
script.add(bsv.Opcode.OP_VERIFY);
|
|
119
|
+
|
|
120
|
+
// Validate state transition logic
|
|
121
|
+
for (let i = 0; i < states.length - 1; i++) {
|
|
122
|
+
if (i === currentState) {
|
|
123
|
+
// Valid next states
|
|
124
|
+
states[i].nextStates.forEach(nextState => {
|
|
125
|
+
script.add(Buffer.from([nextState]));
|
|
126
|
+
script.add(bsv.Opcode.OP_EQUAL);
|
|
127
|
+
script.add(bsv.Opcode.OP_IF);
|
|
128
|
+
// Add transition logic here
|
|
129
|
+
script.add(bsv.Opcode.OP_ENDIF);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Require authorized signature
|
|
135
|
+
script.add(bsv.Opcode.OP_DUP);
|
|
136
|
+
script.add(bsv.Opcode.OP_HASH160);
|
|
137
|
+
script.add(authorizedKey.toAddress().hashBuffer);
|
|
138
|
+
script.add(bsv.Opcode.OP_EQUALVERIFY);
|
|
139
|
+
script.add(bsv.Opcode.OP_CHECKSIG);
|
|
140
|
+
|
|
141
|
+
return script;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Covenant Template: Escrow with Arbitration
|
|
146
|
+
* @param {PublicKey} buyer - Buyer public key
|
|
147
|
+
* @param {PublicKey} seller - Seller public key
|
|
148
|
+
* @param {PublicKey} arbitrator - Arbitrator public key
|
|
149
|
+
* @param {number} timelock - Optional timelock for refund
|
|
150
|
+
* @returns {Script} Escrow covenant script
|
|
151
|
+
*/
|
|
152
|
+
createEscrow(buyer, seller, arbitrator, timelock = null) {
|
|
153
|
+
const script = new bsv.Script();
|
|
154
|
+
|
|
155
|
+
if (timelock) {
|
|
156
|
+
// Time-locked refund path
|
|
157
|
+
script.add(bsv.Opcode.OP_IF);
|
|
158
|
+
script.add(Buffer.from(timelock.toString(16).padStart(8, '0'), 'hex').reverse());
|
|
159
|
+
script.add(bsv.Opcode.OP_CHECKLOCKTIMEVERIFY);
|
|
160
|
+
script.add(bsv.Opcode.OP_DROP);
|
|
161
|
+
script.add(buyer.toBuffer());
|
|
162
|
+
script.add(bsv.Opcode.OP_CHECKSIG);
|
|
163
|
+
script.add(bsv.Opcode.OP_ELSE);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 2-of-3 multisig: buyer + seller, buyer + arbitrator, or seller + arbitrator
|
|
167
|
+
script.add(bsv.Opcode.OP_2);
|
|
168
|
+
script.add(buyer.toBuffer());
|
|
169
|
+
script.add(seller.toBuffer());
|
|
170
|
+
script.add(arbitrator.toBuffer());
|
|
171
|
+
script.add(bsv.Opcode.OP_3);
|
|
172
|
+
script.add(bsv.Opcode.OP_CHECKMULTISIG);
|
|
173
|
+
|
|
174
|
+
if (timelock) {
|
|
175
|
+
script.add(bsv.Opcode.OP_ENDIF);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return script;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Covenant Template: Token Transfer with Rules
|
|
183
|
+
* @param {PublicKey} newOwner - New owner public key
|
|
184
|
+
* @param {number} minAmount - Minimum transfer amount
|
|
185
|
+
* @param {Script} additionalRules - Optional additional validation rules
|
|
186
|
+
* @returns {Script} Token covenant script
|
|
187
|
+
*/
|
|
188
|
+
createTokenTransfer(newOwner, minAmount, additionalRules = null) {
|
|
189
|
+
const script = new bsv.Script();
|
|
190
|
+
|
|
191
|
+
// Check minimum amount
|
|
192
|
+
if (minAmount > 0) {
|
|
193
|
+
script.add(bsv.Opcode.OP_DUP);
|
|
194
|
+
script.add(Buffer.from(minAmount.toString(16), 'hex'));
|
|
195
|
+
script.add(bsv.Opcode.OP_GREATERTHANOREQUAL);
|
|
196
|
+
script.add(bsv.Opcode.OP_VERIFY);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Add custom rules
|
|
200
|
+
if (additionalRules) {
|
|
201
|
+
script.add(additionalRules.toBuffer());
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Transfer to new owner
|
|
205
|
+
script.add(bsv.Opcode.OP_DUP);
|
|
206
|
+
script.add(bsv.Opcode.OP_HASH160);
|
|
207
|
+
script.add(newOwner.toAddress().hashBuffer);
|
|
208
|
+
script.add(bsv.Opcode.OP_EQUALVERIFY);
|
|
209
|
+
script.add(bsv.Opcode.OP_CHECKSIG);
|
|
210
|
+
|
|
211
|
+
return script;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Validate covenant execution
|
|
216
|
+
* @param {Transaction} transaction - Transaction to validate
|
|
217
|
+
* @param {number} inputIndex - Input index
|
|
218
|
+
* @param {Script} unlockingScript - Unlocking script
|
|
219
|
+
* @param {Script} lockingScript - Locking script
|
|
220
|
+
* @returns {CovenantValidation} Validation result
|
|
221
|
+
*/
|
|
222
|
+
validateCovenant(transaction, inputIndex, unlockingScript, lockingScript) {
|
|
223
|
+
try {
|
|
224
|
+
const interpreter = new bsv.Script.Interpreter();
|
|
225
|
+
const flags = bsv.Script.Interpreter.SCRIPT_VERIFY_P2SH |
|
|
226
|
+
bsv.Script.Interpreter.SCRIPT_VERIFY_STRICTENC |
|
|
227
|
+
bsv.Script.Interpreter.SCRIPT_ENABLE_SIGHASH_FORKID |
|
|
228
|
+
bsv.Script.Interpreter.SCRIPT_ENABLE_MAGNETIC_OPCODES |
|
|
229
|
+
bsv.Script.Interpreter.SCRIPT_ENABLE_MONOLITH_OPCODES;
|
|
230
|
+
|
|
231
|
+
const isValid = interpreter.verify(unlockingScript, lockingScript, transaction, inputIndex, flags);
|
|
232
|
+
|
|
233
|
+
return new CovenantValidation(isValid, interpreter.errstr || 'Success');
|
|
234
|
+
} catch (error) {
|
|
235
|
+
return new CovenantValidation(false, error.message);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Advanced covenant construction based on nChain PUSHTX techniques
|
|
241
|
+
* @param {string} type - Covenant type ('pushtx', 'perpetual', 'pels')
|
|
242
|
+
* @param {Object} params - Covenant parameters
|
|
243
|
+
* @returns {Script} Advanced covenant script
|
|
244
|
+
*/
|
|
245
|
+
createAdvancedCovenant(type, params = {}) {
|
|
246
|
+
const templates = {
|
|
247
|
+
'pushtx': this.createPushtxCovenant,
|
|
248
|
+
'perpetual': this.createPerpetualCovenant,
|
|
249
|
+
'pels': this.createPerpetualCovenant, // PELS is a type of perpetual covenant
|
|
250
|
+
'introspection': this.createIntrospectionCovenant
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const templateFn = templates[type];
|
|
254
|
+
if (!templateFn) {
|
|
255
|
+
throw new Error(`Unknown advanced covenant type: ${type}. Available types: ${Object.keys(templates).join(', ')}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return templateFn.call(this, params);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* PUSHTX implementation based on nChain white paper WP1605
|
|
263
|
+
* Generates signature in-script to push current transaction to stack
|
|
264
|
+
* @param {Object} params - PUSHTX parameters
|
|
265
|
+
* @returns {Script} PUSHTX covenant script
|
|
266
|
+
*/
|
|
267
|
+
createPushtxCovenant(params = {}) {
|
|
268
|
+
const {
|
|
269
|
+
publicKey = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798',
|
|
270
|
+
enforceOutputs = true,
|
|
271
|
+
sighashType = 0x41 // SIGHASH_ALL | SIGHASH_FORKID
|
|
272
|
+
} = params;
|
|
273
|
+
|
|
274
|
+
const script = new bsv.Script();
|
|
275
|
+
|
|
276
|
+
// Message construction for covenant validation (items 1-7 from preimage)
|
|
277
|
+
script.add(bsv.Opcode.OP_2DUP)
|
|
278
|
+
.add(bsv.Opcode.OP_HASH256) // Hash outputs (item 9)
|
|
279
|
+
.add(bsv.Opcode.OP_SWAP);
|
|
280
|
+
|
|
281
|
+
// Add sequence number (item 8)
|
|
282
|
+
script.add(Buffer.from('ffffffff', 'hex'))
|
|
283
|
+
.add(bsv.Opcode.OP_CAT)
|
|
284
|
+
.add(bsv.Opcode.OP_SWAP)
|
|
285
|
+
.add(bsv.Opcode.OP_CAT);
|
|
286
|
+
|
|
287
|
+
// Add locktime (item 10) and sighash flag (item 11)
|
|
288
|
+
script.add(Buffer.from('00000000', 'hex')) // nLockTime
|
|
289
|
+
.add(Buffer.from('41000000', 'hex')) // sighashType in little-endian
|
|
290
|
+
.add(bsv.Opcode.OP_CAT)
|
|
291
|
+
.add(bsv.Opcode.OP_CAT);
|
|
292
|
+
|
|
293
|
+
// PUSHTX signature generation (k=a=1 optimization)
|
|
294
|
+
script.add(bsv.Opcode.OP_HASH256);
|
|
295
|
+
|
|
296
|
+
// Add generator point x-coordinate
|
|
297
|
+
const generatorX = '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798';
|
|
298
|
+
script.add(Buffer.from(generatorX, 'hex'))
|
|
299
|
+
.add(bsv.Opcode.OP_ADD);
|
|
300
|
+
|
|
301
|
+
// Modular arithmetic with secp256k1 order
|
|
302
|
+
const secp256k1Order = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141';
|
|
303
|
+
script.add(Buffer.from(secp256k1Order, 'hex'))
|
|
304
|
+
.add(bsv.Opcode.OP_MOD);
|
|
305
|
+
|
|
306
|
+
// Convert to canonical DER format
|
|
307
|
+
this._addDERConversion(script);
|
|
308
|
+
|
|
309
|
+
// Add sighash flag and public key
|
|
310
|
+
script.add(Buffer.from([sighashType]))
|
|
311
|
+
.add(bsv.Opcode.OP_CAT)
|
|
312
|
+
.add(Buffer.from(publicKey, 'hex'));
|
|
313
|
+
|
|
314
|
+
// Verify signature with OP_CHECKSIGVERIFY
|
|
315
|
+
script.add(bsv.Opcode.OP_CHECKSIGVERIFY);
|
|
316
|
+
|
|
317
|
+
if (enforceOutputs) {
|
|
318
|
+
// Message is now on stack - add output enforcement
|
|
319
|
+
this._addOutputValidation(script, params);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return script;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Perpetually Enforcing Locking Script (PELS) from nChain paper
|
|
327
|
+
* Forces all future transactions to maintain same locking script and value
|
|
328
|
+
* @param {Object} params - PELS parameters
|
|
329
|
+
* @returns {Script} PELS covenant script
|
|
330
|
+
*/
|
|
331
|
+
createPerpetualCovenant(params = {}) {
|
|
332
|
+
const {
|
|
333
|
+
publicKeyHash,
|
|
334
|
+
feeDeduction = 512,
|
|
335
|
+
enforceScript = true,
|
|
336
|
+
enforceValue = true
|
|
337
|
+
} = params;
|
|
338
|
+
|
|
339
|
+
if (!publicKeyHash) {
|
|
340
|
+
throw new Error('publicKeyHash required for perpetual covenant');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const script = new bsv.Script();
|
|
344
|
+
|
|
345
|
+
// Construct message with fee-adjusted output validation
|
|
346
|
+
script.add(bsv.Opcode.OP_2DUP)
|
|
347
|
+
.add(bsv.Opcode.OP_BIN2NUM);
|
|
348
|
+
|
|
349
|
+
// Deduct transaction fee if specified
|
|
350
|
+
if (feeDeduction > 0) {
|
|
351
|
+
script.add(Buffer.from(feeDeduction.toString(16).padStart(4, '0'), 'hex'))
|
|
352
|
+
.add(bsv.Opcode.OP_SUB);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Convert back to 8-byte little-endian
|
|
356
|
+
script.add(Buffer.from([0x08]))
|
|
357
|
+
.add(bsv.Opcode.OP_NUM2BIN)
|
|
358
|
+
.add(bsv.Opcode.OP_SWAP)
|
|
359
|
+
.add(bsv.Opcode.OP_CAT);
|
|
360
|
+
|
|
361
|
+
// Hash the new output for message construction
|
|
362
|
+
script.add(bsv.Opcode.OP_HASH256);
|
|
363
|
+
|
|
364
|
+
// Build complete sighash preimage
|
|
365
|
+
this._addPreimageConstruction(script, params);
|
|
366
|
+
|
|
367
|
+
// Generate and verify PUSHTX signature
|
|
368
|
+
this._addPushtxSignature(script, params);
|
|
369
|
+
|
|
370
|
+
// Extract and validate output structure against previous output
|
|
371
|
+
// Skip to value field (104 bytes into preimage)
|
|
372
|
+
script.add(bsv.Opcode.OP_SWAP)
|
|
373
|
+
.add(Buffer.from([0x68])) // 104 decimal = 0x68 hex
|
|
374
|
+
.add(bsv.Opcode.OP_SPLIT)
|
|
375
|
+
.add(bsv.Opcode.OP_NIP)
|
|
376
|
+
.add(bsv.Opcode.OP_SWAP)
|
|
377
|
+
.add(Buffer.from([0x08])) // 8 bytes for value
|
|
378
|
+
.add(bsv.Opcode.OP_SPLIT)
|
|
379
|
+
.add(bsv.Opcode.OP_SWAP)
|
|
380
|
+
.add(bsv.Opcode.OP_CAT);
|
|
381
|
+
|
|
382
|
+
// Verify current output matches previous output format
|
|
383
|
+
if (enforceValue || enforceScript) {
|
|
384
|
+
script.add(bsv.Opcode.OP_EQUALVERIFY);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Final P2PKH check for authenticity
|
|
388
|
+
script.add(bsv.Opcode.OP_DUP)
|
|
389
|
+
.add(bsv.Opcode.OP_HASH160)
|
|
390
|
+
.add(Buffer.from(publicKeyHash, 'hex'))
|
|
391
|
+
.add(bsv.Opcode.OP_EQUALVERIFY)
|
|
392
|
+
.add(bsv.Opcode.OP_CHECKSIG);
|
|
393
|
+
|
|
394
|
+
return script;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Transaction introspection covenant using preimage analysis
|
|
399
|
+
* @param {Object} params - Introspection parameters
|
|
400
|
+
* @returns {Script} Introspection covenant script
|
|
401
|
+
*/
|
|
402
|
+
createIntrospectionCovenant(params = {}) {
|
|
403
|
+
const {
|
|
404
|
+
validateInputs = false,
|
|
405
|
+
validateOutputs = true,
|
|
406
|
+
validateSequence = false,
|
|
407
|
+
validateLocktime = false
|
|
408
|
+
} = params;
|
|
409
|
+
|
|
410
|
+
const script = new bsv.Script();
|
|
411
|
+
|
|
412
|
+
// Get preimage for analysis
|
|
413
|
+
this._addPushtxSignature(script, params);
|
|
414
|
+
|
|
415
|
+
// Parse preimage fields for validation
|
|
416
|
+
if (validateInputs) {
|
|
417
|
+
script.add(bsv.Opcode.OP_DUP)
|
|
418
|
+
.add(Buffer.from([0x04, 0x24])) // Skip to hashPrevouts
|
|
419
|
+
.add(bsv.Opcode.OP_SPLIT)
|
|
420
|
+
.add(bsv.Opcode.OP_DROP);
|
|
421
|
+
// Add input validation logic here
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (validateOutputs) {
|
|
425
|
+
// Extract hashOutputs for validation
|
|
426
|
+
script.add(bsv.Opcode.OP_DUP)
|
|
427
|
+
.add(Buffer.from([0x64])) // Skip to hashOutputs position
|
|
428
|
+
.add(bsv.Opcode.OP_SPLIT)
|
|
429
|
+
.add(bsv.Opcode.OP_NIP)
|
|
430
|
+
.add(Buffer.from([0x20])) // 32 bytes
|
|
431
|
+
.add(bsv.Opcode.OP_SPLIT)
|
|
432
|
+
.add(bsv.Opcode.OP_DROP);
|
|
433
|
+
// Add output validation logic here
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return script;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Helper methods for advanced covenant construction
|
|
440
|
+
|
|
441
|
+
_addDERConversion(script) {
|
|
442
|
+
// Convert s value to canonical form (s <= n/2)
|
|
443
|
+
const secp256k1HalfOrder = '7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0';
|
|
444
|
+
|
|
445
|
+
script.add(bsv.Opcode.OP_DUP)
|
|
446
|
+
.add(Buffer.from(secp256k1HalfOrder, 'hex'))
|
|
447
|
+
.add(bsv.Opcode.OP_GREATERTHAN)
|
|
448
|
+
.add(bsv.Opcode.OP_IF);
|
|
449
|
+
|
|
450
|
+
// If s > n/2, compute n - s to prevent malleability
|
|
451
|
+
const secp256k1Order = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141';
|
|
452
|
+
script.add(Buffer.from(secp256k1Order, 'hex'))
|
|
453
|
+
.add(bsv.Opcode.OP_SWAP)
|
|
454
|
+
.add(bsv.Opcode.OP_SUB);
|
|
455
|
+
|
|
456
|
+
script.add(bsv.Opcode.OP_ENDIF);
|
|
457
|
+
|
|
458
|
+
// Add DER sequence formatting
|
|
459
|
+
script.add(bsv.Opcode.OP_SIZE)
|
|
460
|
+
.add(bsv.Opcode.OP_DUP)
|
|
461
|
+
.add(Buffer.from([0x24])) // 36 bytes total length
|
|
462
|
+
.add(bsv.Opcode.OP_ADD)
|
|
463
|
+
.add(Buffer.from([0x30])) // DER sequence tag
|
|
464
|
+
.add(bsv.Opcode.OP_SWAP)
|
|
465
|
+
.add(bsv.Opcode.OP_CAT);
|
|
466
|
+
|
|
467
|
+
// Add r component (using generator x-coordinate as r)
|
|
468
|
+
script.add(Buffer.from('022079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179802', 'hex'))
|
|
469
|
+
.add(bsv.Opcode.OP_CAT)
|
|
470
|
+
.add(bsv.Opcode.OP_SWAP)
|
|
471
|
+
.add(bsv.Opcode.OP_CAT)
|
|
472
|
+
.add(bsv.Opcode.OP_SWAP)
|
|
473
|
+
.add(bsv.Opcode.OP_CAT);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
_addOutputValidation(script, params) {
|
|
477
|
+
const { enforceValue, enforceScript, enforceCount } = params;
|
|
478
|
+
|
|
479
|
+
// Output validation after CHECKSIGVERIFY leaves preimage on stack
|
|
480
|
+
if (enforceValue) {
|
|
481
|
+
script.add(bsv.Opcode.OP_DUP)
|
|
482
|
+
.add(Buffer.from([0x08])) // 8 bytes for value
|
|
483
|
+
.add(bsv.Opcode.OP_SPLIT);
|
|
484
|
+
// Add value enforcement logic
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (enforceScript) {
|
|
488
|
+
// Extract and validate locking script
|
|
489
|
+
script.add(bsv.Opcode.OP_DUP)
|
|
490
|
+
.add(bsv.Opcode.OP_SIZE)
|
|
491
|
+
.add(Buffer.from([0x09])) // Skip value + length byte
|
|
492
|
+
.add(bsv.Opcode.OP_SUB)
|
|
493
|
+
.add(bsv.Opcode.OP_SPLIT)
|
|
494
|
+
.add(bsv.Opcode.OP_NIP);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (enforceCount) {
|
|
498
|
+
// Validate number of outputs in hashOutputs
|
|
499
|
+
// This requires parsing the actual outputs, not just hash
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
_addPreimageConstruction(script, params) {
|
|
504
|
+
const {
|
|
505
|
+
version = 1,
|
|
506
|
+
locktime = 0,
|
|
507
|
+
sighashType = 0x41,
|
|
508
|
+
sequence = 0xffffffff
|
|
509
|
+
} = params;
|
|
510
|
+
|
|
511
|
+
// Construct fields for complete BIP143 preimage
|
|
512
|
+
// Items 8-11: sequence, hashOutputs, locktime, sighashType
|
|
513
|
+
script.add(Buffer.from(sequence.toString(16).padStart(8, '0'), 'hex'))
|
|
514
|
+
.add(bsv.Opcode.OP_CAT)
|
|
515
|
+
.add(bsv.Opcode.OP_SWAP) // hashOutputs already computed
|
|
516
|
+
.add(bsv.Opcode.OP_CAT)
|
|
517
|
+
.add(Buffer.from(locktime.toString(16).padStart(8, '0'), 'hex'))
|
|
518
|
+
.add(bsv.Opcode.OP_CAT)
|
|
519
|
+
.add(Buffer.from(sighashType.toString(16).padStart(8, '0'), 'hex'))
|
|
520
|
+
.add(bsv.Opcode.OP_CAT);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
_addPushtxSignature(script, params) {
|
|
524
|
+
const {
|
|
525
|
+
publicKey = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798',
|
|
526
|
+
sighashType = 0x41
|
|
527
|
+
} = params;
|
|
528
|
+
|
|
529
|
+
// Generate signature using PUSHTX technique (k=a=1)
|
|
530
|
+
script.add(bsv.Opcode.OP_HASH256);
|
|
531
|
+
|
|
532
|
+
// s = z + Gx mod n (where z = hash256(message), Gx = generator x-coord)
|
|
533
|
+
const generatorX = '79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798';
|
|
534
|
+
script.add(Buffer.from(generatorX, 'hex'))
|
|
535
|
+
.add(bsv.Opcode.OP_ADD);
|
|
536
|
+
|
|
537
|
+
// Modular reduction with secp256k1 order
|
|
538
|
+
const secp256k1Order = 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141';
|
|
539
|
+
script.add(Buffer.from(secp256k1Order, 'hex'))
|
|
540
|
+
.add(bsv.Opcode.OP_MOD);
|
|
541
|
+
|
|
542
|
+
// Convert to DER format and verify
|
|
543
|
+
this._addDERConversion(script);
|
|
544
|
+
script.add(Buffer.from([sighashType]))
|
|
545
|
+
.add(bsv.Opcode.OP_CAT)
|
|
546
|
+
.add(Buffer.from(publicKey, 'hex'))
|
|
547
|
+
.add(bsv.Opcode.OP_CHECKSIGVERIFY);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Enhanced transaction with covenant-specific methods
|
|
553
|
+
*/
|
|
554
|
+
class CovenantTransaction {
|
|
555
|
+
constructor(transaction, covenantInterface) {
|
|
556
|
+
this.tx = transaction;
|
|
557
|
+
this.covenant = covenantInterface;
|
|
558
|
+
this.preimages = new Map();
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Get preimage for specific input with caching
|
|
563
|
+
*/
|
|
564
|
+
getPreimage(inputIndex, lockingScript, satoshis) {
|
|
565
|
+
const key = `${inputIndex}-${lockingScript.toHex()}-${satoshis}`;
|
|
566
|
+
|
|
567
|
+
if (!this.preimages.has(key)) {
|
|
568
|
+
const preimage = this.covenant.getPreimage(this.tx, inputIndex, lockingScript, satoshis);
|
|
569
|
+
this.preimages.set(key, preimage);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return this.preimages.get(key);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Sign input with covenant-compatible signature
|
|
577
|
+
*/
|
|
578
|
+
signInput(inputIndex, privateKey, lockingScript, satoshis) {
|
|
579
|
+
const signature = this.covenant.createSignature(this.tx, privateKey, inputIndex, lockingScript, satoshis);
|
|
580
|
+
|
|
581
|
+
const unlockingScript = new bsv.Script()
|
|
582
|
+
.add(signature)
|
|
583
|
+
.add(privateKey.publicKey.toBuffer());
|
|
584
|
+
|
|
585
|
+
this.tx.inputs[inputIndex].setScript(unlockingScript);
|
|
586
|
+
return this;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Access underlying BSV transaction
|
|
591
|
+
*/
|
|
592
|
+
getTransaction() {
|
|
593
|
+
return this.tx;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Verify transaction with enhanced error reporting
|
|
598
|
+
*/
|
|
599
|
+
verify() {
|
|
600
|
+
try {
|
|
601
|
+
return this.tx.verify();
|
|
602
|
+
} catch (error) {
|
|
603
|
+
console.error('Transaction validation error:', error.message);
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Parsed preimage with convenient access methods
|
|
611
|
+
*/
|
|
612
|
+
class CovenantPreimage {
|
|
613
|
+
constructor(preimageHex) {
|
|
614
|
+
this.hex = preimageHex;
|
|
615
|
+
this.buffer = Buffer.from(preimageHex, 'hex');
|
|
616
|
+
this.parseFields();
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
parseFields() {
|
|
620
|
+
let offset = 0;
|
|
621
|
+
|
|
622
|
+
// BIP143 sighash preimage structure (detailed field-by-field parsing)
|
|
623
|
+
// Field 1: nVersion (4 bytes, little-endian)
|
|
624
|
+
this.nVersion = this.buffer.subarray(offset, offset + 4);
|
|
625
|
+
this.nVersionValue = this.buffer.readUInt32LE(offset);
|
|
626
|
+
offset += 4;
|
|
627
|
+
|
|
628
|
+
// Field 2: hashPrevouts (32 bytes) - double SHA256 of all input outpoints
|
|
629
|
+
this.hashPrevouts = this.buffer.subarray(offset, offset + 32);
|
|
630
|
+
offset += 32;
|
|
631
|
+
|
|
632
|
+
// Field 3: hashSequence (32 bytes) - double SHA256 of all input sequences
|
|
633
|
+
this.hashSequence = this.buffer.subarray(offset, offset + 32);
|
|
634
|
+
offset += 32;
|
|
635
|
+
|
|
636
|
+
// Field 4: outpoint (36 bytes) - prevTxId (32) + outputIndex (4, little-endian)
|
|
637
|
+
this.outpoint = this.buffer.subarray(offset, offset + 36);
|
|
638
|
+
this.prevTxId = this.buffer.subarray(offset, offset + 32);
|
|
639
|
+
offset += 32;
|
|
640
|
+
this.outputIndex = this.buffer.subarray(offset, offset + 4);
|
|
641
|
+
this.outputIndexValue = this.buffer.readUInt32LE(offset);
|
|
642
|
+
offset += 4;
|
|
643
|
+
|
|
644
|
+
// Field 5: scriptCode - variable length with proper parsing
|
|
645
|
+
const scriptLen = this.readVarInt(offset);
|
|
646
|
+
offset += this.getVarIntLength(scriptLen);
|
|
647
|
+
this.scriptCode = this.buffer.subarray(offset, offset + scriptLen);
|
|
648
|
+
offset += scriptLen;
|
|
649
|
+
|
|
650
|
+
// Field 6: amount (8 bytes, little-endian) - value of UTXO being spent
|
|
651
|
+
this.amount = this.buffer.subarray(offset, offset + 8);
|
|
652
|
+
this.amountValue = this.buffer.readBigUInt64LE(offset);
|
|
653
|
+
offset += 8;
|
|
654
|
+
|
|
655
|
+
// Field 7: nSequence (4 bytes, little-endian)
|
|
656
|
+
this.nSequence = this.buffer.subarray(offset, offset + 4);
|
|
657
|
+
this.nSequenceValue = this.buffer.readUInt32LE(offset);
|
|
658
|
+
offset += 4;
|
|
659
|
+
|
|
660
|
+
// Field 8: hashOutputs (32 bytes) - double SHA256 of all outputs
|
|
661
|
+
this.hashOutputs = this.buffer.subarray(offset, offset + 32);
|
|
662
|
+
offset += 32;
|
|
663
|
+
|
|
664
|
+
// Field 9: nLockTime (4 bytes, little-endian)
|
|
665
|
+
this.nLockTime = this.buffer.subarray(offset, offset + 4);
|
|
666
|
+
this.nLockTimeValue = this.buffer.readUInt32LE(offset);
|
|
667
|
+
offset += 4;
|
|
668
|
+
|
|
669
|
+
// Field 10: sighashType (4 bytes, little-endian)
|
|
670
|
+
this.sighashType = this.buffer.subarray(offset, offset + 4);
|
|
671
|
+
this.sighashTypeValue = this.buffer.readUInt32LE(offset);
|
|
672
|
+
offset += 4;
|
|
673
|
+
|
|
674
|
+
// Validate preimage structure (typical 108+ bytes)
|
|
675
|
+
this.isValid = offset === this.buffer.length && this.buffer.length >= 108;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
readVarInt(offset) {
|
|
679
|
+
const first = this.buffer.readUInt8(offset);
|
|
680
|
+
if (first < 0xfd) return first;
|
|
681
|
+
if (first === 0xfd) return this.buffer.readUInt16LE(offset + 1);
|
|
682
|
+
if (first === 0xfe) return this.buffer.readUInt32LE(offset + 1);
|
|
683
|
+
if (first === 0xff) return this.buffer.readBigUInt64LE(offset + 1);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
getVarIntLength(value) {
|
|
687
|
+
if (value < 0xfd) return 1;
|
|
688
|
+
if (value <= 0xffff) return 3;
|
|
689
|
+
if (value <= 0xffffffff) return 5;
|
|
690
|
+
return 9;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Covenant validation result
|
|
696
|
+
*/
|
|
697
|
+
class CovenantValidation {
|
|
698
|
+
constructor(isValid, message) {
|
|
699
|
+
this.isValid = isValid;
|
|
700
|
+
this.message = message;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
toString() {
|
|
704
|
+
return `${this.isValid ? 'VALID' : 'INVALID'}: ${this.message}`;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
module.exports = {
|
|
709
|
+
CovenantInterface,
|
|
710
|
+
CovenantTransaction,
|
|
711
|
+
CovenantPreimage,
|
|
712
|
+
CovenantValidation
|
|
713
|
+
};
|