Interactive Learning Platform for Bitcoin Script Development
+
+
+
Bitcoin Script Playground
+
A visual stack-based learning tool for Bitcoin Script concepts.
+
+
+
+ Educational mode: this playground uses a simplified JavaScript interpreter. It is not a Bitcoin Core-compatible consensus engine and must not be used to validate real transactions.
+
+
+
+
+
Script Editor
+
+
+
+
+
+
+
-
-
-
Script Editor
-
-
-
-
-
-
-
+
+
Sample Scripts
+
+
+
+
+
-
-
Sample Scripts:
-
-
-
-
-
+
+
-
-
+
+
Stack Visualization
+
+
+
-
-
Stack Visualization
-
-
-
-
Stack will appear here when you run a script
-
-
-
-
-
-
-
Available Opcodes
-
-
-
-
-
+
+
Available Opcodes
+
+
+
-
+
From 057dfe4156da5505b4c4a1803029ae59dc225482 Mon Sep 17 00:00:00 2001
From: Giancarlo Vizhnay <75271806+polydeuces32@users.noreply.github.com>
Date: Fri, 1 May 2026 11:12:17 -0400
Subject: [PATCH 2/9] Add extracted stylesheet
---
styles.css | 1 +
1 file changed, 1 insertion(+)
create mode 100644 styles.css
diff --git a/styles.css b/styles.css
new file mode 100644
index 0000000..5b1ea27
--- /dev/null
+++ b/styles.css
@@ -0,0 +1 @@
+*{box-sizing:border-box}body{margin:0;font-family:Segoe UI,Tahoma,sans-serif;background:linear-gradient(135deg,#667eea,#764ba2);color:#333;min-height:100vh}.container{max-width:1400px;margin:0 auto;padding:20px}.header{text-align:center;color:#fff;margin-bottom:20px}.notice{background:#fff3cd;border:1px solid #ffe69c;padding:12px;border-radius:10px;margin-bottom:20px}.main-content{display:grid;grid-template-columns:1fr 1fr;gap:20px}.panel{background:#fff;border-radius:15px;padding:25px;box-shadow:0 10px 30px rgba(0,0,0,.15)}.script-input{width:100%;min-height:220px;padding:12px;border:2px solid #e2e8f0;border-radius:10px;font-family:monospace}.controls{display:flex;gap:10px;flex-wrap:wrap;margin:15px 0}.btn{border:none;border-radius:8px;padding:12px 18px;cursor:pointer;font-weight:600}.btn-primary{background:#667eea;color:#fff}.btn-secondary{background:#e2e8f0}.btn-success{background:#48bb78;color:#fff}.sample-btn{background:#ed8936;color:#fff;margin:5px 5px 0 0}.stack-container{min-height:320px;border:2px solid #e2e8f0;border-radius:10px;padding:15px;background:#f7fafc}.stack-item{background:#667eea;color:#fff;padding:10px 15px;margin:5px 0;border-radius:8px;display:flex;justify-content:space-between}.opcodes-panel{margin-top:20px}.opcodes-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}.opcode-card{background:#f7fafc;border:2px solid #e2e8f0;border-radius:10px;padding:12px}.status{padding:10px;border-radius:8px;margin-top:10px}.success{background:#c6f6d5}.error{background:#fed7d7}.sr-only{position:absolute;left:-9999px}@media(max-width:768px){.main-content{grid-template-columns:1fr}}
\ No newline at end of file
From 7577bf5cba22ee44e2fcd35f473b9f1b4931a7c4 Mon Sep 17 00:00:00 2001
From: Giancarlo Vizhnay <75271806+polydeuces32@users.noreply.github.com>
Date: Fri, 1 May 2026 11:12:38 -0400
Subject: [PATCH 3/9] Harden README claims and positioning
---
README.md | 196 +++++++-----------------------------------------------
1 file changed, 23 insertions(+), 173 deletions(-)
diff --git a/README.md b/README.md
index 0ccde34..4c1b2fa 100644
--- a/README.md
+++ b/README.md
@@ -1,192 +1,42 @@
-# 🪙 Bitcoin Script Playground
+# Bitcoin Script Playground
-> **Interactive Learning Platform for Bitcoin Script Development**
+A browser-based educational playground for learning Bitcoin Script fundamentals through opcode execution and stack visualization.
-A modern, web-based playground for learning and experimenting with Bitcoin Script. Built with vanilla JavaScript, this tool provides an intuitive interface for understanding Bitcoin's scripting language through hands-on practice.
+## Important Note
-
-
-
-
+This project uses a simplified JavaScript interpreter for learning purposes. It is **not** Bitcoin Core-compatible and should not be used to validate real transactions.
-## ✨ Features
+## Features
-### 🎯 **Interactive Script Editor**
-- Real-time script execution
-- Syntax highlighting and validation
-- Error handling with detailed messages
-- Keyboard shortcuts (Ctrl+Enter to run)
+- Interactive script editor
+- Visual stack rendering
+- Sample scripts
+- Shareable URLs
+- Opcode reference cards
+- Zero-build static site
-### 📚 **Comprehensive Opcode Library**
-- **30+ Bitcoin Script opcodes** implemented
-- Organized by categories: Stack, Arithmetic, Comparison, Bitwise, Logical, Constants, Verification
-- Click-to-insert opcode functionality
-- Detailed descriptions for each opcode
+## Quick Start
-### 🎨 **Visual Stack Management**
-- Real-time stack visualization
-- Animated stack operations
-- Color-coded stack items
-- Clear visual feedback
-
-### 🧩 **Sample Scripts & Puzzles**
-- Pre-built examples for different skill levels
-- Basic math operations
-- Comparison logic
-- Multi-signature examples
-- Advanced stack manipulation
-
-### 🔗 **Sharing & Collaboration**
-- Generate shareable URLs for scripts
-- Copy-to-clipboard functionality
-- Easy script distribution
-
-## 🚀 Quick Start
-
-### Option 1: Live Demo
-Simply open `index.html` in your web browser - no setup required!
-
-### Option 2: Local Development Server
```bash
-# Clone the repository
git clone https://github.com/polydeuces32/bitcoin-script-playground.git
cd bitcoin-script-playground
-
-# Start local server
python3 -m http.server 8000
-# or
-npx serve .
-
-# Open in browser
-open http://localhost:8000
-```
-
-## 📖 Usage Examples
-
-### Basic Arithmetic
-```bitcoin-script
-OP_1 OP_2 OP_ADD OP_DUP
-```
-**Result**: Stack contains `[3, 3]`
-
-### Comparison Logic
-```bitcoin-script
-OP_5 OP_3 OP_ADD OP_8 OP_EQUAL
-```
-**Result**: Stack contains `[1]` (true)
-
-### Stack Manipulation
-```bitcoin-script
-OP_1 OP_2 OP_3 OP_ROT OP_SWAP
```
-**Result**: Stack contains `[2, 1, 3]`
-
-## 🛠 Supported Opcodes
-
-### Stack Operations
-- `OP_DUP` - Duplicates the top stack item
-- `OP_2DUP` - Duplicates the top two stack items
-- `OP_DROP` - Removes the top stack item
-- `OP_SWAP` - Swaps the top two stack items
-- `OP_OVER` - Copies the second-to-top item to the top
-- `OP_ROT` - Rotates the top 3 stack items
-
-### Arithmetic Operations
-- `OP_ADD` - Adds the top two stack items
-- `OP_SUB` - Subtracts the second item from the first
-- `OP_MUL` - Multiplies the top two stack items
-- `OP_DIV` - Divides the second item by the first
-- `OP_MOD` - Returns the remainder of division
-
-### Comparison Operations
-- `OP_EQUAL` - Returns 1 if top two items are equal
-- `OP_EQUALVERIFY` - Same as OP_EQUAL but runs OP_VERIFY afterward
-- `OP_1EQUAL` - Returns 1 if input is 1, 0 otherwise
-- `OP_0NOTEQUAL` - Returns 1 if input is not 0, 0 otherwise
-
-### Bitwise Operations
-- `OP_AND` - Bitwise AND of the top two items
-- `OP_OR` - Bitwise OR of the top two items
-- `OP_XOR` - Bitwise XOR of the top two items
-- `OP_NOT` - Bitwise NOT of the top item
-
-### Logical Operations
-- `OP_BOOLAND` - Boolean AND of the top two items
-- `OP_BOOLOR` - Boolean OR of the top two items
-
-### Constants
-- `OP_0` through `OP_5` - Push numbers 0-5 onto stack
-
-### Verification
-- `OP_VERIFY` - Marks transaction as invalid if top stack value is not true
-- `OP_RETURN` - Marks transaction as invalid
-
-## 🎓 Educational Value
-
-This playground is perfect for:
-- **Bitcoin developers** learning script fundamentals
-- **Students** understanding stack-based programming
-- **Cryptocurrency enthusiasts** exploring Bitcoin's scripting capabilities
-- **Educators** teaching blockchain concepts
-
-## 🔮 Future Roadmap
-
-### Phase 1: Enhanced Features
-- [ ] More Bitcoin Script opcodes
-- [ ] Script validation against Bitcoin rules
-- [ ] Transaction simulation
-- [ ] Cost estimation
-
-### Phase 2: Advanced Tools
-- [ ] Multi-signature script builder
-- [ ] P2SH/P2WSH support
-- [ ] Real Bitcoin testnet integration
-- [ ] Script optimization suggestions
-
-### Phase 3: SaaS Platform
-- [ ] User accounts and authentication
-- [ ] Script library and sharing
-- [ ] Collaborative editing
-- [ ] API access for developers
-- [ ] Premium features and subscriptions
-
-## 🤝 Contributing
-
-We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for details.
-
-### Development Setup
-1. Fork the repository
-2. Create a feature branch: `git checkout -b feature/amazing-feature`
-3. Make your changes
-4. Test thoroughly
-5. Commit your changes: `git commit -m 'Add amazing feature'`
-6. Push to the branch: `git push origin feature/amazing-feature`
-7. Open a Pull Request
-
-## 📄 License
-
-This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
-
-## 🙏 Acknowledgments
-
-- Bitcoin Core developers for the original Bitcoin Script specification
-- The Bitcoin community for continuous innovation
-- Contributors and users who provide feedback and suggestions
-
-## 📞 Support
-- 🐛 **Bug Reports**: [GitHub Issues](https://github.com/polydeuces32/bitcoin-script-playground/issues)
-- 💡 **Feature Requests**: [GitHub Discussions](https://github.com/polydeuces32/bitcoin-script-playground/discussions)
-- 📧 **Contact**: [polydeuces32@github.com](mailto:polydeuces32@github.com)
+Open http://localhost:8000
----
+## Supported Scope
-
+This tool demonstrates common stack, arithmetic, comparison, and verification concepts. Some opcode behavior is intentionally simplified.
-**⭐ Star this repository if you find it helpful!**
+## Roadmap
-Made with ❤️ for the Bitcoin community
+- Pure engine/test separation
+- More accurate script semantics
+- Additional opcode coverage
+- Transaction templates
+- Testnet teaching mode
-[🌐 Live Demo](https://polydeuces32.github.io/bitcoin-script-playground) | [📖 Documentation](https://github.com/polydeuces32/bitcoin-script-playground/wiki) | [🐦 Twitter](https://twitter.com/polydeuces32)
+## License
-
A visual stack-based learning tool for Bitcoin Script concepts.
+
+
+
+
POLYDEUCES32 / BTC DEV LAB
+
Bitcoin Script Lab_
+
Trace Bitcoin Script concepts opcode by opcode with live stack state.
+
+
EDUCATIONAL SIMULATOR
-
- Educational mode: this playground uses a simplified JavaScript interpreter. It is not a Bitcoin Core-compatible consensus engine and must not be used to validate real transactions.
+
+ This lab uses a simplified JavaScript interpreter. It is not Bitcoin Core-compatible and must not be used to validate real transactions.
-
-
-
Script Editor
+
+
+
01 / SCRIPT INPUT
-
-
+
-
-
-
+
+
+
+
+
-
-
Sample Scripts
-
-
-
-
+
+
+
+
+
+
-
+
+
02 / EXECUTION TRACE
+
-
-
Stack Visualization
+
+
03 / STACK STATE
-
-
Available Opcodes
-
+
+
04 / OPCODE REFERENCE
+
From f75c492aa47b499f8ad47b5f1938fcba7dfeec05 Mon Sep 17 00:00:00 2001
From: Giancarlo Vizhnay <75271806+polydeuces32@users.noreply.github.com>
Date: Fri, 1 May 2026 11:21:53 -0400
Subject: [PATCH 7/9] Add terminal UI styles for Script Lab
---
styles.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/styles.css b/styles.css
index 5b1ea27..aa6a5c3 100644
--- a/styles.css
+++ b/styles.css
@@ -1 +1 @@
-*{box-sizing:border-box}body{margin:0;font-family:Segoe UI,Tahoma,sans-serif;background:linear-gradient(135deg,#667eea,#764ba2);color:#333;min-height:100vh}.container{max-width:1400px;margin:0 auto;padding:20px}.header{text-align:center;color:#fff;margin-bottom:20px}.notice{background:#fff3cd;border:1px solid #ffe69c;padding:12px;border-radius:10px;margin-bottom:20px}.main-content{display:grid;grid-template-columns:1fr 1fr;gap:20px}.panel{background:#fff;border-radius:15px;padding:25px;box-shadow:0 10px 30px rgba(0,0,0,.15)}.script-input{width:100%;min-height:220px;padding:12px;border:2px solid #e2e8f0;border-radius:10px;font-family:monospace}.controls{display:flex;gap:10px;flex-wrap:wrap;margin:15px 0}.btn{border:none;border-radius:8px;padding:12px 18px;cursor:pointer;font-weight:600}.btn-primary{background:#667eea;color:#fff}.btn-secondary{background:#e2e8f0}.btn-success{background:#48bb78;color:#fff}.sample-btn{background:#ed8936;color:#fff;margin:5px 5px 0 0}.stack-container{min-height:320px;border:2px solid #e2e8f0;border-radius:10px;padding:15px;background:#f7fafc}.stack-item{background:#667eea;color:#fff;padding:10px 15px;margin:5px 0;border-radius:8px;display:flex;justify-content:space-between}.opcodes-panel{margin-top:20px}.opcodes-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}.opcode-card{background:#f7fafc;border:2px solid #e2e8f0;border-radius:10px;padding:12px}.status{padding:10px;border-radius:8px;margin-top:10px}.success{background:#c6f6d5}.error{background:#fed7d7}.sr-only{position:absolute;left:-9999px}@media(max-width:768px){.main-content{grid-template-columns:1fr}}
\ No newline at end of file
+*{box-sizing:border-box}body{margin:0;background:#050505;color:#33ff66;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}button,textarea{font:inherit}.terminal-shell{max-width:1500px;margin:0 auto;padding:24px}.terminal-header{display:flex;justify-content:space-between;gap:20px;align-items:flex-start}.eyebrow{color:#ff9900;font-size:12px;margin:0 0 8px}.subtitle{color:#9ad8a7}.cursor{animation:blink 1s steps(1) infinite}.status-pill{border:1px solid #33ff66;padding:8px 12px}.notice,.panel{border:1px solid #143d1e;background:#081108}.notice{padding:12px;margin:18px 0}.lab-grid{display:grid;grid-template-columns:1.2fr 1fr .8fr;gap:16px}.panel{padding:16px}.panel-title{color:#ff9900;font-size:12px;margin-bottom:10px}.script-input{width:100%;min-height:220px;background:#000;color:#33ff66;border:1px solid #1f5a2d;padding:12px}.controls,.samples{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}.btn,.samples button,.opcode-card{background:#0c180c;color:#33ff66;border:1px solid #1f5a2d;padding:10px 12px;cursor:pointer}.btn.primary{border-color:#ff9900;color:#ff9900}.btn.ghost{color:#9ad8a7}.trace-container,.stack-container{min-height:340px;max-height:340px;overflow:auto;border:1px solid #143d1e;padding:10px;background:#000}.trace-row,.stack-item{padding:8px;border-bottom:1px solid #0f2c16}.trace-row.active{background:#102710}.trace-row.error{color:#ff6666}.opcodes-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px}.opcode-card{text-align:left}.status-line{margin-top:12px;color:#9ad8a7}.sr-only{position:absolute;left:-9999px}@keyframes blink{50%{opacity:0}}@media(max-width:1100px){.lab-grid{grid-template-columns:1fr}}
\ No newline at end of file
From c585481b83b4b74ec4844d5de34382327e1da4df Mon Sep 17 00:00:00 2001
From: Giancarlo Vizhnay <75271806+polydeuces32@users.noreply.github.com>
Date: Fri, 1 May 2026 11:23:56 -0400
Subject: [PATCH 8/9] Implement execution trace debugger
---
script.js | 443 ++++--------------------------------------------------
1 file changed, 31 insertions(+), 412 deletions(-)
diff --git a/script.js b/script.js
index 685c7e9..d246b4a 100644
--- a/script.js
+++ b/script.js
@@ -9,421 +9,40 @@ class BitcoinScriptEngine {
initializeOpcodes() {
return {
- OP_DUP: { description: 'Duplicate the top stack item', category: 'Stack', execute: () => this.dup() },
- OP_2DUP: { description: 'Duplicate the top two stack items', category: 'Stack', execute: () => this.dup2() },
- OP_DROP: { description: 'Remove the top stack item', category: 'Stack', execute: () => this.drop() },
- OP_SWAP: { description: 'Swap the top two stack items', category: 'Stack', execute: () => this.swap() },
- OP_OVER: { description: 'Copy the second-to-top item to the top', category: 'Stack', execute: () => this.over() },
- OP_ROT: { description: 'Rotate the top three stack items', category: 'Stack', execute: () => this.rot() },
-
- OP_ADD: { description: 'Add the top two numeric values', category: 'Arithmetic', execute: () => this.add() },
- OP_SUB: { description: 'Subtract the top value from the second value', category: 'Arithmetic', execute: () => this.sub() },
- OP_MUL: { description: 'Educational-only multiply operation', category: 'Arithmetic', execute: () => this.mul() },
- OP_DIV: { description: 'Educational-only integer division operation', category: 'Arithmetic', execute: () => this.div() },
- OP_MOD: { description: 'Educational-only modulo operation', category: 'Arithmetic', execute: () => this.mod() },
-
- OP_EQUAL: { description: 'Return 1 when the top two items are equal, else 0', category: 'Comparison', execute: () => this.equal() },
- OP_EQUALVERIFY: { description: 'Run OP_EQUAL followed by OP_VERIFY', category: 'Comparison', execute: () => this.equalVerify() },
- OP_1EQUAL: { description: 'Return 1 when the top item equals 1, else 0', category: 'Comparison', execute: () => this.oneEqual() },
- OP_0NOTEQUAL: { description: 'Return 1 when the top item is not 0, else 0', category: 'Comparison', execute: () => this.zeroNotEqual() },
-
- OP_AND: { description: 'Educational-only bitwise AND', category: 'Bitwise', execute: () => this.and() },
- OP_OR: { description: 'Educational-only bitwise OR', category: 'Bitwise', execute: () => this.or() },
- OP_XOR: { description: 'Educational-only bitwise XOR', category: 'Bitwise', execute: () => this.xor() },
- OP_NOT: { description: 'Return 1 for 0, otherwise 0', category: 'Logical', execute: () => this.not() },
- OP_BOOLAND: { description: 'Boolean AND of the top two stack items', category: 'Logical', execute: () => this.boolAnd() },
- OP_BOOLOR: { description: 'Boolean OR of the top two stack items', category: 'Logical', execute: () => this.boolOr() },
-
- OP_0: { description: 'Push 0 onto the stack', category: 'Constants', execute: () => this.push('0') },
- OP_1: { description: 'Push 1 onto the stack', category: 'Constants', execute: () => this.push('1') },
- OP_2: { description: 'Push 2 onto the stack', category: 'Constants', execute: () => this.push('2') },
- OP_3: { description: 'Push 3 onto the stack', category: 'Constants', execute: () => this.push('3') },
- OP_4: { description: 'Push 4 onto the stack', category: 'Constants', execute: () => this.push('4') },
- OP_5: { description: 'Push 5 onto the stack', category: 'Constants', execute: () => this.push('5') },
- OP_6: { description: 'Push 6 onto the stack', category: 'Constants', execute: () => this.push('6') },
- OP_7: { description: 'Push 7 onto the stack', category: 'Constants', execute: () => this.push('7') },
- OP_8: { description: 'Push 8 onto the stack', category: 'Constants', execute: () => this.push('8') },
- OP_9: { description: 'Push 9 onto the stack', category: 'Constants', execute: () => this.push('9') },
- OP_10: { description: 'Push 10 onto the stack', category: 'Constants', execute: () => this.push('10') },
-
- OP_VERIFY: { description: 'Fail unless the top item is true', category: 'Verification', execute: () => this.verify() },
- OP_RETURN: { description: 'Immediately fail execution', category: 'Verification', execute: () => this.returnOp() }
+ OP_DUP:{description:'Duplicate the top stack item',category:'Stack',execute:()=>this.dup()},OP_2DUP:{description:'Duplicate the top two stack items',category:'Stack',execute:()=>this.dup2()},OP_DROP:{description:'Remove the top stack item',category:'Stack',execute:()=>this.drop()},OP_SWAP:{description:'Swap the top two stack items',category:'Stack',execute:()=>this.swap()},OP_OVER:{description:'Copy the second-to-top item to the top',category:'Stack',execute:()=>this.over()},OP_ROT:{description:'Rotate the top three stack items',category:'Stack',execute:()=>this.rot()},
+ OP_ADD:{description:'Add the top two numeric values',category:'Arithmetic',execute:()=>this.add()},OP_SUB:{description:'Subtract top value from second value',category:'Arithmetic',execute:()=>this.sub()},OP_MUL:{description:'Educational-only multiply operation',category:'Arithmetic',execute:()=>this.mul()},OP_DIV:{description:'Educational-only integer division',category:'Arithmetic',execute:()=>this.div()},OP_MOD:{description:'Educational-only modulo',category:'Arithmetic',execute:()=>this.mod()},
+ OP_EQUAL:{description:'Return 1 when top two items match',category:'Comparison',execute:()=>this.equal()},OP_EQUALVERIFY:{description:'Run OP_EQUAL then OP_VERIFY',category:'Comparison',execute:()=>this.equalVerify()},OP_1EQUAL:{description:'Return 1 when top item equals 1',category:'Comparison',execute:()=>this.oneEqual()},OP_0NOTEQUAL:{description:'Return 1 when top item is not 0',category:'Comparison',execute:()=>this.zeroNotEqual()},
+ OP_AND:{description:'Educational-only bitwise AND',category:'Bitwise',execute:()=>this.and()},OP_OR:{description:'Educational-only bitwise OR',category:'Bitwise',execute:()=>this.or()},OP_XOR:{description:'Educational-only bitwise XOR',category:'Bitwise',execute:()=>this.xor()},OP_NOT:{description:'Return 1 for 0, otherwise 0',category:'Logical',execute:()=>this.not()},OP_BOOLAND:{description:'Boolean AND of top two items',category:'Logical',execute:()=>this.boolAnd()},OP_BOOLOR:{description:'Boolean OR of top two items',category:'Logical',execute:()=>this.boolOr()},
+ OP_0:{description:'Push 0',category:'Constants',execute:()=>this.push('0')},OP_1:{description:'Push 1',category:'Constants',execute:()=>this.push('1')},OP_2:{description:'Push 2',category:'Constants',execute:()=>this.push('2')},OP_3:{description:'Push 3',category:'Constants',execute:()=>this.push('3')},OP_4:{description:'Push 4',category:'Constants',execute:()=>this.push('4')},OP_5:{description:'Push 5',category:'Constants',execute:()=>this.push('5')},OP_6:{description:'Push 6',category:'Constants',execute:()=>this.push('6')},OP_7:{description:'Push 7',category:'Constants',execute:()=>this.push('7')},OP_8:{description:'Push 8',category:'Constants',execute:()=>this.push('8')},OP_9:{description:'Push 9',category:'Constants',execute:()=>this.push('9')},OP_10:{description:'Push 10',category:'Constants',execute:()=>this.push('10')},
+ OP_VERIFY:{description:'Fail unless top item is true',category:'Verification',execute:()=>this.verify()},OP_RETURN:{description:'Immediately fail execution',category:'Verification',execute:()=>this.returnOp()}
};
}
- initializeSampleScripts() {
- return {
- basic: 'OP_1 OP_2 OP_ADD OP_DUP',
- comparison: 'OP_5 OP_3 OP_ADD OP_8 OP_EQUAL',
- stack: 'OP_1 OP_2 OP_3 OP_ROT OP_SWAP',
- verify: 'OP_2 OP_3 OP_ADD OP_5 OP_EQUALVERIFY OP_1'
- };
- }
-
- run(script) {
- const tokens = this.tokenize(script);
- this.stack = [];
-
- tokens.forEach((token, index) => {
- try {
- if (this.opcodes[token]) {
- this.opcodes[token].execute();
- return;
- }
-
- if (this.isLiteral(token)) {
- this.push(token);
- return;
- }
-
- throw new Error(`Unsupported token: ${token}`);
- } catch (error) {
- throw new Error(`Token ${index + 1} (${token}): ${error.message}`);
- }
- });
-
- return [...this.stack];
- }
-
- tokenize(script) {
- return script.trim().split(/\s+/).filter(Boolean);
- }
-
- isLiteral(token) {
- return /^-?\d+$/.test(token);
- }
-
- push(value) {
- this.stack.push(String(value));
- }
-
- requireStackSize(size, message = 'Not enough items on stack') {
- if (this.stack.length < size) throw new Error(message);
- }
-
- popNumber() {
- this.requireStackSize(1, 'Stack is empty');
- const value = this.stack.pop();
- const number = Number.parseInt(value, 10);
- if (!Number.isSafeInteger(number)) throw new Error(`Invalid number: ${value}`);
- return number;
- }
-
- isTrue(value) {
- return value !== '0' && value !== '';
- }
-
- dup() {
- this.requireStackSize(1, 'Stack is empty');
- this.push(this.stack[this.stack.length - 1]);
- }
-
- dup2() {
- this.requireStackSize(2);
- const second = this.stack[this.stack.length - 2];
- const top = this.stack[this.stack.length - 1];
- this.push(second);
- this.push(top);
- }
-
- drop() {
- this.requireStackSize(1, 'Stack is empty');
- this.stack.pop();
- }
-
- swap() {
- this.requireStackSize(2);
- const top = this.stack.pop();
- const second = this.stack.pop();
- this.push(top);
- this.push(second);
- }
-
- over() {
- this.requireStackSize(2);
- this.push(this.stack[this.stack.length - 2]);
- }
-
- rot() {
- this.requireStackSize(3);
- const top = this.stack.pop();
- const second = this.stack.pop();
- const third = this.stack.pop();
- this.push(second);
- this.push(top);
- this.push(third);
- }
-
- add() {
- const b = this.popNumber();
- const a = this.popNumber();
- this.push(a + b);
- }
-
- sub() {
- const b = this.popNumber();
- const a = this.popNumber();
- this.push(a - b);
- }
-
- mul() {
- const b = this.popNumber();
- const a = this.popNumber();
- this.push(a * b);
- }
-
- div() {
- const b = this.popNumber();
- const a = this.popNumber();
- if (b === 0) throw new Error('Division by zero');
- this.push(Math.trunc(a / b));
- }
-
- mod() {
- const b = this.popNumber();
- const a = this.popNumber();
- if (b === 0) throw new Error('Modulo by zero');
- this.push(a % b);
- }
-
- equal() {
- this.requireStackSize(2);
- const b = this.stack.pop();
- const a = this.stack.pop();
- this.push(a === b ? '1' : '0');
- }
-
- equalVerify() {
- this.equal();
- this.verify();
- }
-
- oneEqual() {
- this.requireStackSize(1, 'Stack is empty');
- this.push(this.stack.pop() === '1' ? '1' : '0');
- }
-
- zeroNotEqual() {
- this.requireStackSize(1, 'Stack is empty');
- this.push(this.stack.pop() !== '0' ? '1' : '0');
- }
-
- and() {
- const b = this.popNumber();
- const a = this.popNumber();
- this.push(a & b);
- }
-
- or() {
- const b = this.popNumber();
- const a = this.popNumber();
- this.push(a | b);
- }
-
- xor() {
- const b = this.popNumber();
- const a = this.popNumber();
- this.push(a ^ b);
- }
-
- not() {
- const value = this.popNumber();
- this.push(value === 0 ? '1' : '0');
- }
-
- boolAnd() {
- const b = this.stack.pop();
- const a = this.stack.pop();
- if (a === undefined || b === undefined) throw new Error('Not enough items on stack');
- this.push(this.isTrue(a) && this.isTrue(b) ? '1' : '0');
- }
-
- boolOr() {
- const b = this.stack.pop();
- const a = this.stack.pop();
- if (a === undefined || b === undefined) throw new Error('Not enough items on stack');
- this.push(this.isTrue(a) || this.isTrue(b) ? '1' : '0');
- }
-
- verify() {
- this.requireStackSize(1, 'Stack is empty');
- const value = this.stack.pop();
- if (!this.isTrue(value)) throw new Error('Verification failed');
- }
-
- returnOp() {
- throw new Error('OP_RETURN failed execution');
- }
+ initializeSampleScripts(){return{basic:'OP_1 OP_2 OP_ADD OP_DUP',comparison:'OP_5 OP_3 OP_ADD OP_8 OP_EQUAL',stack:'OP_1 OP_2 OP_3 OP_ROT OP_SWAP',verify:'OP_2 OP_3 OP_ADD OP_5 OP_EQUALVERIFY OP_1'};}
+ tokenize(script){return script.trim().split(/\s+/).filter(Boolean);} isLiteral(t){return/^-?\d+$/.test(t);} push(v){this.stack.push(String(v));}
+ requireStackSize(n,m='Not enough items on stack'){if(this.stack.length this.runScript());
- document.querySelector('[data-action="clear"]').addEventListener('click', () => this.clearScript());
- document.querySelector('[data-action="share"]').addEventListener('click', () => this.shareScript());
-
- document.querySelectorAll('[data-sample]').forEach((button) => {
- button.addEventListener('click', () => this.loadSample(button.dataset.sample));
- });
-
- document.addEventListener('keydown', (event) => {
- if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
- event.preventDefault();
- this.runScript();
- }
- });
- }
-
- runScript() {
- const script = this.scriptInput.value.trim();
- if (!script) {
- this.showStatus('Please enter a script to run.', 'error');
- return;
- }
-
- try {
- const stack = this.engine.run(script);
- this.renderStack(stack);
- this.showStatus(`Script executed successfully. Stack items: ${stack.length}.`, 'success');
- } catch (error) {
- this.renderStack(this.engine.stack);
- this.showStatus(error.message, 'error');
- }
- }
-
- clearScript() {
- this.scriptInput.value = '';
- this.engine.stack = [];
- this.renderStack();
- this.clearStatus();
- }
-
- async shareScript() {
- const script = this.scriptInput.value.trim();
- if (!script) {
- this.showStatus('No script to share.', 'error');
- return;
- }
-
- const url = new URL(window.location.href);
- url.searchParams.set('script', script);
-
- try {
- await navigator.clipboard.writeText(url.toString());
- this.showStatus('Share URL copied to clipboard.', 'success');
- } catch {
- this.showStatus('Clipboard unavailable. Copy the URL from the address bar after sharing.', 'error');
- window.history.replaceState(null, '', url.toString());
- }
- }
-
- loadSample(type) {
- const script = this.engine.sampleScripts[type];
- if (!script) return;
- this.scriptInput.value = script;
- this.showStatus(`Loaded sample: ${type}.`, 'success');
- }
-
- loadScriptFromUrl() {
- const params = new URLSearchParams(window.location.search);
- const script = params.get('script');
- if (!script) return;
- this.scriptInput.value = script;
- this.runScript();
- }
-
- renderOpcodes() {
- this.opcodesGrid.textContent = '';
- const categories = [...new Set(Object.values(this.engine.opcodes).map((opcode) => opcode.category))];
-
- categories.forEach((category) => {
- const title = document.createElement('h3');
- title.textContent = category;
- title.style.gridColumn = '1 / -1';
- this.opcodesGrid.appendChild(title);
-
- Object.entries(this.engine.opcodes)
- .filter(([, opcode]) => opcode.category === category)
- .forEach(([name, opcode]) => {
- const card = document.createElement('button');
- card.type = 'button';
- card.className = 'opcode-card';
- card.addEventListener('click', () => this.insertOpcode(name));
-
- const opcodeName = document.createElement('div');
- opcodeName.className = 'opcode-name';
- opcodeName.textContent = name;
-
- const description = document.createElement('div');
- description.className = 'opcode-desc';
- description.textContent = opcode.description;
-
- card.appendChild(opcodeName);
- card.appendChild(description);
- this.opcodesGrid.appendChild(card);
- });
- });
- }
-
- insertOpcode(opcodeName) {
- const existing = this.scriptInput.value.trim();
- this.scriptInput.value = existing ? `${existing} ${opcodeName}` : opcodeName;
- this.scriptInput.focus();
- }
-
- renderStack(stack = this.engine.stack) {
- this.stackContainer.textContent = '';
-
- if (!stack || stack.length === 0) {
- const empty = document.createElement('p');
- empty.className = 'empty-stack';
- empty.textContent = 'Stack will appear here when you run a script.';
- this.stackContainer.appendChild(empty);
- return;
- }
-
- stack.forEach((item, index) => {
- const row = document.createElement('div');
- row.className = 'stack-item';
-
- const value = document.createElement('span');
- value.textContent = item;
-
- const label = document.createElement('span');
- label.textContent = `#${stack.length - index}`;
-
- row.appendChild(value);
- row.appendChild(label);
- this.stackContainer.appendChild(row);
- });
- }
-
- showStatus(message, type) {
- this.status.textContent = '';
- const box = document.createElement('div');
- box.className = `status ${type}`;
- box.textContent = message;
- this.status.appendChild(box);
- }
-
- clearStatus() {
- this.status.textContent = '';
- }
+class PlaygroundUI{
+ constructor(engine){this.engine=engine;this.trace=[];this.activeStep=-1;this.scriptInput=document.getElementById('scriptInput');this.stackContainer=document.getElementById('stackContainer');this.traceContainer=document.getElementById('traceContainer');this.opcodesGrid=document.getElementById('opcodesGrid');this.status=document.getElementById('status');this.bindEvents();this.renderOpcodes();this.resetTrace();this.loadScriptFromUrl();}
+ bindEvents(){document.querySelector('[data-action="run"]').addEventListener('click',()=>this.runAll());document.querySelector('[data-action="step"]').addEventListener('click',()=>this.step());document.querySelector('[data-action="reset"]').addEventListener('click',()=>this.resetTrace());document.querySelector('[data-action="clear"]').addEventListener('click',()=>this.clearScript());document.querySelector('[data-action="share"]').addEventListener('click',()=>this.shareScript());document.querySelectorAll('[data-sample]').forEach(b=>b.addEventListener('click',()=>this.loadSample(b.dataset.sample)));document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='Enter'){e.preventDefault();this.runAll();}});}
+ compileTrace(){const script=this.scriptInput.value.trim();if(!script){this.showStatus('Enter a script first.','error');return false;}this.trace=this.engine.buildTrace(script);this.activeStep=-1;return true;}
+ runAll(){if(!this.compileTrace())return;this.activeStep=this.trace.length-1;this.renderTrace();this.renderStack(this.trace.at(-1)?.after||[]);const failed=this.trace.find(t=>!t.ok);this.showStatus(failed?`Execution stopped: ${failed.error}`:`Execution complete. Steps: ${this.trace.length}.`,failed?'error':'success');}
+ step(){if(this.trace.length===0&&!this.compileTrace())return;if(this.activeStep{const row=document.createElement('div');row.className=`trace-row${i===this.activeStep?' active':''}${!t.ok?' error':''}`;const main=document.createElement('div');main.textContent=`${String(t.index+1).padStart(2,'0')} > ${t.token}`;const before=document.createElement('small');before.textContent=`before [${t.before.join(', ')}] -> after [${t.after.join(', ')}]`;row.append(main,before);if(t.error){const err=document.createElement('small');err.textContent=`error: ${t.error}`;row.appendChild(err);}this.traceContainer.appendChild(row);});}
+ renderStack(stack=[]){this.stackContainer.textContent='';if(stack.length===0){const p=document.createElement('p');p.textContent='[] empty stack';this.stackContainer.appendChild(p);return;}stack.forEach((item,i)=>{const row=document.createElement('div');row.className='stack-item';const v=document.createElement('span');v.textContent=item;const l=document.createElement('span');l.textContent=`#${stack.length-i}`;row.append(v,l);this.stackContainer.appendChild(row);});}
+ renderOpcodes(){this.opcodesGrid.textContent='';const cats=[...new Set(Object.values(this.engine.opcodes).map(o=>o.category))];cats.forEach(c=>{const h=document.createElement('h3');h.textContent=c;h.style.gridColumn='1/-1';this.opcodesGrid.appendChild(h);Object.entries(this.engine.opcodes).filter(([,o])=>o.category===c).forEach(([name,o])=>{const card=document.createElement('button');card.type='button';card.className='opcode-card';card.addEventListener('click',()=>this.insertOpcode(name));const n=document.createElement('div');n.textContent=name;const d=document.createElement('small');d.textContent=o.description;card.append(n,d);this.opcodesGrid.appendChild(card);});});}
+ insertOpcode(name){const current=this.scriptInput.value.trim();this.scriptInput.value=current?`${current} ${name}`:name;this.resetTrace();this.scriptInput.focus();}
+ showStatus(msg,type){this.status.textContent=`${type.toUpperCase()}: ${msg}`;} clearStatus(){this.status.textContent='';}
}
-
-window.addEventListener('DOMContentLoaded', () => {
- new PlaygroundUI(new BitcoinScriptEngine());
-});
+window.addEventListener('DOMContentLoaded',()=>new PlaygroundUI(new BitcoinScriptEngine()));
From cb4a1f732cdd77937a6a38bdf96344c9e2561324 Mon Sep 17 00:00:00 2001
From: Giancarlo Vizhnay <75271806+polydeuces32@users.noreply.github.com>
Date: Fri, 1 May 2026 11:25:30 -0400
Subject: [PATCH 9/9] Prepare Bitcoin Script Lab v2 launch README
---
README.md | 58 +++++++++++++++++++++++++++++++++++++------------------
1 file changed, 39 insertions(+), 19 deletions(-)
diff --git a/README.md b/README.md
index 4c1b2fa..87511b4 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,20 @@
-# Bitcoin Script Playground
+# Bitcoin Script Lab
-A browser-based educational playground for learning Bitcoin Script fundamentals through opcode execution and stack visualization.
+A terminal-style educational lab for learning Bitcoin Script concepts through opcode-by-opcode execution tracing and live stack visualization.
-## Important Note
+## v2 Highlights
-This project uses a simplified JavaScript interpreter for learning purposes. It is **not** Bitcoin Core-compatible and should not be used to validate real transactions.
+- Terminal-inspired Bitcoin developer UI
+- Step-by-step execution trace
+- Run All / Step / Reset Trace controls
+- Live stack state panel
+- Opcode reference panel
+- Shareable scripts through URL parameters
+- Zero-build static web app
-## Features
+## Important Scope Note
-- Interactive script editor
-- Visual stack rendering
-- Sample scripts
-- Shareable URLs
-- Opcode reference cards
-- Zero-build static site
+Bitcoin Script Lab uses a simplified JavaScript interpreter for learning purposes. It is **not** Bitcoin Core-compatible and must not be used to validate real transactions.
## Quick Start
@@ -23,19 +24,38 @@ cd bitcoin-script-playground
python3 -m http.server 8000
```
-Open http://localhost:8000
+Open:
-## Supported Scope
+```text
+http://localhost:8000
+```
+
+## Example Script
+
+```text
+OP_1 OP_2 OP_ADD OP_DUP
+```
+
+Trace result:
+
+```text
+01 > OP_1 [] -> [1]
+02 > OP_2 [1] -> [1, 2]
+03 > OP_ADD [1, 2] -> [3]
+04 > OP_DUP [3] -> [3, 3]
+```
+
+## Why This Exists
-This tool demonstrates common stack, arithmetic, comparison, and verification concepts. Some opcode behavior is intentionally simplified.
+Bitcoin Script is stack-based and difficult to understand from static documentation alone. This project makes the stack transitions visible so beginners can learn by running and stepping through scripts.
## Roadmap
-- Pure engine/test separation
-- More accurate script semantics
-- Additional opcode coverage
-- Transaction templates
-- Testnet teaching mode
+- Animated stack transitions
+- Real transaction template examples
+- Hashlock and timelock teaching modes
+- Miniscript learning mode
+- Better automated tests
## License