Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions examples/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "cashscript-examples",
"private": true,
"version": "0.8.2",
"version": "0.9.0",
"description": "Usage examples of the CashScript SDK",
"main": "p2pkh.js",
"type": "module",
Expand All @@ -11,8 +11,8 @@
"@bitauth/libauth": "^2.0.0-alpha.8",
"@types/node": "^12.7.8",
"bip39": "^3.0.4",
"cashc": "^0.8.2",
"cashscript": "^0.8.2",
"cashc": "^0.9.0",
"cashscript": "^0.9.0",
"typescript": "^4.9.5"
}
}
110 changes: 0 additions & 110 deletions examples/pat_bug.ts

This file was deleted.

4 changes: 2 additions & 2 deletions packages/cashc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cashc",
"version": "0.8.2",
"version": "0.9.0",
"description": "Compile Bitcoin Cash contracts to Bitcoin Cash Script or artifacts",
"keywords": [
"bitcoin",
Expand Down Expand Up @@ -50,7 +50,7 @@
},
"dependencies": {
"@bitauth/libauth": "^2.0.0-alpha.8",
"@cashscript/utils": "^0.8.2",
"@cashscript/utils": "^0.9.0",
"antlr4ts": "^0.5.0-alpha.4",
"commander": "^7.1.0",
"semver": "^7.3.4"
Expand Down
2 changes: 1 addition & 1 deletion packages/cashc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export * from './Errors.js';
export * as utils from '@cashscript/utils';
export { compileFile, compileString } from './compiler.js';

export const version = '0.8.2';
export const version = '0.9.0';
4 changes: 2 additions & 2 deletions packages/cashscript/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cashscript",
"version": "0.8.2",
"version": "0.9.0",
"description": "Easily write and interact with Bitcoin Cash contracts",
"keywords": [
"bitcoin cash",
Expand Down Expand Up @@ -44,7 +44,7 @@
},
"dependencies": {
"@bitauth/libauth": "^2.0.0-alpha.8",
"@cashscript/utils": "^0.8.2",
"@cashscript/utils": "^0.9.0",
"bip68": "^1.0.4",
"bitcoin-rpc-promise-retry": "^1.3.0",
"delay": "^5.0.0",
Expand Down
65 changes: 56 additions & 9 deletions packages/cashscript/src/Contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ import {
calculateBytesize,
countOpcodes,
generateRedeemScript,
hash256,
Script,
scriptToBytecode,
} from '@cashscript/utils';
import { Transaction } from './Transaction.js';
import { Argument, encodeArgument } from './Argument.js';
import { ContractOptions, Utxo } from './interfaces.js';
import {
Unlocker, ContractOptions, GenerateUnlockingBytecodeOptions, Utxo,
} from './interfaces.js';
import NetworkProvider from './network/NetworkProvider.js';
import { scriptToAddress } from './utils.js';
import {
addressToLockScript, createInputScript, createSighashPreimage, scriptToAddress,
} from './utils.js';
import SignatureTemplate from './SignatureTemplate.js';
import { ElectrumNetworkProvider } from './network/index.js';

Expand All @@ -25,9 +30,8 @@ export class Contract {
bytesize: number;
opcount: number;

functions: {
[name: string]: ContractFunction,
};
functions: Record<string, ContractFunction>;
unlock: Record<string, ContractUnlocker>;

private redeemScript: Script;
private provider: NetworkProvider;
Expand All @@ -38,10 +42,8 @@ export class Contract {
constructorArgs: Argument[],
private options?: ContractOptions,
) {
const defaultProvider = new ElectrumNetworkProvider();
const defaultAddressType = 'p2sh32';
this.provider = this.options?.provider ?? defaultProvider;
this.addressType = this.options?.addressType ?? defaultAddressType;
this.provider = this.options?.provider ?? new ElectrumNetworkProvider();
this.addressType = this.options?.addressType ?? 'p2sh32';

const expectedProperties = ['abi', 'bytecode', 'constructorInputs', 'contractName'];
if (!expectedProperties.every((property) => property in artifact)) {
Expand Down Expand Up @@ -79,6 +81,18 @@ export class Contract {
});
}

// Populate the functions object with the contract's functions
// (with a special case for single function, which has no "function selector")
this.unlock = {};
if (artifact.abi.length === 1) {
const f = artifact.abi[0];
this.unlock[f.name] = this.createUnlocker(f);
} else {
artifact.abi.forEach((f, i) => {
this.unlock[f.name] = this.createUnlocker(f, i);
});
}

this.name = artifact.contractName;
this.address = scriptToAddress(this.redeemScript, this.provider.network, this.addressType, false);
this.tokenAddress = scriptToAddress(this.redeemScript, this.provider.network, this.addressType, true);
Expand Down Expand Up @@ -116,6 +130,39 @@ export class Contract {
);
};
}

private createUnlocker(abiFunction: AbiFunction, selector?: number): ContractUnlocker {
return (...args: Argument[]) => {
const bytecode = scriptToBytecode(this.redeemScript);

const encodedArgs = args
.map((arg, i) => encodeArgument(arg, abiFunction.inputs[i].type));

const generateUnlockingBytecode = (
{ transaction, sourceOutputs, inputIndex }: GenerateUnlockingBytecodeOptions,
): Uint8Array => {
const completeArgs = encodedArgs.map((arg) => {
if (!(arg instanceof SignatureTemplate)) return arg;

const preimage = createSighashPreimage(transaction, sourceOutputs, inputIndex, bytecode, arg.getHashType());
const sighash = hash256(preimage);

return arg.generateSignature(sighash);
});

const unlockingBytecode = createInputScript(
this.redeemScript, completeArgs, selector,
);

return unlockingBytecode;
};

const generateLockingBytecode = (): Uint8Array => addressToLockScript(this.address);

return { generateUnlockingBytecode, generateLockingBytecode };
};
}
}

export type ContractFunction = (...args: Argument[]) => Transaction;
export type ContractUnlocker = (...args: Argument[]) => Unlocker;
4 changes: 2 additions & 2 deletions packages/cashscript/src/Errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export class TokensToNonTokenAddressError extends Error {
}

export class FailedTransactionError extends Error {
constructor(public reason: string, public meep: string) {
super(`Transaction failed with reason: ${reason}\n${meep}`);
constructor(public reason: string, public meep?: string) {
super(`Transaction failed with reason: ${reason}${meep ? `\n${meep}` : ''}`);
}
}

Expand Down
26 changes: 25 additions & 1 deletion packages/cashscript/src/SignatureTemplate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { decodePrivateKeyWif, secp256k1, SigningSerializationFlag } from '@bitauth/libauth';
import { HashType, SignatureAlgorithm } from './interfaces.js';
import { hash256, scriptToBytecode } from '@cashscript/utils';
import {
Unlocker,
GenerateUnlockingBytecodeOptions,
HashType,
SignatureAlgorithm,
} from './interfaces.js';
import { createSighashPreimage, publicKeyToP2PKHLockingBytecode } from './utils.js';

export default class SignatureTemplate {
private privateKey: Uint8Array;
Expand Down Expand Up @@ -34,6 +41,23 @@ export default class SignatureTemplate {
getPublicKey(): Uint8Array {
return secp256k1.derivePublicKeyCompressed(this.privateKey) as Uint8Array;
}

unlockP2PKH(): Unlocker {
const publicKey = this.getPublicKey();
const prevOutScript = publicKeyToP2PKHLockingBytecode(publicKey);
const hashtype = this.getHashType();

return {
generateLockingBytecode: () => prevOutScript,
generateUnlockingBytecode: ({ transaction, sourceOutputs, inputIndex }: GenerateUnlockingBytecodeOptions) => {
const preimage = createSighashPreimage(transaction, sourceOutputs, inputIndex, prevOutScript, hashtype);
const sighash = hash256(preimage);
const signature = this.generateSignature(sighash);
const unlockingBytecode = scriptToBytecode([signature, publicKey]);
return unlockingBytecode;
},
};
}
}

// Works for both BITBOX/bitcoincash.js ECPair and bitcore-lib-cash PrivateKey
Expand Down
8 changes: 4 additions & 4 deletions packages/cashscript/src/Transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
Recipient,
TokenDetails,
NftObject,
isSignableUtxo,
isUtxoP2PKH,
TransactionDetails,
} from './interfaces.js';
import {
Expand Down Expand Up @@ -169,7 +169,7 @@ export class Transaction {
const sourceOutputs = this.inputs.map((input) => {
const sourceOutput = {
amount: input.satoshis,
to: isSignableUtxo(input) ? publicKeyToP2PKHLockingBytecode(input.template.getPublicKey()) : lockingBytecode,
to: isUtxoP2PKH(input) ? publicKeyToP2PKHLockingBytecode(input.template.getPublicKey()) : lockingBytecode,
token: input.token,
};

Expand All @@ -189,7 +189,7 @@ export class Transaction {

this.inputs.forEach((utxo, i) => {
// UTXO's with signature templates are signed using P2PKH
if (isSignableUtxo(utxo)) {
if (isUtxoP2PKH(utxo)) {
const pubkey = utxo.template.getPublicKey();
const prevOutScript = publicKeyToP2PKHLockingBytecode(pubkey);

Expand Down Expand Up @@ -413,7 +413,7 @@ export class Transaction {
// If inputs are already defined, the user provided the UTXOs and we perform no further UTXO selection
if (!this.hardcodedFee) {
const totalInputSize = this.inputs.reduce(
(acc, input) => acc + (isSignableUtxo(input) ? P2PKH_INPUT_SIZE : contractInputSize),
(acc, input) => acc + (isUtxoP2PKH(input) ? P2PKH_INPUT_SIZE : contractInputSize),
0,
);
fee += addPrecision(totalInputSize * this.feePerByte);
Expand Down
Loading