cashscript 0.11.4 → 0.12.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.
@@ -1,426 +0,0 @@
1
- import { binToHex, decodeCashAddress, } from '@bitauth/libauth';
2
- import { encodeFunctionArguments } from '../Argument.js';
3
- import { debugTemplate } from '../debugging.js';
4
- import { isP2PKHUnlocker, isStandardUnlockableUtxo, } from '../interfaces.js';
5
- import { addHexPrefixExceptEmpty, formatBytecodeForDebugging, formatParametersForDebugging, generateTemplateScenarioBytecode, generateTemplateScenarioKeys, generateTemplateScenarioParametersFunctionIndex, generateTemplateScenarioParametersValues, generateTemplateScenarioTransactionOutputLockingBytecode, getHashTypeName, getSignatureAlgorithmName, serialiseTokenDetails, } from '../LibauthTemplate.js';
6
- import SignatureTemplate from '../SignatureTemplate.js';
7
- import { addressToLockScript } from '../utils.js';
8
- /**
9
- * Generates template entities for P2PKH (Pay to Public Key Hash) placeholder scripts.
10
- *
11
- * Follows the WalletTemplateEntity specification from:
12
- * https://ide.bitauth.com/authentication-template-v0.schema.json
13
- *
14
- */
15
- export const generateTemplateEntitiesP2PKH = (inputIndex) => {
16
- const lockScriptName = `p2pkh_placeholder_lock_${inputIndex}`;
17
- const unlockScriptName = `p2pkh_placeholder_unlock_${inputIndex}`;
18
- return {
19
- [`signer_${inputIndex}`]: {
20
- scripts: [lockScriptName, unlockScriptName],
21
- description: `placeholder_key_${inputIndex}`,
22
- name: `P2PKH Signer (input #${inputIndex})`,
23
- variables: {
24
- [`placeholder_key_${inputIndex}`]: {
25
- description: '',
26
- name: `P2PKH Placeholder Key (input #${inputIndex})`,
27
- type: 'Key',
28
- },
29
- },
30
- },
31
- };
32
- };
33
- /**
34
- * Generates template entities for P2SH (Pay to Script Hash) placeholder scripts.
35
- *
36
- * Follows the WalletTemplateEntity specification from:
37
- * https://ide.bitauth.com/authentication-template-v0.schema.json
38
- *
39
- */
40
- export const generateTemplateEntitiesP2SH = (contract, abiFunction, encodedFunctionArgs, inputIndex) => {
41
- const entities = {
42
- [contract.artifact.contractName + '_input' + inputIndex + '_parameters']: {
43
- description: 'Contract creation and function parameters',
44
- name: `${contract.artifact.contractName} (input #${inputIndex})`,
45
- scripts: [
46
- getLockScriptName(contract),
47
- getUnlockScriptName(contract, abiFunction, inputIndex),
48
- ],
49
- variables: createWalletTemplateVariables(contract.artifact, abiFunction, encodedFunctionArgs),
50
- },
51
- };
52
- // function_index is a special variable that indicates the function to execute
53
- if (contract.artifact.abi.length > 1) {
54
- entities[contract.artifact.contractName + '_input' + inputIndex + '_parameters'].variables.function_index = {
55
- description: 'Script function index to execute',
56
- name: 'function_index',
57
- type: 'WalletData',
58
- };
59
- }
60
- return entities;
61
- };
62
- const createWalletTemplateVariables = (artifact, abiFunction, encodedFunctionArgs) => {
63
- const functionParameters = Object.fromEntries(abiFunction.inputs.map((input, index) => ([
64
- input.name,
65
- {
66
- description: `"${input.name}" parameter of function "${abiFunction.name}"`,
67
- name: input.name,
68
- type: encodedFunctionArgs[index] instanceof SignatureTemplate ? 'Key' : 'WalletData',
69
- },
70
- ])));
71
- const constructorParameters = Object.fromEntries(artifact.constructorInputs.map((input) => ([
72
- input.name,
73
- {
74
- description: `"${input.name}" parameter of this contract`,
75
- name: input.name,
76
- type: 'WalletData',
77
- },
78
- ])));
79
- return { ...functionParameters, ...constructorParameters };
80
- };
81
- /**
82
- * Generates template scripts for P2PKH (Pay to Public Key Hash) placeholder scripts.
83
- *
84
- * Follows the WalletTemplateScript specification from:
85
- * https://ide.bitauth.com/authentication-template-v0.schema.json
86
- *
87
- */
88
- export const generateTemplateScriptsP2PKH = (template, inputIndex) => {
89
- const scripts = {};
90
- const lockScriptName = `p2pkh_placeholder_lock_${inputIndex}`;
91
- const unlockScriptName = `p2pkh_placeholder_unlock_${inputIndex}`;
92
- const placeholderKeyName = `placeholder_key_${inputIndex}`;
93
- const signatureAlgorithmName = getSignatureAlgorithmName(template.getSignatureAlgorithm());
94
- const hashtypeName = getHashTypeName(template.getHashType(false));
95
- const signatureString = `${placeholderKeyName}.${signatureAlgorithmName}.${hashtypeName}`;
96
- // add extra unlocking and locking script for P2PKH inputs spent alongside our contract
97
- // this is needed for correct cross-references in the template
98
- scripts[unlockScriptName] = {
99
- name: `P2PKH Unlock (input #${inputIndex})`,
100
- script: `<${signatureString}>\n<${placeholderKeyName}.public_key>`,
101
- unlocks: lockScriptName,
102
- };
103
- scripts[lockScriptName] = {
104
- lockingType: 'standard',
105
- name: `P2PKH Lock (input #${inputIndex})`,
106
- script: `OP_DUP\nOP_HASH160 <$(<${placeholderKeyName}.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG`,
107
- };
108
- return scripts;
109
- };
110
- /**
111
- * Generates template scripts for P2SH (Pay to Script Hash) placeholder scripts.
112
- *
113
- * Follows the WalletTemplateScript specification from:
114
- * https://ide.bitauth.com/authentication-template-v0.schema.json
115
- *
116
- */
117
- export const generateTemplateScriptsP2SH = (contract, abiFunction, encodedFunctionArgs, encodedConstructorArgs, inputIndex) => {
118
- // definition of locking scripts and unlocking scripts with their respective bytecode
119
- const unlockingScriptName = getUnlockScriptName(contract, abiFunction, inputIndex);
120
- const lockingScriptName = getLockScriptName(contract);
121
- return {
122
- [unlockingScriptName]: generateTemplateUnlockScript(contract, abiFunction, encodedFunctionArgs, inputIndex),
123
- [lockingScriptName]: generateTemplateLockScript(contract, encodedConstructorArgs),
124
- };
125
- };
126
- /**
127
- * Generates a template lock script for a P2SH (Pay to Script Hash) placeholder script.
128
- *
129
- * Follows the WalletTemplateScriptLocking specification from:
130
- * https://ide.bitauth.com/authentication-template-v0.schema.json
131
- *
132
- */
133
- const generateTemplateLockScript = (contract, constructorArguments) => {
134
- return {
135
- lockingType: contract.addressType,
136
- name: contract.artifact.contractName,
137
- script: [
138
- `// "${contract.artifact.contractName}" contract constructor parameters`,
139
- formatParametersForDebugging(contract.artifact.constructorInputs, constructorArguments),
140
- '',
141
- '// bytecode',
142
- formatBytecodeForDebugging(contract.artifact),
143
- ].join('\n'),
144
- };
145
- };
146
- /**
147
- * Generates a template unlock script for a P2SH (Pay to Script Hash) placeholder script.
148
- *
149
- * Follows the WalletTemplateScriptUnlocking specification from:
150
- * https://ide.bitauth.com/authentication-template-v0.schema.json
151
- *
152
- */
153
- const generateTemplateUnlockScript = (contract, abiFunction, encodedFunctionArgs, inputIndex) => {
154
- const scenarioIdentifier = `${contract.artifact.contractName}_${abiFunction.name}_input${inputIndex}_evaluate`;
155
- const functionIndex = contract.artifact.abi.findIndex((func) => func.name === abiFunction.name);
156
- const functionIndexString = contract.artifact.abi.length > 1
157
- ? ['// function index in contract', `<function_index> // int = <${functionIndex}>`, '']
158
- : [];
159
- return {
160
- // this unlocking script must pass our only scenario
161
- passes: [scenarioIdentifier],
162
- name: `${abiFunction.name} (input #${inputIndex})`,
163
- script: [
164
- `// "${abiFunction.name}" function parameters`,
165
- formatParametersForDebugging(abiFunction.inputs, encodedFunctionArgs),
166
- '',
167
- ...functionIndexString,
168
- ].join('\n'),
169
- unlocks: getLockScriptName(contract),
170
- };
171
- };
172
- export const generateTemplateScenarios = (contract, libauthTransaction, csTransaction, abiFunction, encodedFunctionArgs, inputIndex) => {
173
- const artifact = contract.artifact;
174
- const encodedConstructorArgs = contract.encodedConstructorArgs;
175
- const scenarioIdentifier = `${artifact.contractName}_${abiFunction.name}_input${inputIndex}_evaluate`;
176
- const scenarios = {
177
- // single scenario to spend out transaction under test given the CashScript parameters provided
178
- [scenarioIdentifier]: {
179
- name: `Evaluate ${artifact.contractName} ${abiFunction.name} (input #${inputIndex})`,
180
- description: 'An example evaluation where this script execution passes.',
181
- data: {
182
- // encode values for the variables defined above in `entities` property
183
- bytecode: {
184
- ...generateTemplateScenarioParametersValues(abiFunction.inputs, encodedFunctionArgs),
185
- ...generateTemplateScenarioParametersValues(artifact.constructorInputs, encodedConstructorArgs),
186
- },
187
- currentBlockHeight: 2,
188
- currentBlockTime: Math.round(+new Date() / 1000),
189
- keys: {
190
- privateKeys: generateTemplateScenarioKeys(abiFunction.inputs, encodedFunctionArgs),
191
- },
192
- },
193
- transaction: generateTemplateScenarioTransaction(contract, libauthTransaction, csTransaction, inputIndex),
194
- sourceOutputs: generateTemplateScenarioSourceOutputs(csTransaction, inputIndex),
195
- },
196
- };
197
- if (artifact.abi.length > 1) {
198
- const functionIndex = artifact.abi.findIndex((func) => func.name === abiFunction.name);
199
- scenarios[scenarioIdentifier].data.bytecode.function_index = functionIndex.toString();
200
- }
201
- return scenarios;
202
- };
203
- const generateTemplateScenarioTransaction = (contract, libauthTransaction, csTransaction, slotIndex) => {
204
- const inputs = libauthTransaction.inputs.map((input, inputIndex) => {
205
- const csInput = csTransaction.inputs[inputIndex];
206
- return {
207
- outpointIndex: input.outpointIndex,
208
- outpointTransactionHash: binToHex(input.outpointTransactionHash),
209
- sequenceNumber: input.sequenceNumber,
210
- unlockingBytecode: generateTemplateScenarioBytecode(csInput, inputIndex, 'p2pkh_placeholder_unlock', slotIndex === inputIndex),
211
- };
212
- });
213
- const locktime = libauthTransaction.locktime;
214
- const outputs = libauthTransaction.outputs.map((output, index) => {
215
- const csOutput = csTransaction.outputs[index];
216
- return {
217
- lockingBytecode: generateTemplateScenarioTransactionOutputLockingBytecode(csOutput, contract),
218
- token: serialiseTokenDetails(output.token),
219
- valueSatoshis: Number(output.valueSatoshis),
220
- };
221
- });
222
- const version = libauthTransaction.version;
223
- return { inputs, locktime, outputs, version };
224
- };
225
- const generateTemplateScenarioSourceOutputs = (csTransaction, slotIndex) => {
226
- return csTransaction.inputs.map((input, inputIndex) => {
227
- return {
228
- lockingBytecode: generateTemplateScenarioBytecode(input, inputIndex, 'p2pkh_placeholder_lock', inputIndex === slotIndex),
229
- valueSatoshis: Number(input.satoshis),
230
- token: serialiseTokenDetails(input.token),
231
- };
232
- });
233
- };
234
- /**
235
- * Creates a transaction object from a TransactionBuilder instance
236
- *
237
- * @param txn - The TransactionBuilder instance to convert
238
- * @returns A transaction object containing inputs, outputs, locktime and version
239
- */
240
- // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
241
- const createCSTransaction = (txn) => {
242
- const csTransaction = {
243
- inputs: txn.inputs,
244
- locktime: txn.locktime,
245
- outputs: txn.outputs,
246
- version: 2,
247
- };
248
- return csTransaction;
249
- };
250
- export const getLibauthTemplates = (txn) => {
251
- if (txn.inputs.some((input) => !isStandardUnlockableUtxo(input))) {
252
- throw new Error('Cannot use debugging functionality with a transaction that contains custom unlockers');
253
- }
254
- const libauthTransaction = txn.buildLibauthTransaction();
255
- const csTransaction = createCSTransaction(txn);
256
- const baseTemplate = {
257
- $schema: 'https://ide.bitauth.com/authentication-template-v0.schema.json',
258
- description: 'Imported from cashscript',
259
- name: 'CashScript Generated Debugging Template',
260
- supported: ['BCH_2025_05'],
261
- version: 0,
262
- entities: {},
263
- scripts: {},
264
- scenarios: {},
265
- };
266
- // Initialize collections for entities, scripts, and scenarios
267
- const entities = {};
268
- const scripts = {};
269
- const scenarios = {};
270
- // Initialize collections for P2PKH entities and scripts
271
- const p2pkhEntities = {};
272
- const p2pkhScripts = {};
273
- // Initialize bytecode mappings, these will be used to map the locking and unlocking scripts and naming the scripts
274
- const unlockingBytecodeToLockingBytecodeParams = {};
275
- const lockingBytecodeToLockingBytecodeParams = {};
276
- // We can typecast this because we check that all inputs are standard unlockable at the top of this function
277
- for (const [inputIndex, input] of txn.inputs.entries()) {
278
- // If template exists on the input, it indicates this is a P2PKH (Pay to Public Key Hash) input
279
- if ('template' in input.unlocker) {
280
- // @ts-ignore TODO: Remove UtxoP2PKH type and only use UnlockableUtxo in Libauth Template generation
281
- input.template = input.unlocker?.template; // Added to support P2PKH inputs in buildTemplate
282
- Object.assign(p2pkhEntities, generateTemplateEntitiesP2PKH(inputIndex));
283
- Object.assign(p2pkhScripts, generateTemplateScriptsP2PKH(input.unlocker.template, inputIndex));
284
- continue;
285
- }
286
- // If contract exists on the input, it indicates this is a contract input
287
- if ('contract' in input.unlocker) {
288
- const contract = input.unlocker?.contract;
289
- const abiFunction = input.unlocker?.abiFunction;
290
- if (!abiFunction) {
291
- throw new Error('No ABI function found in unlocker');
292
- }
293
- // Encode the function arguments for this contract input
294
- const encodedArgs = encodeFunctionArguments(abiFunction, input.unlocker.params ?? []);
295
- // Generate a scenario object for this contract input
296
- Object.assign(scenarios, generateTemplateScenarios(contract, libauthTransaction, csTransaction, abiFunction, encodedArgs, inputIndex));
297
- // Generate entities for this contract input
298
- const entity = generateTemplateEntitiesP2SH(contract, abiFunction, encodedArgs, inputIndex);
299
- // Generate scripts for this contract input
300
- const script = generateTemplateScriptsP2SH(contract, abiFunction, encodedArgs, contract.encodedConstructorArgs, inputIndex);
301
- // Find the lock script name for this contract input
302
- const lockScriptName = Object.keys(script).find(scriptName => scriptName.includes('_lock'));
303
- if (lockScriptName) {
304
- // Generate bytecodes for this contract input
305
- const unlockingBytecode = binToHex(libauthTransaction.inputs[inputIndex].unlockingBytecode);
306
- const lockingScriptParams = generateLockingScriptParams(input.unlocker.contract, input, lockScriptName);
307
- // Assign a name to the unlocking bytecode so later it can be used to replace the bytecode/slot in scenarios
308
- unlockingBytecodeToLockingBytecodeParams[unlockingBytecode] = lockingScriptParams;
309
- // Assign a name to the locking bytecode so later it can be used to replace with bytecode/slot in scenarios
310
- lockingBytecodeToLockingBytecodeParams[binToHex(addressToLockScript(contract.address))] = lockingScriptParams;
311
- }
312
- // Add entities and scripts to the base template and repeat the process for the next input
313
- Object.assign(entities, entity);
314
- Object.assign(scripts, script);
315
- }
316
- }
317
- Object.assign(entities, p2pkhEntities);
318
- Object.assign(scripts, p2pkhScripts);
319
- const finalTemplate = { ...baseTemplate, entities, scripts, scenarios };
320
- // Loop through all scenarios and map the locking and unlocking scripts to the scenarios
321
- // Replace the script tag with the identifiers we created earlier
322
- // For Inputs
323
- for (const scenario of Object.values(scenarios)) {
324
- for (const [idx, input] of libauthTransaction.inputs.entries()) {
325
- const unlockingBytecode = binToHex(input.unlockingBytecode);
326
- // If false then it stays lockingBytecode: {}
327
- if (unlockingBytecodeToLockingBytecodeParams[unlockingBytecode]) {
328
- // ['slot'] this identifies the source output in which the locking script under test will be placed
329
- if (Array.isArray(scenario?.sourceOutputs?.[idx]?.lockingBytecode))
330
- continue;
331
- // If true then assign a name to the locking bytecode script.
332
- if (scenario.sourceOutputs && scenario.sourceOutputs[idx]) {
333
- scenario.sourceOutputs[idx] = {
334
- ...scenario.sourceOutputs[idx],
335
- lockingBytecode: unlockingBytecodeToLockingBytecodeParams[unlockingBytecode],
336
- };
337
- }
338
- }
339
- }
340
- // For Outputs
341
- for (const [idx, output] of libauthTransaction.outputs.entries()) {
342
- const lockingBytecode = binToHex(output.lockingBytecode);
343
- // If false then it stays lockingBytecode: {}
344
- if (lockingBytecodeToLockingBytecodeParams[lockingBytecode]) {
345
- // ['slot'] this identifies the source output in which the locking script under test will be placed
346
- if (Array.isArray(scenario?.transaction?.outputs?.[idx]?.lockingBytecode))
347
- continue;
348
- // If true then assign a name to the locking bytecode script.
349
- if (scenario?.transaction && scenario?.transaction?.outputs && scenario?.transaction?.outputs[idx]) {
350
- scenario.transaction.outputs[idx] = {
351
- ...scenario.transaction.outputs[idx],
352
- lockingBytecode: lockingBytecodeToLockingBytecodeParams[lockingBytecode],
353
- };
354
- }
355
- }
356
- }
357
- }
358
- return finalTemplate;
359
- };
360
- export const debugLibauthTemplate = (template, transaction) => {
361
- const allArtifacts = transaction.inputs
362
- .map(input => 'contract' in input.unlocker ? input.unlocker.contract : undefined)
363
- .filter((contract) => Boolean(contract))
364
- .map(contract => contract.artifact);
365
- return debugTemplate(template, allArtifacts);
366
- };
367
- const generateLockingScriptParams = (contract, { unlocker }, lockScriptName) => {
368
- if (isP2PKHUnlocker(unlocker)) {
369
- return {
370
- script: lockScriptName,
371
- };
372
- }
373
- const constructorParamsEntries = contract.artifact.constructorInputs
374
- .map(({ name }, index) => [
375
- name,
376
- addHexPrefixExceptEmpty(binToHex(unlocker.contract.encodedConstructorArgs[index])),
377
- ]);
378
- const constructorParams = Object.fromEntries(constructorParamsEntries);
379
- return {
380
- script: lockScriptName,
381
- overrides: {
382
- bytecode: { ...constructorParams },
383
- },
384
- };
385
- };
386
- export const generateUnlockingScriptParams = (csInput, p2pkhScriptNameTemplate, inputIndex) => {
387
- if (isP2PKHUnlocker(csInput.unlocker)) {
388
- return {
389
- script: `${p2pkhScriptNameTemplate}_${inputIndex}`,
390
- overrides: {
391
- keys: {
392
- privateKeys: {
393
- [`placeholder_key_${inputIndex}`]: binToHex(csInput.unlocker.template.privateKey),
394
- },
395
- },
396
- },
397
- };
398
- }
399
- const abiFunction = csInput.unlocker.abiFunction;
400
- const contract = csInput.unlocker.contract;
401
- const encodedFunctionArgs = encodeFunctionArguments(abiFunction, csInput.unlocker.params);
402
- return {
403
- script: getUnlockScriptName(contract, abiFunction, inputIndex),
404
- overrides: {
405
- // encode values for the variables defined above in `entities` property
406
- bytecode: {
407
- ...generateTemplateScenarioParametersFunctionIndex(abiFunction, contract.artifact.abi),
408
- ...generateTemplateScenarioParametersValues(abiFunction.inputs, encodedFunctionArgs),
409
- ...generateTemplateScenarioParametersValues(contract.artifact.constructorInputs, contract.encodedConstructorArgs),
410
- },
411
- keys: {
412
- privateKeys: generateTemplateScenarioKeys(abiFunction.inputs, encodedFunctionArgs),
413
- },
414
- },
415
- };
416
- };
417
- const getLockScriptName = (contract) => {
418
- const result = decodeCashAddress(contract.address);
419
- if (typeof result === 'string')
420
- throw new Error(result);
421
- return `${contract.artifact.contractName}_${binToHex(result.payload)}_lock`;
422
- };
423
- const getUnlockScriptName = (contract, abiFunction, inputIndex) => {
424
- return `${contract.artifact.contractName}_${abiFunction.name}_input${inputIndex}_unlock`;
425
- };
426
- //# sourceMappingURL=LibauthTemplate.js.map