Skip to content

Commit 0e98b11

Browse files
committed
[cashc] Remove bitbox/trout-bch in favour of libauth
- Add functions to convert between Script/ASM/bytecode using libauth - Keep the interface the same as it was before [cashc] Bump version to 0.5.0 [cashscript] Replace BITBOX/trout-bch with libauth and bump version to 0.5.0 - Update SignatureTemplate to allow passing of BITBOX keypairs, bitcore private keys, wif strings, and raw private key buffers. - experimentalFromP2PKH() now takes a SignatureTemplate instead of a BITBOX keypair. - Replace all bitbox utility functions with libauth/homebrew functions. - Replace bitbox transaction building with libauth/homebrew functions. - getTxDetails() / send() now returns a LibauthTransaction instead of a BITBOX transaction. With the option to return a raw transaction hex, so it can be imported into any library. - Still using BITBOX in tests (devDependency). - Clean up interfaces.
1 parent a616d23 commit 0e98b11

31 files changed

+1903
-1607
lines changed

cspell.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
"bignumber",
1515
"bitauth",
1616
"bitbox",
17+
"bitbox's",
1718
"bitcoin",
1819
"bitcoincash",
1920
"bitcoincashbip",
2021
"bitcoincashjs",
22+
"bitcore",
2123
"bitfield",
2224
"bitfield's",
2325
"blackie",
@@ -29,6 +31,7 @@
2931
"bytecode",
3032
"bytesize",
3133
"cardona",
34+
"cashaddress",
3235
"cashc",
3336
"cashscript",
3437
"castable",
@@ -79,6 +82,7 @@
7982
"lessthanorequal",
8083
"lexer",
8184
"libauth",
85+
"libauth's",
8286
"licho",
8387
"licho's",
8488
"lichos",
@@ -130,9 +134,11 @@
130134
"schnorr",
131135
"scripthash",
132136
"sdks",
137+
"secp",
133138
"setup",
134139
"sig",
135140
"sighash",
141+
"signable",
136142
"sigs",
137143
"spedn",
138144
"startup",

examples/Browser/package.json

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6-
"@testing-library/jest-dom": "^4.2.4",
7-
"@testing-library/react": "^9.3.2",
8-
"@testing-library/user-event": "^7.1.2",
96
"bitbox-sdk": "^8.11.2",
107
"cashscript": "^0.4.0",
118
"react": "^16.12.0",
@@ -15,7 +12,6 @@
1512
"scripts": {
1613
"start": "react-scripts start",
1714
"build": "react-scripts build",
18-
"test": "react-scripts test",
1915
"eject": "react-scripts eject"
2016
},
2117
"browserslist": {

packages/cashc/package.json

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cashc",
3-
"version": "0.4.3",
3+
"version": "0.5.0",
44
"description": "Compile Bitcoin Cash contracts to Bitcoin Cash Script or artifacts",
55
"keywords": [
66
"bitcoin",
@@ -45,11 +45,8 @@
4545
"@bitauth/libauth": "^1.17.1",
4646
"@types/semver": "^6.0.2",
4747
"antlr4ts": "^0.5.0-alpha.3",
48-
"bitbox-sdk": "^8.8.0",
49-
"bitcoincash-ops": "github:christroutner/bitcoincash-ops",
5048
"commander": "^6.0.0",
51-
"semver": "^6.3.0",
52-
"trout-bch": "github:christroutner/bitcoincashjs-lib#34f23671b42f0ebcbeba9f1f9e7bcdba7c50d55b"
49+
"semver": "^6.3.0"
5350
},
5451
"devDependencies": {
5552
"delay": "^4.3.0",

packages/cashc/src/ast/Globals.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export const NumberUnit: { [index:string] : number } = {
1414
WEEKS: 604800,
1515
};
1616

17-
1817
export enum GlobalFunction {
1918
ABS = 'abs',
2019
MIN = 'min',

packages/cashc/src/cashc-cli.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#! /usr/bin/env node
2+
import { binToHex } from '@bitauth/libauth';
23
import { program } from 'commander';
34
import fs from 'fs';
45
import path from 'path';
@@ -73,7 +74,7 @@ function run(): void {
7374
}
7475

7576
if (opts.hex) {
76-
console.log(Data.scriptToHex(script));
77+
console.log(binToHex(Data.scriptToBytecode(script)));
7778
return;
7879
}
7980

packages/cashc/src/generation/GenerateTargetTraversal.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,12 @@ export default class GenerateTargetTraversal extends AstTraversal {
149149
// If the final opcodes are OP_CHECK{LOCKTIME|SEQUENCE}VERIFY OP_DROP
150150
// Or if the final opcode is OP_ENDIF
151151
// Or if the remaining stack size >=5 (2DROP 2DROP 1 < NIP NIP NIP NIP)
152-
// then push it back to the script, and push OP_TRUE to the stack
152+
// then push it back to the script, and push OP_TRUE (OP_1) to the stack
153153
const finalOp = this.output.pop();
154154
this.pushToStack('(value)');
155155
if (finalOp === Op.OP_DROP || finalOp === Op.OP_ENDIF || (finalOp && this.stack.length >= 5)) {
156156
this.emit(finalOp);
157-
this.emit(Op.OP_TRUE);
157+
this.emit(Op.OP_1);
158158
}
159159
this.cleanStack();
160160
return node;

packages/cashc/src/generation/Script.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { Script as BScript } from 'bitbox-sdk';
1+
import { OpcodesBCH } from '@bitauth/libauth';
22
import { UnaryOperator, BinaryOperator } from '../ast/Operator';
33
import { GlobalFunction, TimeOp } from '../ast/Globals';
44
import { PrimitiveType, Type, BytesType } from '../ast/Type';
55
import { Data } from '../util';
66

7-
// TODO: Update this some tie when a library has OP_REVERSEBYTES support
8-
export const Op = { ...new BScript().opcodes, OP_REVERSEBYTES: 188 };
7+
export const Op = OpcodesBCH;
98
export type Op = number;
109
export type OpOrData = Op | Buffer;
1110
export type Script = OpOrData[];

packages/cashc/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ export {
1515
CashCompiler,
1616
} from './util';
1717

18-
export const version = '0.4.3';
18+
export const version = '0.5.0';

packages/cashc/src/util.ts

Lines changed: 116 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
import {
2+
bigIntToScriptNumber,
3+
parseBytesAsScriptNumber,
4+
isScriptNumberError,
5+
encodeDataPush,
6+
parseBytecode,
7+
serializeAuthenticationInstructions,
8+
disassembleBytecodeBCH,
9+
AuthenticationInstructions,
10+
hexToBin,
11+
} from '@bitauth/libauth';
112
import { ANTLRInputStream, CommonTokenStream } from 'antlr4ts';
213
import fs from 'fs';
314
import { Ast } from './ast/AST';
@@ -13,43 +24,114 @@ import TargetCodeOptimisation from './optimisations/TargetCodeOptimisation';
1324
import ReplaceBytecodeNop from './generation/ReplaceBytecodeNop';
1425
import VerifyCovenantTraversal from './semantic/VerifyCovenantTraversal';
1526

16-
const bch = require('trout-bch');
17-
1827
export const Data = {
19-
encodeBool(b: boolean): Buffer {
20-
return b ? this.encodeInt(1) : this.encodeInt(0);
28+
encodeBool(bool: boolean): Buffer {
29+
return bool ? this.encodeInt(1) : this.encodeInt(0);
2130
},
22-
decodeBool(b: Buffer): boolean {
31+
32+
decodeBool(encodedBool: Buffer): boolean {
2333
// Any encoding of 0 is false, else true
24-
for (let i = 0; i < b.byteLength; i += 1) {
25-
if (b[i] !== 0) {
34+
for (let i = 0; i < encodedBool.byteLength; i += 1) {
35+
if (encodedBool[i] !== 0) {
2636
// Can be negative zero
27-
if (i === b.byteLength - 1 && b[i] === 0x80) return false;
37+
if (i === encodedBool.byteLength - 1 && encodedBool[i] === 0x80) return false;
2838
return true;
2939
}
3040
}
3141
return false;
3242
},
33-
encodeInt(i: number): Buffer {
34-
return bch.script.number.encode(i);
43+
44+
encodeInt(int: number): Buffer {
45+
return Buffer.from(bigIntToScriptNumber(BigInt(int)));
3546
},
36-
decodeInt(i: Buffer, maxLength?: number): number {
37-
return bch.script.number.decode(i, maxLength);
47+
48+
decodeInt(encodedInt: Buffer, maxLength?: number): number {
49+
const options = { maximumScriptNumberByteLength: maxLength };
50+
const result = parseBytesAsScriptNumber(encodedInt, options);
51+
52+
if (isScriptNumberError(result)) {
53+
throw new Error(result);
54+
}
55+
56+
return Number(result);
3857
},
39-
encodeString(s: string): Buffer {
40-
return Buffer.from(s, 'ascii');
58+
59+
encodeString(str: string): Buffer {
60+
return Buffer.from(str, 'ascii');
4161
},
42-
decodeString(s: Buffer): string {
43-
return s.toString('ascii');
62+
63+
decodeString(encodedString: Buffer): string {
64+
return encodedString.toString('ascii');
4465
},
45-
scriptToAsm(s: Script): string {
46-
return bch.script.toASM(bch.script.compile(s));
66+
67+
scriptToAsm(script: Script): string {
68+
return Data.bytecodeToAsm(Data.scriptToBytecode(script));
4769
},
48-
scriptToHex(s: Script): string {
49-
return bch.script.compile(s).toString('hex');
70+
71+
asmToScript(asm: string): Script {
72+
return Data.bytecodeToScript(Data.asmToBytecode(asm));
5073
},
51-
asmToScript(s: string): Script {
52-
return bch.script.decompile(bch.script.fromASM(s));
74+
75+
scriptToBytecode(script: Script): Uint8Array {
76+
// Convert the script elements to AuthenticationInstructions
77+
const instructions = script.map((opOrData) => {
78+
if (opOrData instanceof Buffer) {
79+
return parseBytecode(encodeDataPush(opOrData))[0];
80+
} else {
81+
return { opcode: opOrData };
82+
}
83+
});
84+
85+
// Convert the AuthenticationInstructions to bytecode
86+
return serializeAuthenticationInstructions(instructions);
87+
},
88+
89+
bytecodeToScript(bytecode: Uint8Array): Script {
90+
// Convert the bytecode to AuthenticationInstructions
91+
const instructions = parseBytecode(bytecode) as AuthenticationInstructions;
92+
93+
// Convert the AuthenticationInstructions to script elements
94+
const script = instructions.map((instruction) => {
95+
if ('data' in instruction) {
96+
return Buffer.from(instruction.data);
97+
} else {
98+
return instruction.opcode;
99+
}
100+
});
101+
102+
return script;
103+
},
104+
105+
asmToBytecode(asm: string): Uint8Array {
106+
// Remove any duplicate whitespace
107+
asm = asm.replace(/\s+/g, ' ').trim();
108+
109+
// Convert the ASM tokens to AuthenticationInstructions
110+
const instructions = asm.split(' ').map((token) => {
111+
if (token.startsWith('OP_')) {
112+
return { opcode: Op[token as keyof typeof Op] };
113+
} else {
114+
return parseBytecode(encodeDataPush(hexToBin(token)))[0];
115+
}
116+
});
117+
118+
// Convert the AuthenticationInstructions to bytecode
119+
return serializeAuthenticationInstructions(instructions);
120+
},
121+
122+
bytecodeToAsm(bytecode: Uint8Array): string {
123+
// Convert the bytecode to libauth's ASM format
124+
let asm = disassembleBytecodeBCH(bytecode);
125+
126+
// COnvert libauth's ASM format to BITBOX's
127+
asm = asm.replace(/OP_PUSHBYTES_[^\s]+/g, '');
128+
asm = asm.replace(/OP_PUSHDATA[^\s]+ [^\s]+/g, '');
129+
asm = asm.replace(/(^|\s)0x/g, ' ');
130+
131+
// Remove any duplicate whitespace
132+
asm = asm.replace(/\s+/g, ' ').trim();
133+
134+
return asm;
53135
},
54136
};
55137
export type Data = typeof Data;
@@ -67,13 +149,20 @@ export type Artifacts = typeof Artifacts;
67149

68150
export const CashCompiler = {
69151
compileString(code: string): Artifact {
152+
// Lexing + parsing
70153
let ast = parseCode(code);
154+
155+
// Semantic analysis
71156
ast = ast.accept(new SymbolTableTraversal()) as Ast;
72157
ast = ast.accept(new TypeCheckTraversal()) as Ast;
73158
ast = ast.accept(new VerifyCovenantTraversal()) as Ast;
159+
160+
// Code generation
74161
const traversal = new GenerateTargetTraversal();
75-
ast.accept(traversal);
162+
ast = ast.accept(traversal) as Ast;
76163
let bytecode = traversal.output;
164+
165+
// Bytecode optimisation
77166
bytecode = TargetCodeOptimisation.optimise(bytecode);
78167
bytecode = ReplaceBytecodeNop.replace(bytecode);
79168

@@ -87,9 +176,12 @@ export const CashCompiler = {
87176
export type CashCompiler = typeof CashCompiler;
88177

89178
export function parseCode(code: string): Ast {
179+
// Lexing
90180
const inputStream: ANTLRInputStream = new ANTLRInputStream(code);
91181
const lexer: CashScriptLexer = new CashScriptLexer(inputStream);
92182
const tokenStream: CommonTokenStream = new CommonTokenStream(lexer);
183+
184+
// Parsing + AST building
93185
const parser: CashScriptParser = new CashScriptParser(tokenStream);
94186
const ast: Ast = new AstBuilder(parser.sourceFile()).build() as Ast;
95187
return ast;
@@ -103,5 +195,5 @@ export function countOpcodes(script: Script): number {
103195
}
104196

105197
export function calculateBytesize(script: Script): number {
106-
return bch.script.compile(script).byteLength;
198+
return Data.scriptToBytecode(script).byteLength;
107199
}

packages/cashc/test/fixture/announcement.cash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
pragma cashscript ^0.4.0;
1+
pragma cashscript ^0.5.0;
22

33
/* This is a contract showcasing covenants outside of regular transactional use.
44
* It enforces the contract to make an "announcement" on Memo.cash, and send the

0 commit comments

Comments
 (0)