cashscript 0.9.1 → 0.10.0-next.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/dist/{Contract.d.ts → src/Contract.d.ts} +7 -5
- package/dist/{Contract.js → src/Contract.js} +12 -2
- package/dist/{Errors.d.ts → src/Errors.d.ts} +2 -2
- package/dist/{Errors.js → src/Errors.js} +3 -3
- package/dist/src/LibauthTemplate.d.ts +12 -0
- package/dist/src/LibauthTemplate.js +472 -0
- package/dist/{SignatureTemplate.d.ts → src/SignatureTemplate.d.ts} +1 -1
- package/dist/{Transaction.d.ts → src/Transaction.d.ts} +13 -12
- package/dist/{Transaction.js → src/Transaction.js} +76 -81
- package/dist/{TransactionBuilder.js → src/TransactionBuilder.js} +3 -3
- package/dist/{index.d.ts → src/index.d.ts} +3 -2
- package/dist/{index.js → src/index.js} +3 -2
- package/dist/{interfaces.d.ts → src/interfaces.d.ts} +1 -1
- package/dist/src/network/MockNetworkProvider.d.ts +14 -0
- package/dist/src/network/MockNetworkProvider.js +46 -0
- package/dist/{network → src/network}/index.d.ts +1 -0
- package/dist/{network → src/network}/index.js +1 -0
- package/dist/{utils.d.ts → src/utils.d.ts} +8 -5
- package/dist/{utils.js → src/utils.js} +61 -24
- package/dist/test/JestExtensions.d.ts +9 -0
- package/dist/test/JestExtensions.js +66 -0
- package/package.json +8 -6
- /package/dist/{Argument.d.ts → src/Argument.d.ts} +0 -0
- /package/dist/{Argument.js → src/Argument.js} +0 -0
- /package/dist/{SignatureTemplate.js → src/SignatureTemplate.js} +0 -0
- /package/dist/{TransactionBuilder.d.ts → src/TransactionBuilder.d.ts} +0 -0
- /package/dist/{constants.d.ts → src/constants.d.ts} +0 -0
- /package/dist/{constants.js → src/constants.js} +0 -0
- /package/dist/{interfaces.js → src/interfaces.js} +0 -0
- /package/dist/{network → src/network}/BitcoinRpcNetworkProvider.d.ts +0 -0
- /package/dist/{network → src/network}/BitcoinRpcNetworkProvider.js +0 -0
- /package/dist/{network → src/network}/ElectrumNetworkProvider.d.ts +0 -0
- /package/dist/{network → src/network}/ElectrumNetworkProvider.js +0 -0
- /package/dist/{network → src/network}/FullStackNetworkProvider.d.ts +0 -0
- /package/dist/{network → src/network}/FullStackNetworkProvider.js +0 -0
- /package/dist/{network → src/network}/NetworkProvider.d.ts +0 -0
- /package/dist/{network → src/network}/NetworkProvider.js +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Artifact } from '@cashscript/utils';
|
|
1
|
+
import { Artifact, Script } from '@cashscript/utils';
|
|
2
2
|
import { Transaction } from './Transaction.js';
|
|
3
3
|
import { Argument } from './Argument.js';
|
|
4
4
|
import { Unlocker, ContractOptions, Utxo } from './interfaces.js';
|
|
5
|
+
import NetworkProvider from './network/NetworkProvider.js';
|
|
5
6
|
export declare class Contract {
|
|
6
|
-
|
|
7
|
+
artifact: Artifact;
|
|
8
|
+
constructorArgs: Argument[];
|
|
7
9
|
private options?;
|
|
8
10
|
name: string;
|
|
9
11
|
address: string;
|
|
@@ -13,9 +15,9 @@ export declare class Contract {
|
|
|
13
15
|
opcount: number;
|
|
14
16
|
functions: Record<string, ContractFunction>;
|
|
15
17
|
unlock: Record<string, ContractUnlocker>;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
redeemScript: Script;
|
|
19
|
+
provider: NetworkProvider;
|
|
20
|
+
addressType: 'p2sh20' | 'p2sh32';
|
|
19
21
|
constructor(artifact: Artifact, constructorArgs: Argument[], options?: ContractOptions | undefined);
|
|
20
22
|
getBalance(): Promise<bigint>;
|
|
21
23
|
getUtxos(): Promise<Utxo[]>;
|
|
@@ -8,6 +8,7 @@ import { ElectrumNetworkProvider } from './network/index.js';
|
|
|
8
8
|
export class Contract {
|
|
9
9
|
constructor(artifact, constructorArgs, options) {
|
|
10
10
|
this.artifact = artifact;
|
|
11
|
+
this.constructorArgs = constructorArgs;
|
|
11
12
|
this.options = options;
|
|
12
13
|
this.provider = this.options?.provider ?? new ElectrumNetworkProvider();
|
|
13
14
|
this.addressType = this.options?.addressType ?? 'p2sh32';
|
|
@@ -73,7 +74,8 @@ export class Contract {
|
|
|
73
74
|
// Encode passed args (this also performs type checking)
|
|
74
75
|
const encodedArgs = args
|
|
75
76
|
.map((arg, i) => encodeArgument(arg, abiFunction.inputs[i].type));
|
|
76
|
-
|
|
77
|
+
const unlocker = this.createUnlocker(abiFunction, selector)(...args);
|
|
78
|
+
return new Transaction(this, unlocker, abiFunction, encodedArgs, selector);
|
|
77
79
|
};
|
|
78
80
|
}
|
|
79
81
|
createUnlocker(abiFunction, selector) {
|
|
@@ -82,14 +84,22 @@ export class Contract {
|
|
|
82
84
|
const encodedArgs = args
|
|
83
85
|
.map((arg, i) => encodeArgument(arg, abiFunction.inputs[i].type));
|
|
84
86
|
const generateUnlockingBytecode = ({ transaction, sourceOutputs, inputIndex }) => {
|
|
87
|
+
// TODO: Remove old-style covenant code for v1.0 release
|
|
88
|
+
let covenantHashType = -1;
|
|
85
89
|
const completeArgs = encodedArgs.map((arg) => {
|
|
86
90
|
if (!(arg instanceof SignatureTemplate))
|
|
87
91
|
return arg;
|
|
92
|
+
// First signature is used for sighash preimage (maybe not the best way)
|
|
93
|
+
if (covenantHashType < 0)
|
|
94
|
+
covenantHashType = arg.getHashType();
|
|
88
95
|
const preimage = createSighashPreimage(transaction, sourceOutputs, inputIndex, bytecode, arg.getHashType());
|
|
89
96
|
const sighash = hash256(preimage);
|
|
90
97
|
return arg.generateSignature(sighash);
|
|
91
98
|
});
|
|
92
|
-
const
|
|
99
|
+
const preimage = abiFunction.covenant
|
|
100
|
+
? createSighashPreimage(transaction, sourceOutputs, inputIndex, bytecode, covenantHashType)
|
|
101
|
+
: undefined;
|
|
102
|
+
const unlockingBytecode = createInputScript(this.redeemScript, completeArgs, selector, preimage);
|
|
93
103
|
return unlockingBytecode;
|
|
94
104
|
};
|
|
95
105
|
const generateLockingBytecode = () => addressToLockScript(this.address);
|
|
@@ -10,8 +10,8 @@ export declare class TokensToNonTokenAddressError extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
export declare class FailedTransactionError extends Error {
|
|
12
12
|
reason: string;
|
|
13
|
-
|
|
14
|
-
constructor(reason: string,
|
|
13
|
+
debugStr?: string | undefined;
|
|
14
|
+
constructor(reason: string, debugStr?: string | undefined);
|
|
15
15
|
}
|
|
16
16
|
export declare class FailedRequireError extends FailedTransactionError {
|
|
17
17
|
}
|
|
@@ -14,10 +14,10 @@ export class TokensToNonTokenAddressError extends Error {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
export class FailedTransactionError extends Error {
|
|
17
|
-
constructor(reason,
|
|
18
|
-
super(`Transaction failed with reason: ${reason}${
|
|
17
|
+
constructor(reason, debugStr) {
|
|
18
|
+
super(`Transaction failed with reason: ${reason}${debugStr ? `\n\n${debugStr}` : ''}`);
|
|
19
19
|
this.reason = reason;
|
|
20
|
-
this.
|
|
20
|
+
this.debugStr = debugStr;
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
export class FailedRequireError extends FailedTransactionError {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Artifact } from '@cashscript/utils';
|
|
2
|
+
import { AuthenticationTemplate, AuthenticationProgramStateBCHCHIPs } from '@bitauth/libauth';
|
|
3
|
+
import { Transaction } from './Transaction.js';
|
|
4
|
+
export declare const stringify: (any: any, spaces?: number | undefined) => string;
|
|
5
|
+
export declare const buildTemplate: ({ transaction, transactionHex, }: {
|
|
6
|
+
transaction: Transaction;
|
|
7
|
+
transactionHex?: string | undefined;
|
|
8
|
+
}) => Promise<AuthenticationTemplate>;
|
|
9
|
+
export declare const getBitauthUri: (template: AuthenticationTemplate) => string;
|
|
10
|
+
export declare const evaluateTemplate: (template: AuthenticationTemplate) => boolean;
|
|
11
|
+
export declare type DebugResult = AuthenticationProgramStateBCHCHIPs[];
|
|
12
|
+
export declare const debugTemplate: (template: AuthenticationTemplate, artifact: Artifact) => DebugResult;
|
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
import { Op, PrimitiveType, bytecodeToScript, decodeBool, decodeInt, decodeString, formatLibauthScript, } from '@cashscript/utils';
|
|
2
|
+
import { hash160, hexToBin, decodeTransaction, binToHex, authenticationTemplateToCompilerConfiguration, createCompiler, createVirtualMachineBCHCHIPs, binToBase64, utf8ToBin, isHex, AuthenticationErrorCommon, } from '@bitauth/libauth';
|
|
3
|
+
import { deflate } from 'pako';
|
|
4
|
+
import { isUtxoP2PKH, } from '../src/interfaces.js';
|
|
5
|
+
import { encodeArgument as csEncodeArgument } from './Argument.js';
|
|
6
|
+
import { toRegExp } from './utils.js';
|
|
7
|
+
// all bitauth variables must be in snake case
|
|
8
|
+
const snakeCase = (str) => (str
|
|
9
|
+
&& str
|
|
10
|
+
.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
|
|
11
|
+
.map((s) => s.toLowerCase())
|
|
12
|
+
.join('_'));
|
|
13
|
+
const merge = (array) => array.reduce((prev, cur) => ({
|
|
14
|
+
...prev,
|
|
15
|
+
...{ [Object.keys(cur)[0]]: cur[Object.keys(cur)[0]] },
|
|
16
|
+
}), {});
|
|
17
|
+
const encodeArgument = (argument, typeStr) => {
|
|
18
|
+
if (typeStr === PrimitiveType.INT && argument === 0n) {
|
|
19
|
+
return Uint8Array.from([0]);
|
|
20
|
+
}
|
|
21
|
+
return csEncodeArgument(argument, typeStr);
|
|
22
|
+
};
|
|
23
|
+
// stringify version which can serialize otherwise unsupported types
|
|
24
|
+
export const stringify = (any, spaces) => JSON.stringify(any, (_, v) => {
|
|
25
|
+
if (typeof v === 'bigint') {
|
|
26
|
+
return `${v.toString()}`;
|
|
27
|
+
}
|
|
28
|
+
if (v instanceof Uint8Array) {
|
|
29
|
+
return `${binToHex(v)}`;
|
|
30
|
+
}
|
|
31
|
+
return v;
|
|
32
|
+
}, spaces);
|
|
33
|
+
const zip = (a, b) => Array.from(Array(Math.max(b.length, a.length)), (_, i) => [a[i], b[i]]);
|
|
34
|
+
const createScenarioTransaction = (libauthTransaction, csTransaction) => {
|
|
35
|
+
const contract = csTransaction.contract;
|
|
36
|
+
const result = {};
|
|
37
|
+
// only one 'slot' is allowed, otherwise {} must be used
|
|
38
|
+
let inputSlotInserted = false;
|
|
39
|
+
result.inputs = libauthTransaction.inputs.map((input, index) => {
|
|
40
|
+
const csInput = csTransaction.inputs[index];
|
|
41
|
+
const signable = isUtxoP2PKH(csInput);
|
|
42
|
+
let unlockingBytecode = {};
|
|
43
|
+
if (signable) {
|
|
44
|
+
unlockingBytecode = {
|
|
45
|
+
script: 'p2pkh_placeholder_unlock',
|
|
46
|
+
overrides: {
|
|
47
|
+
keys: {
|
|
48
|
+
privateKeys: {
|
|
49
|
+
placeholder_key: binToHex(csInput.template.privateKey),
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
// assume it is our contract's input
|
|
57
|
+
// eslint-disable-next-line
|
|
58
|
+
if (!inputSlotInserted) {
|
|
59
|
+
unlockingBytecode = ['slot'];
|
|
60
|
+
inputSlotInserted = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
outpointIndex: input.outpointIndex,
|
|
65
|
+
outpointTransactionHash: input.outpointTransactionHash instanceof Uint8Array
|
|
66
|
+
? binToHex(input.outpointTransactionHash)
|
|
67
|
+
: input.outpointTransactionHash,
|
|
68
|
+
sequenceNumber: input.sequenceNumber,
|
|
69
|
+
unlockingBytecode,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
result.locktime = libauthTransaction.locktime;
|
|
73
|
+
result.outputs = libauthTransaction.outputs.map((output, index) => {
|
|
74
|
+
const csOutput = csTransaction.outputs[index];
|
|
75
|
+
let { lockingBytecode } = output;
|
|
76
|
+
if (typeof csOutput.to === 'string') {
|
|
77
|
+
if ([
|
|
78
|
+
contract.address,
|
|
79
|
+
contract.tokenAddress,
|
|
80
|
+
].includes(csOutput.to)) {
|
|
81
|
+
lockingBytecode = {};
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
for (const csInput of csTransaction.inputs) {
|
|
85
|
+
if (isUtxoP2PKH(csInput)) {
|
|
86
|
+
const inputPkh = hash160(csInput.template.getPublicKey());
|
|
87
|
+
if (binToHex(output.lockingBytecode).slice(6, 46)
|
|
88
|
+
=== binToHex(inputPkh)) {
|
|
89
|
+
lockingBytecode = {
|
|
90
|
+
script: 'p2pkh_placeholder_lock',
|
|
91
|
+
overrides: {
|
|
92
|
+
keys: {
|
|
93
|
+
privateKeys: {
|
|
94
|
+
placeholder_key: binToHex(csInput.template.privateKey),
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
lockingBytecode: lockingBytecode instanceof Uint8Array
|
|
106
|
+
? binToHex(lockingBytecode)
|
|
107
|
+
: lockingBytecode,
|
|
108
|
+
token: output.token,
|
|
109
|
+
valueSatoshis: Number(output.valueSatoshis),
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
result.version = libauthTransaction.version;
|
|
113
|
+
return result;
|
|
114
|
+
};
|
|
115
|
+
const createScenarioSourceOutputs = (csTransaction) => {
|
|
116
|
+
// only one 'slot' is allowed, otherwise {} must be used
|
|
117
|
+
let inputSlotInserted = false;
|
|
118
|
+
return csTransaction.inputs.map((csInput) => {
|
|
119
|
+
const signable = isUtxoP2PKH(csInput);
|
|
120
|
+
let lockingBytecode = {};
|
|
121
|
+
if (signable) {
|
|
122
|
+
lockingBytecode = {
|
|
123
|
+
script: 'p2pkh_placeholder_lock',
|
|
124
|
+
overrides: {
|
|
125
|
+
keys: {
|
|
126
|
+
privateKeys: {
|
|
127
|
+
placeholder_key: binToHex(csInput.template.privateKey),
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// assume it is our contract's input
|
|
135
|
+
// eslint-disable-next-line
|
|
136
|
+
if (!inputSlotInserted) {
|
|
137
|
+
lockingBytecode = ['slot'];
|
|
138
|
+
inputSlotInserted = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const result = {
|
|
142
|
+
lockingBytecode: lockingBytecode,
|
|
143
|
+
valueSatoshis: Number(csInput.satoshis),
|
|
144
|
+
};
|
|
145
|
+
if (csInput.token) {
|
|
146
|
+
result.token = {
|
|
147
|
+
amount: csInput.token.amount.toString(),
|
|
148
|
+
category: csInput.token.category,
|
|
149
|
+
};
|
|
150
|
+
if (csInput.token.nft) {
|
|
151
|
+
result.token.nft = {
|
|
152
|
+
capability: csInput.token.nft.capability,
|
|
153
|
+
commitment: csInput.token.nft.commitment
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
export const buildTemplate = async ({ transaction, transactionHex = undefined, // set this argument to prevent unnecessary call `transaction.build()`
|
|
161
|
+
}) => {
|
|
162
|
+
const contract = transaction.contract;
|
|
163
|
+
const txHex = transactionHex ?? await transaction.build();
|
|
164
|
+
const libauthTransaction = decodeTransaction(hexToBin(txHex));
|
|
165
|
+
if (typeof libauthTransaction === 'string') {
|
|
166
|
+
throw Error(libauthTransaction);
|
|
167
|
+
}
|
|
168
|
+
const constructorInputs = contract.artifact.constructorInputs
|
|
169
|
+
.slice()
|
|
170
|
+
.reverse();
|
|
171
|
+
const contractParameters = contract.constructorArgs.slice().reverse();
|
|
172
|
+
const abiFunction = transaction.abiFunction;
|
|
173
|
+
const funcName = abiFunction.name;
|
|
174
|
+
const functionIndex = contract.artifact.abi.findIndex((func) => func.name === funcName);
|
|
175
|
+
const func = contract.artifact.abi[functionIndex];
|
|
176
|
+
const functionInputs = func.inputs.slice().reverse();
|
|
177
|
+
const args = transaction.args.slice().reverse();
|
|
178
|
+
const hasSignatureTemplates = transaction.inputs.filter((input) => isUtxoP2PKH(input)).length;
|
|
179
|
+
const formattedBytecode = contract.artifact.debug
|
|
180
|
+
? formatLibauthScript(bytecodeToScript(hexToBin(contract.artifact.debug.bytecode)), contract.artifact.debug.sourceMap, contract.artifact.source).split('\n')
|
|
181
|
+
: contract.artifact.bytecode.split(' ').map((asmElement) => {
|
|
182
|
+
if (isHex(asmElement)) {
|
|
183
|
+
return `<0x${asmElement}>`;
|
|
184
|
+
}
|
|
185
|
+
return asmElement;
|
|
186
|
+
});
|
|
187
|
+
const template = {
|
|
188
|
+
$schema: 'https://ide.bitauth.com/authentication-template-v0.schema.json',
|
|
189
|
+
description: `Imported from cashscript`,
|
|
190
|
+
name: contract.artifact.contractName,
|
|
191
|
+
supported: ['BCH_SPEC'],
|
|
192
|
+
version: 0,
|
|
193
|
+
};
|
|
194
|
+
// declaration of template variables and their types
|
|
195
|
+
template.entities = {
|
|
196
|
+
parameters: {
|
|
197
|
+
description: 'Contract creation and function parameters',
|
|
198
|
+
name: 'parameters',
|
|
199
|
+
scripts: [
|
|
200
|
+
'lock',
|
|
201
|
+
'unlock_lock',
|
|
202
|
+
],
|
|
203
|
+
variables: merge([
|
|
204
|
+
...functionInputs.map((input) => ({
|
|
205
|
+
[snakeCase(input.name)]: {
|
|
206
|
+
description: `"${input.name}" parameter of function "${func.name}"`,
|
|
207
|
+
name: input.name,
|
|
208
|
+
type: input.type === PrimitiveType.SIG ? 'Key' : 'WalletData',
|
|
209
|
+
},
|
|
210
|
+
})),
|
|
211
|
+
{
|
|
212
|
+
function_index: {
|
|
213
|
+
description: 'Script function index to execute',
|
|
214
|
+
name: 'function_index',
|
|
215
|
+
type: 'WalletData',
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
...constructorInputs.map((input) => ({
|
|
219
|
+
[snakeCase(input.name)]: {
|
|
220
|
+
description: `"${input.name}" parameter of this contract`,
|
|
221
|
+
name: input.name,
|
|
222
|
+
type: 'WalletData',
|
|
223
|
+
},
|
|
224
|
+
})),
|
|
225
|
+
]),
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
// add extra variables for the p2pkh utxos spent together with our contract
|
|
229
|
+
if (hasSignatureTemplates) {
|
|
230
|
+
template.entities.parameters.scripts.push('p2pkh_placeholder_lock', 'p2pkh_placeholder_unlock');
|
|
231
|
+
template.entities.parameters.variables = {
|
|
232
|
+
...template.entities.parameters.variables,
|
|
233
|
+
placeholder_key: {
|
|
234
|
+
description: 'placeholder_key',
|
|
235
|
+
name: 'placeholder_key',
|
|
236
|
+
type: 'Key',
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
template.scenarios = {
|
|
241
|
+
// single scenario to spend out transaction under test given the CashScript parameters provided
|
|
242
|
+
evaluate_function: {
|
|
243
|
+
name: 'Evaluate',
|
|
244
|
+
description: 'An example evaluation where this script execution passes.',
|
|
245
|
+
data: {
|
|
246
|
+
// encode values for the variables defined above in `entities` property
|
|
247
|
+
bytecode: merge([
|
|
248
|
+
...zip(functionInputs, args)
|
|
249
|
+
.filter(([input]) => input.type !== PrimitiveType.SIG)
|
|
250
|
+
.map(([input, arg]) => {
|
|
251
|
+
const hex = binToHex(arg);
|
|
252
|
+
const result = hex.length ? `0x${hex}` : hex;
|
|
253
|
+
return {
|
|
254
|
+
[snakeCase(input.name)]: result,
|
|
255
|
+
};
|
|
256
|
+
}),
|
|
257
|
+
{ function_index: functionIndex.toString() },
|
|
258
|
+
...constructorInputs.map((input, index) => {
|
|
259
|
+
const hex = binToHex(encodeArgument(contractParameters[index], constructorInputs[index].type));
|
|
260
|
+
const result = hex.length ? `0x${hex}` : hex;
|
|
261
|
+
return {
|
|
262
|
+
[snakeCase(input.name)]: result,
|
|
263
|
+
};
|
|
264
|
+
}),
|
|
265
|
+
]),
|
|
266
|
+
currentBlockHeight: 2,
|
|
267
|
+
currentBlockTime: Math.round(+new Date() / 1000),
|
|
268
|
+
keys: {
|
|
269
|
+
privateKeys: merge([
|
|
270
|
+
...zip(functionInputs, args)
|
|
271
|
+
.filter(([input]) => input.type === PrimitiveType.SIG)
|
|
272
|
+
.map(([input, arg]) => ({
|
|
273
|
+
[snakeCase(input.name)]: binToHex(arg.privateKey),
|
|
274
|
+
})),
|
|
275
|
+
...(hasSignatureTemplates
|
|
276
|
+
? [
|
|
277
|
+
{
|
|
278
|
+
// placeholder will be replaced by a key for each respective P2PKH input spent
|
|
279
|
+
placeholder_key: '<Uint8Array: 0x0000000000000000000000000000000000000000000000000000000000000000>',
|
|
280
|
+
},
|
|
281
|
+
]
|
|
282
|
+
: []),
|
|
283
|
+
]),
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
transaction: createScenarioTransaction(libauthTransaction, transaction),
|
|
287
|
+
sourceOutputs: createScenarioSourceOutputs(transaction),
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
// definition of locking scripts and unlocking scripts with their respective bytecode
|
|
291
|
+
template.scripts = {
|
|
292
|
+
unlock_lock: {
|
|
293
|
+
// this unlocking script must pass our only scenario
|
|
294
|
+
passes: ['evaluate_function'],
|
|
295
|
+
name: 'unlock',
|
|
296
|
+
// unlocking script contains the CashScript function parameters and function selector
|
|
297
|
+
// we output these values as pushdata, comment will contain the type and the value of the variable
|
|
298
|
+
// example: '<timeout> // int = <0xa08601>'
|
|
299
|
+
script: [
|
|
300
|
+
`// "${func.name}" function parameters`,
|
|
301
|
+
...(functionInputs.length
|
|
302
|
+
? zip(functionInputs, args).map(([input, arg]) => (input.type === PrimitiveType.SIG
|
|
303
|
+
? `<${snakeCase(input.name)}.schnorr_signature.all_outputs> // ${input.type}`
|
|
304
|
+
: `<${snakeCase(input.name)}> // ${input.type} = <${`0x${binToHex(arg)}`}>`))
|
|
305
|
+
: ['// none']),
|
|
306
|
+
'',
|
|
307
|
+
...(contract.artifact.abi.length > 1
|
|
308
|
+
? [
|
|
309
|
+
'// function index in contract',
|
|
310
|
+
`<function_index> // int = <${functionIndex.toString()}>`,
|
|
311
|
+
'',
|
|
312
|
+
]
|
|
313
|
+
: []),
|
|
314
|
+
].join('\n'),
|
|
315
|
+
unlocks: 'lock',
|
|
316
|
+
},
|
|
317
|
+
lock: {
|
|
318
|
+
lockingType: "p2sh20",
|
|
319
|
+
name: 'lock',
|
|
320
|
+
// locking script contains the CashScript contract parameters followed by the contract opcodes
|
|
321
|
+
// we output these values as pushdata, comment will contain the type and the value of the variable
|
|
322
|
+
// example: '<timeout> // int = <0xa08601>'
|
|
323
|
+
script: [
|
|
324
|
+
`// "${contract.artifact.contractName}" contract constructor parameters`,
|
|
325
|
+
...(constructorInputs.length
|
|
326
|
+
? constructorInputs.map((input, index) => {
|
|
327
|
+
const encoded = encodeArgument(contractParameters[index], constructorInputs[index].type);
|
|
328
|
+
return `<${snakeCase(input.name)}> // ${input.type === 'bytes' ? `bytes${encoded.length}` : input.type} = <${`0x${binToHex(encoded)}`}>`;
|
|
329
|
+
})
|
|
330
|
+
: ['// none']),
|
|
331
|
+
'',
|
|
332
|
+
'// bytecode',
|
|
333
|
+
...formattedBytecode,
|
|
334
|
+
].join('\n'),
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
// add extra unlocking and locking script for P2PKH inputs spent alongside our contract
|
|
338
|
+
// this is needed for correct cross-referrences in the template
|
|
339
|
+
if (hasSignatureTemplates) {
|
|
340
|
+
template.scripts.p2pkh_placeholder_unlock = {
|
|
341
|
+
name: 'p2pkh_placeholder_unlock',
|
|
342
|
+
script: '<placeholder_key.schnorr_signature.all_outputs>\n<placeholder_key.public_key>',
|
|
343
|
+
unlocks: 'p2pkh_placeholder_lock',
|
|
344
|
+
};
|
|
345
|
+
template.scripts.p2pkh_placeholder_lock = {
|
|
346
|
+
lockingType: 'standard',
|
|
347
|
+
name: 'p2pkh_placeholder_lock',
|
|
348
|
+
script: 'OP_DUP\nOP_HASH160 <$(<placeholder_key.public_key> OP_HASH160\n)> OP_EQUALVERIFY\nOP_CHECKSIG',
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
return template;
|
|
352
|
+
};
|
|
353
|
+
export const getBitauthUri = (template) => {
|
|
354
|
+
const base64toBase64Url = (base64) => base64.replace(/\+/g, '-').replace(/\//g, '_');
|
|
355
|
+
const payload = base64toBase64Url(binToBase64(deflate(utf8ToBin(stringify(template)))));
|
|
356
|
+
return `https://ide.bitauth.com/import-template/${payload}`;
|
|
357
|
+
};
|
|
358
|
+
// internal util. instantiates the virtual machine and compiles the template into a program
|
|
359
|
+
const createProgram = (template) => {
|
|
360
|
+
const configuration = authenticationTemplateToCompilerConfiguration(template);
|
|
361
|
+
const vm = createVirtualMachineBCHCHIPs();
|
|
362
|
+
const compiler = createCompiler(configuration);
|
|
363
|
+
const scenarioGeneration = compiler.generateScenario({
|
|
364
|
+
debug: true,
|
|
365
|
+
lockingScriptId: undefined,
|
|
366
|
+
unlockingScriptId: 'unlock_lock',
|
|
367
|
+
scenarioId: 'evaluate_function',
|
|
368
|
+
});
|
|
369
|
+
if (typeof scenarioGeneration === 'string'
|
|
370
|
+
|| typeof scenarioGeneration.scenario === 'string') {
|
|
371
|
+
// eslint-disable-next-line
|
|
372
|
+
throw scenarioGeneration;
|
|
373
|
+
}
|
|
374
|
+
return { vm, program: scenarioGeneration.scenario.program };
|
|
375
|
+
};
|
|
376
|
+
// evaluates the fully defined template, throws upon error
|
|
377
|
+
export const evaluateTemplate = (template) => {
|
|
378
|
+
const { vm, program } = createProgram(template);
|
|
379
|
+
const verifyResult = vm.verify(program);
|
|
380
|
+
if (typeof verifyResult === 'string') {
|
|
381
|
+
// eslint-disable-next-line
|
|
382
|
+
throw verifyResult;
|
|
383
|
+
}
|
|
384
|
+
return verifyResult;
|
|
385
|
+
};
|
|
386
|
+
// debugs the template, optionally logging the execution data
|
|
387
|
+
export const debugTemplate = (template, artifact) => {
|
|
388
|
+
const { vm, program } = createProgram(template);
|
|
389
|
+
const debugResult = vm.debug(program);
|
|
390
|
+
for (const log of artifact.debug?.logs ?? []) {
|
|
391
|
+
// there might be 2 elements with same instruction pointer, first from unllocking script, second from locking
|
|
392
|
+
const state = debugResult
|
|
393
|
+
.filter((debugState) => debugState.ip === log.ip)
|
|
394
|
+
.slice().reverse()[0];
|
|
395
|
+
if (!state) {
|
|
396
|
+
throw Error(`Instruction pointer ${log.ip} points to a non-existing state of the program`);
|
|
397
|
+
}
|
|
398
|
+
let line = `${artifact.contractName}.cash:${log.line}`;
|
|
399
|
+
log.data.forEach((element) => {
|
|
400
|
+
let value;
|
|
401
|
+
if (typeof element === 'string') {
|
|
402
|
+
value = element;
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
const stackItem = state.stack.slice().reverse()[element.stackIndex];
|
|
406
|
+
if (!stackItem) {
|
|
407
|
+
throw Error(`Stack item at index ${element.stackIndex} not found at instruction pointer ${log.ip}`);
|
|
408
|
+
}
|
|
409
|
+
switch (element.type) {
|
|
410
|
+
case PrimitiveType.BOOL:
|
|
411
|
+
value = decodeBool(stackItem);
|
|
412
|
+
break;
|
|
413
|
+
case PrimitiveType.INT:
|
|
414
|
+
value = decodeInt(stackItem);
|
|
415
|
+
break;
|
|
416
|
+
case PrimitiveType.STRING:
|
|
417
|
+
value = decodeString(stackItem);
|
|
418
|
+
break;
|
|
419
|
+
default:
|
|
420
|
+
value = `0x${binToHex(stackItem)}`;
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
line += ` ${value}`;
|
|
425
|
+
});
|
|
426
|
+
// actual log, do not delete :)
|
|
427
|
+
console.log(line);
|
|
428
|
+
}
|
|
429
|
+
const lastState = debugResult[debugResult.length - 1];
|
|
430
|
+
if (lastState.error) {
|
|
431
|
+
const requireMessage = (artifact.debug?.requireMessages ?? []).filter((message) => message.ip === lastState.ip)[0];
|
|
432
|
+
if (requireMessage) {
|
|
433
|
+
// eslint-disable-next-line
|
|
434
|
+
throw `${artifact.contractName}.cash:${requireMessage.line} Error in evaluating input index ${lastState.program.inputIndex} with the following message: ${requireMessage.message}.
|
|
435
|
+
${lastState.error}`;
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
// eslint-disable-next-line
|
|
439
|
+
throw `Error in evaluating input index ${lastState.program.inputIndex}.
|
|
440
|
+
${lastState.error}`;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
// one last pass of verifications not covered by the above debugging
|
|
445
|
+
// checks for removed final verify
|
|
446
|
+
const evaluationResult = vm.verify(program);
|
|
447
|
+
if (typeof evaluationResult === 'string' && toRegExp([
|
|
448
|
+
AuthenticationErrorCommon.requiresCleanStack,
|
|
449
|
+
AuthenticationErrorCommon.nonEmptyControlStack,
|
|
450
|
+
AuthenticationErrorCommon.unsuccessfulEvaluation,
|
|
451
|
+
]).test(evaluationResult)) {
|
|
452
|
+
const stackContents = lastState.stack.map(item => `0x${binToHex(item)}`).join(', ');
|
|
453
|
+
const stackContentsMessage = `\nStack contents after evaluation: ${lastState.stack.length ? stackContents : 'empty'}`;
|
|
454
|
+
const lastMessage = artifact.debug?.requireMessages.sort((a, b) => b.ip - a.ip)[0];
|
|
455
|
+
if (!lastMessage) {
|
|
456
|
+
// eslint-disable-next-line
|
|
457
|
+
throw evaluationResult + stackContentsMessage;
|
|
458
|
+
}
|
|
459
|
+
const instructionsLeft = lastState.instructions.slice(lastMessage.ip);
|
|
460
|
+
if (instructionsLeft.length === 0
|
|
461
|
+
|| instructionsLeft.every(instruction => [Op.OP_NIP, Op.OP_ENDIF].includes(instruction.opcode))) {
|
|
462
|
+
// eslint-disable-next-line
|
|
463
|
+
throw `${artifact.contractName}.cash:${lastMessage.line} Error in evaluating input index ${lastState.program.inputIndex} with the following message: ${lastMessage.message}.
|
|
464
|
+
${evaluationResult.replace(/Error in evaluating input index \d: /, '')}` + stackContentsMessage;
|
|
465
|
+
}
|
|
466
|
+
// eslint-disable-next-line
|
|
467
|
+
throw evaluationResult + stackContentsMessage;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return debugResult;
|
|
471
|
+
};
|
|
472
|
+
//# sourceMappingURL=LibauthTemplate.js.map
|
|
@@ -2,7 +2,7 @@ import { Unlocker, HashType, SignatureAlgorithm } from './interfaces.js';
|
|
|
2
2
|
export default class SignatureTemplate {
|
|
3
3
|
private hashtype;
|
|
4
4
|
private signatureAlgorithm;
|
|
5
|
-
|
|
5
|
+
privateKey: Uint8Array;
|
|
6
6
|
constructor(signer: Keypair | Uint8Array | string, hashtype?: HashType, signatureAlgorithm?: SignatureAlgorithm);
|
|
7
7
|
generateSignature(payload: Uint8Array, bchForkId?: boolean): Uint8Array;
|
|
8
8
|
getHashType(bchForkId?: boolean): number;
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { AbiFunction
|
|
2
|
-
import { Utxo, Recipient, TokenDetails, TransactionDetails } from './interfaces.js';
|
|
3
|
-
import NetworkProvider from './network/NetworkProvider.js';
|
|
1
|
+
import { AbiFunction } from '@cashscript/utils';
|
|
2
|
+
import { Utxo, Output, Recipient, TokenDetails, TransactionDetails, Unlocker } from './interfaces.js';
|
|
4
3
|
import SignatureTemplate from './SignatureTemplate.js';
|
|
4
|
+
import { Contract } from './Contract.js';
|
|
5
|
+
import { DebugResult } from './LibauthTemplate.js';
|
|
5
6
|
export declare class Transaction {
|
|
6
|
-
|
|
7
|
-
private
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
private args;
|
|
7
|
+
contract: Contract;
|
|
8
|
+
private unlocker;
|
|
9
|
+
abiFunction: AbiFunction;
|
|
10
|
+
args: (Uint8Array | SignatureTemplate)[];
|
|
11
11
|
private selector?;
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
inputs: Utxo[];
|
|
13
|
+
outputs: Output[];
|
|
14
14
|
private sequence;
|
|
15
15
|
private locktime;
|
|
16
16
|
private feePerByte;
|
|
17
17
|
private hardcodedFee;
|
|
18
18
|
private minChange;
|
|
19
19
|
private tokenChange;
|
|
20
|
-
constructor(
|
|
20
|
+
constructor(contract: Contract, unlocker: Unlocker, abiFunction: AbiFunction, args: (Uint8Array | SignatureTemplate)[], selector?: number | undefined);
|
|
21
21
|
from(input: Utxo): this;
|
|
22
22
|
from(inputs: Utxo[]): this;
|
|
23
23
|
fromP2PKH(input: Utxo, template: SignatureTemplate): this;
|
|
@@ -35,7 +35,8 @@ export declare class Transaction {
|
|
|
35
35
|
build(): Promise<string>;
|
|
36
36
|
send(): Promise<TransactionDetails>;
|
|
37
37
|
send(raw: true): Promise<string>;
|
|
38
|
+
debug(): Promise<DebugResult>;
|
|
39
|
+
bitauthUri(): Promise<string>;
|
|
38
40
|
private getTxDetails;
|
|
39
|
-
meep(): Promise<string>;
|
|
40
41
|
private setInputsAndOutputs;
|
|
41
42
|
}
|