1
+ import {
2
+ bigIntToScriptNumber ,
3
+ parseBytesAsScriptNumber ,
4
+ isScriptNumberError ,
5
+ encodeDataPush ,
6
+ parseBytecode ,
7
+ serializeAuthenticationInstructions ,
8
+ disassembleBytecodeBCH ,
9
+ AuthenticationInstructions ,
10
+ hexToBin ,
11
+ } from '@bitauth/libauth' ;
1
12
import { ANTLRInputStream , CommonTokenStream } from 'antlr4ts' ;
2
13
import fs from 'fs' ;
3
14
import { Ast } from './ast/AST' ;
@@ -13,43 +24,114 @@ import TargetCodeOptimisation from './optimisations/TargetCodeOptimisation';
13
24
import ReplaceBytecodeNop from './generation/ReplaceBytecodeNop' ;
14
25
import VerifyCovenantTraversal from './semantic/VerifyCovenantTraversal' ;
15
26
16
- const bch = require ( 'trout-bch' ) ;
17
-
18
27
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 ) ;
21
30
} ,
22
- decodeBool ( b : Buffer ) : boolean {
31
+
32
+ decodeBool ( encodedBool : Buffer ) : boolean {
23
33
// 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 ) {
26
36
// 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 ;
28
38
return true ;
29
39
}
30
40
}
31
41
return false ;
32
42
} ,
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 ) ) ) ;
35
46
} ,
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 ) ;
38
57
} ,
39
- encodeString ( s : string ) : Buffer {
40
- return Buffer . from ( s , 'ascii' ) ;
58
+
59
+ encodeString ( str : string ) : Buffer {
60
+ return Buffer . from ( str , 'ascii' ) ;
41
61
} ,
42
- decodeString ( s : Buffer ) : string {
43
- return s . toString ( 'ascii' ) ;
62
+
63
+ decodeString ( encodedString : Buffer ) : string {
64
+ return encodedString . toString ( 'ascii' ) ;
44
65
} ,
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 ) ) ;
47
69
} ,
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 ) ) ;
50
73
} ,
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 ( / O P _ P U S H B Y T E S _ [ ^ \s ] + / g, '' ) ;
128
+ asm = asm . replace ( / O P _ P U S H D A T A [ ^ \s ] + [ ^ \s ] + / g, '' ) ;
129
+ asm = asm . replace ( / ( ^ | \s ) 0 x / g, ' ' ) ;
130
+
131
+ // Remove any duplicate whitespace
132
+ asm = asm . replace ( / \s + / g, ' ' ) . trim ( ) ;
133
+
134
+ return asm ;
53
135
} ,
54
136
} ;
55
137
export type Data = typeof Data ;
@@ -67,13 +149,20 @@ export type Artifacts = typeof Artifacts;
67
149
68
150
export const CashCompiler = {
69
151
compileString ( code : string ) : Artifact {
152
+ // Lexing + parsing
70
153
let ast = parseCode ( code ) ;
154
+
155
+ // Semantic analysis
71
156
ast = ast . accept ( new SymbolTableTraversal ( ) ) as Ast ;
72
157
ast = ast . accept ( new TypeCheckTraversal ( ) ) as Ast ;
73
158
ast = ast . accept ( new VerifyCovenantTraversal ( ) ) as Ast ;
159
+
160
+ // Code generation
74
161
const traversal = new GenerateTargetTraversal ( ) ;
75
- ast . accept ( traversal ) ;
162
+ ast = ast . accept ( traversal ) as Ast ;
76
163
let bytecode = traversal . output ;
164
+
165
+ // Bytecode optimisation
77
166
bytecode = TargetCodeOptimisation . optimise ( bytecode ) ;
78
167
bytecode = ReplaceBytecodeNop . replace ( bytecode ) ;
79
168
@@ -87,9 +176,12 @@ export const CashCompiler = {
87
176
export type CashCompiler = typeof CashCompiler ;
88
177
89
178
export function parseCode ( code : string ) : Ast {
179
+ // Lexing
90
180
const inputStream : ANTLRInputStream = new ANTLRInputStream ( code ) ;
91
181
const lexer : CashScriptLexer = new CashScriptLexer ( inputStream ) ;
92
182
const tokenStream : CommonTokenStream = new CommonTokenStream ( lexer ) ;
183
+
184
+ // Parsing + AST building
93
185
const parser : CashScriptParser = new CashScriptParser ( tokenStream ) ;
94
186
const ast : Ast = new AstBuilder ( parser . sourceFile ( ) ) . build ( ) as Ast ;
95
187
return ast ;
@@ -103,5 +195,5 @@ export function countOpcodes(script: Script): number {
103
195
}
104
196
105
197
export function calculateBytesize ( script : Script ) : number {
106
- return bch . script . compile ( script ) . byteLength ;
198
+ return Data . scriptToBytecode ( script ) . byteLength ;
107
199
}
0 commit comments