├── LICENCE ├── scripts ├── builtin.js ├── bytecode.js ├── extract-verify.js ├── call-marker.js ├── visitor.js ├── replace-bytecode.js ├── emit.js ├── compile.js ├── inline.js ├── ast.js ├── emitter.js ├── parser.js └── optimizations.js ├── index.html └── README.md /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Curiosity driven 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /scripts/builtin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Curiosity driven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var operators = { 18 | size: { 19 | append: [{ 20 | opcode: "nip", 21 | stackBalance: -1 22 | }], 23 | stackBalance: 1 24 | }, 25 | roll: { 26 | inline: false 27 | }, 28 | equal: { 29 | hasVerifyVersion: true 30 | }, 31 | checkSig: { 32 | hasVerifyVersion: true 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /scripts/bytecode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Curiosity driven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | function getByteCode(source) { 18 | var block = parser(lexer(source)).parseBlock(); 19 | block = inlineVariables(block, operators); 20 | // promote VERIFY to the top 21 | block = block.transform(extractVerify); 22 | // mark last function calls 23 | block = block.transform(new LastFunctionCallMarker()); 24 | // generate intermediate bytecode from AST 25 | return block.transform(new IntermediateCodeEmitter(operators)); 26 | } 27 | -------------------------------------------------------------------------------- /scripts/extract-verify.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Curiosity driven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var extractVerify = new Visitor(); 18 | extractVerify.conditional = function(condition, trueBranch, falseBranch) { 19 | if (trueBranch instanceof Block && falseBranch instanceof Block && 20 | (trueBranch.statements.length === 1) && 21 | (trueBranch.statements[0] instanceof Verification) && 22 | (falseBranch.statements.length === 1) && 23 | (falseBranch.statements[0] instanceof Verification)) { 24 | return new Verification(new Conditional(condition, 25 | trueBranch.statements[0].value, 26 | falseBranch.statements[0].value)); 27 | } 28 | return new Conditional(condition, trueBranch, falseBranch); 29 | }; 30 | -------------------------------------------------------------------------------- /scripts/call-marker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Curiosity driven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | function LastFunctionCallMarker() { 18 | this.lastFunctionCall = false; 19 | } 20 | 21 | LastFunctionCallMarker.prototype = new Visitor(); 22 | 23 | LastFunctionCallMarker.prototype.instructions = function(args) { 24 | var _this = this; 25 | return new Block(args.map(function(arg, index) { 26 | _this.lastFunctionCall = index === args.length - 1; 27 | return arg.transform(_this); 28 | })); 29 | }; 30 | 31 | LastFunctionCallMarker.prototype.invokeFunction = function(func, args, meta) { 32 | if (this.lastFunctionCall) { 33 | if (!meta) { 34 | meta = {}; 35 | } 36 | meta.last = true; 37 | } 38 | return new FunctionCall(func, args, meta); 39 | }; 40 | -------------------------------------------------------------------------------- /scripts/visitor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Curiosity driven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | function Visitor() { 18 | 19 | } 20 | 21 | Visitor.prototype = { 22 | assume: function(variables) { 23 | return new Assumption(variables); 24 | }, 25 | verify: function(args) { 26 | return new Verification(args); 27 | }, 28 | instructions: function(args) { 29 | return new Block(args); 30 | }, 31 | invokeFunction: function(func, args, meta) { 32 | return new FunctionCall(func, args, meta); 33 | }, 34 | substitute: function(name) { 35 | return new Identifier(name); 36 | }, 37 | define: function(name, value, meta) { 38 | return new Assignment(name, value, meta); 39 | }, 40 | conditional: function(condition, trueBranch, falseBranch) { 41 | return new Conditional(condition, trueBranch, falseBranch); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /scripts/replace-bytecode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Curiosity driven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | function replaceBytecode(instructions, template, onreplace) { 18 | function matchesTemplate(index) { 19 | for (var i = 0; i < template.length; i++) { 20 | if (template[i]) { 21 | for (var item in template[i]) { 22 | if (template[i][item] !== instructions[index + i][item]) { 23 | return false; 24 | } 25 | } 26 | } 27 | } 28 | return true; 29 | } 30 | var results = []; 31 | for (var i = 0; i < instructions.length;) { 32 | if ((i <= instructions.length - template.length) && matchesTemplate(i)) { 33 | Array.prototype.push.apply(results, onreplace(instructions.slice(i, i + template.length))); 34 | i += template.length; 35 | } else { 36 | results.push(instructions[i]); 37 | i++; 38 | } 39 | } 40 | return results; 41 | } 42 | -------------------------------------------------------------------------------- /scripts/emit.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Curiosity driven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | function emitCode(instructions) { 18 | var results = []; 19 | 20 | function numToOpcode(num) { 21 | if (num >= 0 && num <= 16) { 22 | return "OP_" + num; 23 | } else { 24 | opcode = num.toString(16); 25 | if (opcode.length % 2 === 1) { 26 | return "0" + opcode; 27 | } 28 | return opcode; 29 | } 30 | } 31 | instructions.forEach(function(instruction) { 32 | if (instruction.opcode === "pick") { 33 | if (typeof instruction.index === "number") { 34 | results.push(numToOpcode(instruction.index)); 35 | } 36 | results.push("OP_PICK"); 37 | } else { 38 | if (typeof instruction.opcode === "string") { 39 | var opcode = instruction.opcode; 40 | if (opcode.match(/^([a-f0-9][a-f0-9])+$/g)) { 41 | results.push(opcode); 42 | } else { 43 | results.push("OP_" + opcode.toUpperCase()); 44 | } 45 | } else if (typeof instruction.opcode === "number") { 46 | results.push(numToOpcode(instruction.opcode)); 47 | } 48 | } 49 | }); 50 | return results; 51 | } 52 | -------------------------------------------------------------------------------- /scripts/compile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Curiosity driven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | function compile(source) { 18 | var code = getByteCode(source); 19 | var optimizations = [ 20 | // remove last VERIFY statement - it's not necessary because Bitcoin Script 21 | // requires last argument on stack to be truthy to succeed 22 | 'dropLastVerify', 23 | // replace two-word instructions with shorter opcodes 24 | // EQUAL followed by VERIFY gets replaced by EQUALVERIFY 25 | 'glueVerify', 26 | // optimize CheckSigs 27 | 'optimizeCheckSigs', 28 | // optimize size -> size tuck 29 | 'optimizeSize', 30 | // rewrite mutable 31 | 'rewriteMutableAssignment', 32 | // adjust relative to absolute PICKs 33 | 'adjustPicks', 34 | // mark last variables 35 | 'markLastVariableUsage', 36 | // optimize last1 last2 OP last 3 OP 37 | 'optimizeTwoOp', 38 | // optimize three vars 39 | 'optimizeSizeTuckVariables', 40 | // previous optimizations could have changed the order of operators 41 | // so adjust picks again 42 | 'adjustPicks', 43 | // optimize pick roll pick 44 | 'optimizePickRollPick', 45 | // optimize picks with swaps 46 | 'optimizeBoolAndOrPicks', 47 | // optimize IF 0 PICK ELSE 1 PICK ENDIF 48 | 'optimizeIfPick', 49 | // optimize EQUAL IF 0 ELSE 1 ENDIF 50 | 'optimizeBoolPick', 51 | // optimize 1 PICK 1 PICK at the end 52 | 'optimizeLastCallPicks', 53 | // optimize 1 PICK op at the end 54 | 'optimizeLastCallSwap', 55 | // use DUPs instead of 0 PICK 56 | 'optimizeDups', 57 | // drop DUP if used in pubkey CHECKSIG script 58 | 'optimizeDupCheckSig' 59 | ]; 60 | 61 | optimizations.forEach(function(optimization) { 62 | code = byteCodeOptimizations[optimization](code, operators); 63 | }); 64 | 65 | // generate Bitcoin Script from intermediate code 66 | return emitCode(code); 67 | } 68 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 |
16 |For details see the README document or the Bitcoin contracts article.
41 |Superscript:
42 | 53 | 54 |Bitcoin script:
55 | 56 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /scripts/inline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Curiosity driven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | function inlineVariables(block, operators) { 18 | var counts = {}; 19 | var counter = new Visitor(); 20 | counter.substitute = function(name) { 21 | if (name in counts) { 22 | counts[name]++; 23 | } else { 24 | counts[name] = 1; 25 | } 26 | return Visitor.prototype.substitute.call(this, name); 27 | }; 28 | var caninline = { 29 | assume: function(variables) { 30 | return false; 31 | }, 32 | verify: function(args) { 33 | return false; 34 | }, 35 | instructions: function(args) { 36 | return args.every(function(arg) { 37 | return !!arg; 38 | }); 39 | }, 40 | invokeFunction: function(func, args, meta) { 41 | if (func in operators && 'inline' in operators[func]) { 42 | return operators[func].inline; 43 | } 44 | return args.every(function(arg) { 45 | return !!arg; 46 | }); 47 | }, 48 | substitute: function(name) { 49 | return true; 50 | }, 51 | define: function(name, value, meta) { 52 | return false; 53 | }, 54 | conditional: function(condition, trueBranch, falseBranch) { 55 | var result = condition.transform(this) && trueBranch.transform(this); 56 | if (falseBranch) { 57 | result = result && falseBranch.transform(this); 58 | } 59 | return result; 60 | } 61 | } 62 | var definitions = {}; 63 | var inliner = new Visitor(); 64 | inliner.instructions = function(args) { 65 | return new Block(args.filter(function(arg) { 66 | return !!arg; // filter nulls 67 | })); 68 | }; 69 | inliner.substitute = function(name) { 70 | if (name in definitions) { 71 | return definitions[name]; 72 | } 73 | return Visitor.prototype.substitute.call(this, name); 74 | }; 75 | inliner.define = function(name, value, meta) { 76 | if (counts[name] === 1 && value.transform(caninline)) { 77 | definitions[name] = value; 78 | return null; // drop assignment 79 | } 80 | return Visitor.prototype.define.call(this, name, value, meta); 81 | }; 82 | return block.transform(counter).transform(inliner); 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Superscript compiler 2 | ==================== 3 | 4 | This small programming language has been designed to better illustrate the advanced 5 | scripting used in the [Bitcoin contracts article](https://curiosity-driven.org/bitcoin-contracts). 6 | 7 | Note that this compiler is made only for educational purposes and is **highly experimental** and not actively maintained. You are welcomed to fork and hack on it. 8 | 9 | Both Superscript and Bitcoin Script are [stack-oriented programming languages](http://en.wikipedia.org/wiki/Stack-oriented_programming_language) and that makes the simple 10 | compiler (`scripts/bytecode.js`) trivial. 11 | 12 | As each byte of Bitcoin transaction [makes the transaction fees bigger](https://en.bitcoin.it/wiki/Transaction_fees#Technical_info) 13 | the full compiler is comprised of a set of optimizations (e.g. optimizing 14 | `OP_0 OP_PICK` into one `OP_DUP`). Most of them are generic (can be applied to any script) but some of them are specific to scripts used in the [Bitcoin contracts article](https://curiosity-driven.org/bitcoin-contracts). 15 | 16 | The language 17 | ============ 18 | 19 | The Superscript code consist of an optional assume statement followed by a number 20 | of assignments, conditionals or verification statements. 21 | 22 | Assume statements 23 | ----------------- 24 | They do not generate any code but are ambient declarations about the state of stack before the script executes. Basically they describe the expected input script and are used by the compiler to resolve variable names to their positions on the stack. 25 | 26 | assume signatureA, signatureB, secret 27 | 28 | This statement expects one secret and two signatures to appear on the stack, `secret` being the top-most item. 29 | 30 | Assignments 31 | ----------- 32 | They range from the immutable bindings (`let NAME = value`) to mutable bindings (`let mutable NAME = value`) to modifications (`NAME <- value`). Modifications can work only on names bound to mutable names. Immutable bindings can be inlined. 33 | 34 | let pubKeyA = '04d4bf4642f56fc7af0d2382e2cac34fa16ed3321633f91d06128f0e5c0d17479778cc1f2cc7e4a0c6f1e72d905532e8e127a031bb9794b3ef9b68b657f51cc691' 35 | let signedByA = checkSig(signatureA, pubKeyA) 36 | 37 | Conditional statements 38 | ---------------------- 39 | They take an expression and if it is truthy execute the associated block (`if (expr) { block }`) optionally providing an alternative block (`else`) that is executed if the expression is not truthy. 40 | 41 | let mutable num = sizeB + sizeC + sizeA - 96 42 | if (num > 2) { 43 | num <- num - 3 44 | } 45 | 46 | Verification statements 47 | ----------------------- 48 | These statements evaluate the expression and if it is not truthy abort the script execution. Verify can emit `OP_VERIFY` opcode or be glued during optimization with previous opcode (e.g. producing `OP_EQUALVERIFY`). 49 | 50 | Verify statements at the end of the script are removed entirely (see `dropLastVerify` in `scripts/optimizations.js`). This is safe as [Bitcoin script execution](https://en.bitcoin.it/wiki/Script) requires a truthy value on the top of the stack to succeed. 51 | 52 | verify (secretKnown || signedByB) && signedByA 53 | 54 | Function calls 55 | -------------- 56 | Each function call produces an opcode derived from the function name (e.g. `sha256(secret)` produces `OP_SHA256`). Some opcodes have special behavior that influences 57 | the compiler (see `scripts/builtin.js`). Operators (such as `==`) map to opcodes 58 | (see `scripts/emitter.js`). 59 | 60 | For more technical details of Bitcoin Script execution see [Script on Bitcoin Wiki](https://en.bitcoin.it/wiki/Script). 61 | -------------------------------------------------------------------------------- /scripts/ast.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Curiosity driven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | function FunctionCall(name, args, meta) { 18 | this.name = name; 19 | this.args = args; 20 | this.meta = meta; 21 | } 22 | 23 | FunctionCall.prototype.toString = function() { 24 | if (this.name.charAt(0).toUpperCase() !== this.name.charAt(0).toLowerCase()) { 25 | return this.name + "(" + this.args.join(", ") + ")"; 26 | } 27 | return this.args[0] + " " + this.name + " " + this.args[1]; 28 | }; 29 | 30 | FunctionCall.prototype.transform = function(context) { 31 | return context.invokeFunction(this.name, this.args.map(function(arg) { 32 | return arg.transform(context); 33 | }), this.meta); 34 | } 35 | 36 | function Identifier(name) { 37 | this.name = name; 38 | } 39 | 40 | Identifier.prototype.toString = function() { 41 | return this.name; 42 | }; 43 | 44 | Identifier.prototype.transform = function(context) { 45 | return context.substitute(this.name); 46 | }; 47 | 48 | function Assumption(args) { 49 | this.args = args; 50 | } 51 | 52 | Assumption.prototype.toString = function() { 53 | return "assume " + this.args.join(", ") + "\n"; 54 | }; 55 | 56 | Assumption.prototype.transform = function(context) { 57 | return context.assume(this.args); 58 | } 59 | 60 | function Assignment(variableName, value, meta) { 61 | this.variableName = variableName; 62 | this.value = value; 63 | this.meta = meta; 64 | } 65 | 66 | Assignment.prototype.toString = function() { 67 | return "let " + this.variableName + " = " + this.value + "\n"; 68 | }; 69 | 70 | Assignment.prototype.transform = function(context) { 71 | var value = this.value.transform(context); 72 | return context.define(this.variableName, value, this.meta); 73 | }; 74 | 75 | function Block(statements) { 76 | this.statements = statements; 77 | } 78 | 79 | Block.prototype.toString = function() { 80 | return this.statements.join("\n"); 81 | }; 82 | 83 | Block.prototype.transform = function(context) { 84 | return context.instructions(this.statements.map(function(statement) { 85 | return statement.transform(context); 86 | })); 87 | }; 88 | 89 | function Conditional(condition, trueBranch, falseBranch) { 90 | this.condition = condition; 91 | this.trueBranch = trueBranch; 92 | this.falseBranch = falseBranch; 93 | } 94 | 95 | Conditional.prototype.toString = function() { 96 | var result = "if (" + this.condition + ") {\n " + this.trueBranch + "}"; 97 | if (this.falseBranch) { 98 | result += " else {\n " + this.falseBranch + " }"; 99 | } 100 | return result; 101 | }; 102 | 103 | Conditional.prototype.transform = function(context) { 104 | var condition = this.condition.transform(context); 105 | var trueBranch = this.trueBranch.transform(context); 106 | var falseBranch; 107 | if (this.falseBranch) { 108 | falseBranch = this.falseBranch.transform(context); 109 | } 110 | return context.conditional(condition, trueBranch, falseBranch); 111 | }; 112 | 113 | function Verification(value) { 114 | this.value = value; 115 | } 116 | 117 | Verification.prototype.toString = function() { 118 | return "verify " + this.value + "\n"; 119 | }; 120 | 121 | Verification.prototype.transform = function(context) { 122 | return context.verify(this.value.transform(context)); 123 | }; 124 | -------------------------------------------------------------------------------- /scripts/emitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Curiosity driven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | function IntermediateCodeEmitter(operators) { 18 | this.operators = operators; 19 | } 20 | 21 | IntermediateCodeEmitter.OPERATORS = { 22 | "==": "equal", 23 | "&&": "boolAnd", 24 | "||": "boolOr", 25 | "+": "add", 26 | "-": "sub", 27 | ">": "greaterThan", 28 | "<": "lessThan" 29 | }; 30 | 31 | IntermediateCodeEmitter.prototype = { 32 | assume: function(variables) { 33 | return variables.map(function(variable) { 34 | return { 35 | assume: "variable", 36 | name: variable, 37 | stackBalance: 1 38 | }; 39 | }); 40 | }, 41 | verify: function(args) { 42 | var results = []; 43 | Array.prototype.push.apply(results, args); 44 | results.push({ 45 | opcode: "verify", 46 | stackBalance: -1 47 | }); 48 | return results; 49 | }, 50 | instructions: function(args) { 51 | var results = []; 52 | args.forEach(function(arg) { 53 | Array.prototype.push.apply(results, arg); 54 | }); 55 | return results; 56 | }, 57 | invokeFunction: function(func, args, meta) { 58 | var results = []; 59 | args.forEach(function(arg) { 60 | Array.prototype.push.apply(results, arg); 61 | }); 62 | func = IntermediateCodeEmitter.OPERATORS[func] || func; 63 | // by default function takes N stack items where N is number of args and leaves 1 result 64 | var stackBalance = -(args.length - 1); 65 | if (func in this.operators && 'stackBalance' in this.operators[func]) { 66 | stackBalance = this.operators[func].stackBalance; 67 | } 68 | var instruction = { 69 | opcode: func, 70 | stackBalance: stackBalance 71 | }; 72 | if (meta) { 73 | for (var item in meta) { 74 | instruction[item] = meta[item]; 75 | } 76 | } 77 | results.push(instruction); 78 | if (func in this.operators && this.operators[func].append) { 79 | Array.prototype.push.apply(results, this.operators[func].append); 80 | } 81 | return results; 82 | }, 83 | substitute: function(name) { 84 | if (name.charAt(0) === "'" && name.charAt(name.length - 1) === "'") { 85 | return [{ 86 | opcode: name.substring(1, name.length - 1), 87 | stackBalance: 1 88 | }]; 89 | } 90 | if (!isNaN(parseInt(name, 10))) { 91 | return [{ 92 | opcode: parseInt(name, 10), 93 | stackBalance: 1 94 | }]; 95 | } 96 | return [{ 97 | opcode: "pick", 98 | name: name, 99 | stackBalance: 1 100 | }]; 101 | }, 102 | define: function(name, value, meta) { 103 | var results = []; 104 | if (meta && meta.mutable === "assignment") { 105 | results.push({ 106 | pragma: "marker", 107 | mutable: "assignment", 108 | name: name 109 | }); 110 | } 111 | Array.prototype.push.apply(results, value); 112 | var instruction = { 113 | assume: "variable", 114 | name: name 115 | }; 116 | if (meta) { 117 | for (var item in meta) { 118 | instruction[item] = meta[item]; 119 | } 120 | } 121 | results.push(instruction); 122 | return results; 123 | }, 124 | conditional: function(condition, trueBranch, falseBranch) { 125 | var result = []; 126 | Array.prototype.push.apply(result, condition); 127 | result.push({ 128 | opcode: "if", 129 | stackBalance: -1, 130 | stack: "push" 131 | }); 132 | Array.prototype.push.apply(result, trueBranch); 133 | if (falseBranch) { 134 | result.push({ 135 | opcode: "else", 136 | stack: "pop,push" 137 | }); 138 | Array.prototype.push.apply(result, falseBranch); 139 | }; 140 | result.push({ 141 | opcode: "endif", 142 | stack: "pop" 143 | }); 144 | return result; 145 | } 146 | }; 147 | -------------------------------------------------------------------------------- /scripts/parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Curiosity driven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | function parser(tokens) { 18 | var current, done; 19 | 20 | function next() { 21 | var next = tokens.next(); 22 | current = next.value; 23 | done = next.done; 24 | } 25 | next(); 26 | return { 27 | parseBlock: function() { 28 | var instructions = []; 29 | while (current !== "}" && !done) { 30 | if (current === "assume") { 31 | instructions.push(this.parseAssume()); 32 | } else if (current === "let") { 33 | instructions.push(this.parseLet()); 34 | } else if (current === "if") { 35 | instructions.push(this.parseIf()); 36 | } else if (current === "verify") { 37 | instructions.push(this.parseVerify()); 38 | } else if (current.match(/[A-Za-z][A-Za-z0-9_]*/)) { 39 | instructions.push(this.parseMutableAssignment()) 40 | } else { 41 | throw new SyntaxError("Unknown keyword: " + current); 42 | } 43 | } 44 | return new Block(instructions); 45 | }, 46 | parseAssume: function() { 47 | var variables = []; 48 | do { 49 | next(); // eat "assume" or "," 50 | variables.push(current); 51 | next(); // eat variable name 52 | } while (current === ",") 53 | return new Assumption(variables); 54 | }, 55 | parseLet: function() { 56 | var meta = null; 57 | next(); // eat "let" 58 | var name = current; 59 | if (name === "mutable") { 60 | meta = { 61 | mutable: "variable" 62 | }; 63 | next(); // eat "mutable" 64 | name = current; 65 | } 66 | next(); // eat variable name 67 | if (current !== "=") { 68 | throw new SyntaxError("Expected = in let statement"); 69 | } 70 | next(); // eat "=" 71 | var value = this.parseExpression(); 72 | return new Assignment(name, value, meta); 73 | }, 74 | parseMutableAssignment: function() { 75 | var name = current; 76 | next(); // eat variable name 77 | if (current !== "<-") { 78 | throw new SyntaxError("Expected <- in mutable assignment statement"); 79 | } 80 | next(); // eat "=" 81 | var value = this.parseExpression(); 82 | return new Assignment(name, value, { 83 | mutable: "assignment" 84 | }); 85 | }, 86 | parseIf: function() { 87 | next(); // eat "if" 88 | if (current !== "(") { 89 | throw new SyntaxError("Expected ( in if statement"); 90 | } 91 | next(); // eat "(" 92 | var condition = this.parseExpression(); 93 | if (current !== ")") { 94 | throw new SyntaxError("Expected ) in if statement"); 95 | } 96 | next(); // eat ")" 97 | if (current !== "{") { 98 | throw new SyntaxError("Expected { in if statement"); 99 | } 100 | next(); // eat "{" 101 | var trueBranch = this.parseBlock(); 102 | if (current !== "}") { 103 | throw new SyntaxError("Expected } in if statement"); 104 | } 105 | next(); // eat "}" 106 | var falseBranch; 107 | if (current === "else") { 108 | next(); // eat "else" 109 | if (current !== "{") { 110 | throw new SyntaxError("Expected { in if/else statement"); 111 | } 112 | next(); // eat "{" 113 | falseBranch = this.parseBlock(); 114 | if (current !== "}") { 115 | throw new SyntaxError("Expected } in if/else statement"); 116 | } 117 | next(); // eat "}" 118 | } 119 | return new Conditional(condition, trueBranch, falseBranch); 120 | }, 121 | parseExpression: function() { 122 | var left = this.parsePrimary(), 123 | right, operator; 124 | while (current === "==" || current === "&&" || current === "||" || current === "+" || current === "-" || current === ">" || current === "<") { 125 | operator = current; 126 | next(); // eat operator 127 | right = this.parsePrimary(); 128 | left = new FunctionCall(operator, [left, right]); 129 | } 130 | return left; 131 | }, 132 | parsePrimary: function() { 133 | if (current === "(") { 134 | next(); // eat "(" 135 | var value = this.parseExpression(); 136 | if (current !== ")") { 137 | throw new SyntaxError("Expected ) closing a group."); 138 | } 139 | next(); // eat ")" 140 | return value; 141 | } 142 | var identifier = current; 143 | next(); // eat identifier 144 | if (current === "(") { 145 | // function call 146 | next(); // eat ( 147 | var args = []; 148 | while (current !== ")") { 149 | args.push(this.parseExpression()); 150 | if (current !== "," && current !== ")") { 151 | throw new SyntaxError("Expected , or ) in function call"); 152 | } 153 | if (current === ",") { 154 | next(); // eat "," 155 | } 156 | } 157 | next(); // eat ")" 158 | return new FunctionCall(identifier, args); 159 | } else { 160 | return new Identifier(identifier); 161 | } 162 | }, 163 | parseVerify: function() { 164 | next(); // eat "verify" 165 | return new Verification(this.parseExpression()); 166 | } 167 | } 168 | } 169 | 170 | 171 | function lexer(text) { 172 | var tokenRegexp = /\/\/[^\n]*\n+|[A-Za-z_][A-Za-z_0-9]+|[0-9]+|'[^']*'|==|&&|\|\||<-|[+-><]|[{}()\.,=]/g; 173 | var match; 174 | var matches = []; 175 | while ((match = tokenRegexp.exec(text)) !== null) { 176 | if (match[0] && match[0].indexOf('//') === 0) { 177 | continue; 178 | } 179 | matches.push(match[0]); 180 | } 181 | var index = -1; 182 | return { 183 | next: function() { 184 | index++; 185 | return { 186 | value: matches[index], 187 | done: index >= matches.length - 1 188 | }; 189 | } 190 | }; 191 | } 192 | -------------------------------------------------------------------------------- /scripts/optimizations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014 Curiosity driven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var byteCodeOptimizations = { 18 | dropLastVerify: function(instructions) { 19 | return instructions.filter(function(element, index) { 20 | return !(element.opcode === "verify" && index === instructions.length - 1); 21 | }); 22 | }, 23 | 24 | optimizeDups: function(instructions) { 25 | return instructions.map(function(instruction) { 26 | return instruction.opcode === "pick" && instruction.index === 0 ? { 27 | opcode: "dup" 28 | } : instruction; 29 | }); 30 | }, 31 | 32 | optimizeDupCheckSig: function(instructions) { 33 | return replaceBytecode(instructions, [{ 34 | opcode: "dup" 35 | }, 36 | null, { 37 | opcode: "checkSig" 38 | } 39 | ], function onreplace(slice) { 40 | return [ 41 | slice[1], 42 | slice[2] 43 | ]; 44 | }); 45 | }, 46 | 47 | glueVerify: function(instructions, operators) { 48 | function canGlue(opcode) { 49 | return opcode in operators && operators[opcode].hasVerifyVersion 50 | } 51 | return instructions.reduce(function(previous, current) { 52 | if (current.opcode === "verify" && canGlue(previous[previous.length - 1].opcode)) { 53 | previous[previous.length - 1].opcode += "Verify"; 54 | previous[previous.length - 1].stackBalance = -2; 55 | return previous; 56 | } 57 | return previous.concat([current]); 58 | }, []); 59 | }, 60 | 61 | optimizeIfPick: function(instructions) { 62 | return replaceBytecode(instructions, [{ 63 | opcode: "if" 64 | }, { 65 | opcode: "pick" 66 | }, { 67 | opcode: "else" 68 | }, { 69 | opcode: "pick" 70 | }, { 71 | opcode: "endif" 72 | }], function onreplace(slice) { 73 | return [ 74 | slice[0], // if 75 | { 76 | opcode: slice[1].index 77 | }, // num from PICK 78 | slice[2], // else 79 | { 80 | opcode: slice[3].index 81 | }, // num from PICK 82 | slice[4], // endif 83 | { 84 | opcode: "pick" 85 | } 86 | ]; 87 | }); 88 | }, 89 | rewriteMutableAssignment: function(instructions) { 90 | return replaceBytecode(instructions, [{ 91 | pragma: "marker", 92 | mutable: "assignment" 93 | }, { 94 | opcode: "pick" 95 | }, 96 | null, 97 | null, { 98 | assume: "variable", 99 | mutable: "assignment" 100 | } 101 | ], function onreplace(slice) { 102 | var marker = slice[0], 103 | pick = slice[1], 104 | op1 = slice[2], 105 | op2 = slice[3], 106 | assignment = slice[4]; 107 | if (marker.name === pick.name && marker.name === assignment.name) { 108 | return [ 109 | op1, 110 | op2 111 | ]; 112 | } 113 | return slice; 114 | }); 115 | }, 116 | 117 | optimizeSize: function(instructions) { 118 | return replaceBytecode(instructions, [{ 119 | opcode: "pick" 120 | }, { 121 | opcode: "size" 122 | }, { 123 | opcode: "nip" 124 | }, { 125 | assume: "variable" 126 | }, { 127 | opcode: "pick" 128 | }, 129 | null, 130 | null, 131 | null, 132 | null, { 133 | opcode: "pick" 134 | } 135 | ], function onreplace(slice) { 136 | var pickS = slice[0], 137 | size = slice[1], 138 | nip = slice[2], 139 | assumeSize = slice[3], 140 | pickSize = slice[4], 141 | min = slice[5], 142 | max = slice[6], 143 | within = slice[7], 144 | verify = slice[8], 145 | pickS2 = slice[9]; 146 | if (pickS.name === pickS2.name && assumeSize.name === pickSize.name) { 147 | return [ 148 | pickS, 149 | assumeSize, 150 | size, { 151 | opcode: "tuck", 152 | stackBalance: 1 153 | }, 154 | min, 155 | max, 156 | within, 157 | verify 158 | // S will be left on stack 159 | ]; 160 | } 161 | return slice; 162 | }); 163 | }, 164 | 165 | // FIXME: this optimization changes the order of variables 166 | optimizeSizeTuckVariables: function(instructions) { 167 | return replaceBytecode(instructions, [{ 168 | opcode: "pick" 169 | }, { 170 | assume: "variable" 171 | }, { 172 | opcode: "size" 173 | }, { 174 | opcode: "tuck" 175 | }, ], function onreplace(slice) { 176 | var pick = slice[0], 177 | assume = slice[1], 178 | size = slice[2], 179 | tuck = slice[3]; 180 | if (pick.index === 0) { 181 | return [ 182 | assume, 183 | size, 184 | tuck 185 | ]; 186 | } else if (pick.index === 2) { 187 | return [{ 188 | opcode: "swap", 189 | stackBalance: 0 190 | }, 191 | assume, 192 | size, 193 | tuck 194 | ]; 195 | } else if (pick.index === 4) { 196 | return [{ 197 | opcode: "rot", 198 | stackBalance: 0 199 | }, 200 | assume, 201 | size, 202 | tuck 203 | ]; 204 | } else { 205 | return slice; 206 | } 207 | }); 208 | }, 209 | 210 | optimizeBoolPick: function(instructions) { 211 | return replaceBytecode(instructions, [{ 212 | opcode: "equal" 213 | }, // FIXME: can be any boolean not only equal 214 | { 215 | opcode: "if" 216 | }, { 217 | opcode: 1 218 | }, { 219 | opcode: "else" 220 | }, { 221 | opcode: 0 222 | }, { 223 | opcode: "endif" 224 | } 225 | ], function onreplace(slice) { 226 | return [ 227 | slice[0] // equal 228 | ]; 229 | }); 230 | }, 231 | 232 | optimizePickRollPick: function(instructions) { 233 | return replaceBytecode(instructions, [{ 234 | opcode: "pick" 235 | }, { 236 | opcode: "roll" 237 | }, { 238 | assume: "variable" 239 | }, { 240 | opcode: "pick" 241 | }], function onreplace(slice) { 242 | var pick1 = slice[0], 243 | roll = slice[1], 244 | assume = slice[2], 245 | pick2 = slice[3]; 246 | return [{ 247 | opcode: pick1.index, 248 | stackBalance: 1 249 | }, { 250 | opcode: "roll", 251 | stackBalance: -1 252 | }, 253 | roll, 254 | assume, { 255 | opcode: pick1.index, 256 | stackBalance: 1 257 | }, { 258 | opcode: "roll", 259 | stackBalance: -1 260 | }, 261 | ]; 262 | }); 263 | }, 264 | 265 | optimizeBoolAndOrPicks: function(instructions) { 266 | return replaceBytecode(instructions, [{ 267 | opcode: "pick", 268 | index: 0 269 | }, 270 | null, 271 | null, 272 | null, { 273 | opcode: "pick", 274 | index: 2 275 | }, 276 | null, 277 | null, { 278 | opcode: "boolOr" 279 | }, { 280 | opcode: "pick", 281 | index: 3 282 | }, 283 | null, 284 | null, { 285 | opcode: "boolAnd" 286 | }, 287 | ], function onreplace(slice) { 288 | var sha = slice[1], 289 | const1 = slice[2], 290 | equal = slice[3], 291 | key1 = slice[5], 292 | checkSig1 = slice[6], 293 | boolOr = slice[7], 294 | key2 = slice[9], 295 | checkSig2 = slice[10], 296 | boolAnd = slice[11]; 297 | return [ 298 | sha, 299 | const1, 300 | equal, { 301 | opcode: "swap", 302 | stackBalance: 0 303 | }, 304 | key2, // keys have swapped positions 305 | checkSig1, 306 | boolOr, { 307 | opcode: "swap", 308 | stackBalance: 0 309 | }, 310 | key1, 311 | checkSig2, 312 | boolAnd 313 | ]; 314 | }); 315 | }, 316 | optimizeLastCallPicks: function(instructions) { 317 | return replaceBytecode(instructions, [{ 318 | opcode: "pick", 319 | index: 1 320 | }, { 321 | opcode: "pick", 322 | index: 1 323 | }, { 324 | last: true, 325 | stackBalance: -1 326 | } // any opcode that takes two args 327 | ], function onreplace(slice) { 328 | return [ 329 | slice[2] // drop PICKs 330 | ]; 331 | }); 332 | }, 333 | 334 | optimizeLastCallSwap: function(instructions) { 335 | return replaceBytecode(instructions, [{ 336 | opcode: "pick", 337 | index: 1 338 | }, { 339 | last: true, 340 | stackBalance: -1 341 | } // any opcode that takes two args 342 | ], function onreplace(slice) { 343 | return [{ 344 | opcode: "swap", 345 | stackBalance: 0 346 | }, 347 | slice[1] // drop PICKs 348 | ]; 349 | }); 350 | }, 351 | 352 | optimizeTwoOp: function(instructions) { 353 | return replaceBytecode(instructions, [{ 354 | opcode: "pick", 355 | index: 1, 356 | last: true 357 | }, { 358 | opcode: "pick", 359 | index: 1, 360 | last: true 361 | }, 362 | null, { 363 | opcode: "pick", 364 | index: 3, 365 | last: true 366 | }, 367 | null 368 | ], function onreplace(slice) { 369 | var op1 = slice[2], 370 | op2 = slice[4]; 371 | return [ 372 | op1, 373 | op2 374 | ]; 375 | }); 376 | }, 377 | 378 | optimizeCheckSigs: function(instructions) { 379 | return replaceBytecode(instructions, [ 380 | // two signatures 381 | { 382 | assume: "variable" 383 | }, { 384 | assume: "variable" 385 | }, 386 | // check first 387 | { 388 | opcode: "pick" 389 | }, 390 | null, // key 391 | { 392 | opcode: "checkSig" 393 | }, { 394 | assume: "variable" 395 | }, 396 | // check second 397 | { 398 | opcode: "pick" 399 | }, 400 | null, // key 401 | { 402 | opcode: "checkSig" 403 | }, { 404 | assume: "variable" 405 | } 406 | ], function onreplace(slice) { 407 | var assumeSigA = slice[0], 408 | assumeSigB = slice[1], 409 | pickSigB = slice[2], 410 | keyB = slice[3], 411 | checkSigB = slice[4], 412 | assumeCheckB = slice[5], 413 | pickSigA = slice[6], 414 | keyA = slice[7], 415 | checkSigA = slice[8], 416 | assumeCheckA = slice[9]; 417 | if (assumeSigA.name !== pickSigA.name || assumeSigB.name !== pickSigB.name) { 418 | return slice; 419 | } 420 | return [ 421 | keyB, 422 | checkSigB, { 423 | assume: "variable", 424 | name: assumeCheckB.name, 425 | stackBalance: 1 426 | }, { 427 | opcode: "swap", 428 | stackBalance: 0 429 | }, 430 | keyA, 431 | checkSigA, { 432 | assume: "variable", 433 | name: assumeCheckA.name, 434 | stackBalance: 1 435 | }, { 436 | opcode: "swap", 437 | stackBalance: 0 438 | }, 439 | ]; 440 | }); 441 | }, 442 | markLastVariableUsage: function(instructions) { 443 | var variables = {}, 444 | instruction; 445 | for (var i = instructions.length - 1; instruction = instructions[i]; i--) { 446 | if (instruction.opcode === "pick") { 447 | if (!(instruction.name in variables)) { 448 | instruction.last = true; 449 | variables[instruction.name] = true; 450 | } 451 | } 452 | } 453 | return instructions; 454 | }, 455 | adjustPicks: function(instructions) { 456 | var stacks = [-1], 457 | declarations = {}; 458 | return instructions.map(function(instruction) { 459 | var result = null; 460 | if (instruction.opcode === "pick") { 461 | result = { 462 | opcode: "pick", 463 | index: stacks[stacks.length - 1] - declarations[instruction.name].stackIndex, 464 | name: instruction.name, 465 | stackBalance: 1 466 | }; 467 | } else if (instruction.opcode) { 468 | result = instruction; 469 | } 470 | 471 | if (instruction.stackBalance) { 472 | stacks[stacks.length - 1] += instruction.stackBalance; 473 | } 474 | 475 | if (instruction.assume) { 476 | result = {}; 477 | for (var item in instruction) { 478 | result[item] = instruction[item]; 479 | } 480 | result.stackIndex = stacks[stacks.length - 1] 481 | declarations[instruction.name] = result; 482 | } 483 | 484 | if (instruction.stack) { 485 | instruction.stack.split(",").forEach(function(stackInstruction) { 486 | if (stackInstruction === "push") { 487 | stacks.push(stacks[stacks.length - 1]); 488 | } else if (stackInstruction === "pop") { 489 | stacks.pop(); 490 | } else { 491 | throw new Error("Unknown stack instruction: " + stackInstruction); 492 | } 493 | }) 494 | } 495 | return result; 496 | }).filter(function(instruction) { 497 | return !!instruction; 498 | }); 499 | } 500 | 501 | }; 502 | --------------------------------------------------------------------------------