├── .editorconfig ├── .gitignore ├── Gruntfile.js ├── LICENSE ├── README.md ├── app.css ├── app.html ├── app.js ├── bitcoin-networks.js ├── browserify.cmd ├── browserify.js ├── favicon.ico ├── images └── fork-me-on-github-ribbon.png ├── index.html ├── libs ├── css │ ├── bootstrap-horizon.css │ ├── bootstrap-theme.min.css │ ├── bootstrap.min.css │ ├── ie10-viewport-bug-workaround.css │ └── ladda-themeless.min.css ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── js │ ├── angular-route.js │ ├── angular.min.js │ ├── bitcoin.js │ ├── bootstrap.min.js │ ├── d3.v3.min.js │ ├── ie10-viewport-bug-workaround.js │ ├── jquery.min.js │ ├── ladda.min.js │ ├── lodash.js │ ├── moment.min.js │ ├── qrcode.min.js │ ├── sha256.js │ └── spin.min.js └── templates.js ├── package.json ├── pages ├── aezeed │ ├── aezeed.html │ └── aezeed.js ├── bitcoin-block │ ├── bitcoin-block.html │ └── bitcoin-block.js ├── ecc │ ├── ecc.html │ └── ecc.js ├── encoding-decoding │ ├── encoding-decoding.html │ └── encoding-decoding.js ├── hd-wallet │ ├── hd-wallet.html │ └── hd-wallet.js ├── intro │ ├── intro.html │ └── intro.js ├── macaroon │ ├── id-protobuf.js │ ├── id.proto │ ├── macaroon.html │ └── macaroon.js ├── mu-sig │ ├── mu-sig.html │ └── mu-sig.js ├── schnorr │ ├── schnorr.html │ └── schnorr.js ├── shamir-secret-sharing │ ├── shamir-secret-sharing.html │ └── shamir-secret-sharing.js ├── transaction-creator │ ├── transaction-creator.html │ └── transaction-creator.js └── wallet-import │ ├── wallet-import.html │ └── wallet-import.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | max_line_length = 140 13 | 14 | [*.{diff,md}] 15 | insert_final_newline = false 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | yarn-error.log 4 | release 5 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | ngtemplates: { 4 | app: { 5 | src: '**/*.html', 6 | dest: 'libs/templates.js' 7 | } 8 | } 9 | }); 10 | 11 | grunt.loadNpmTasks('grunt-angular-templates'); 12 | }; 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Oliver Gugger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cryptography Toolkit 2 | 3 | A web-based collection of cryptography tools for schemes/algorithms used in 4 | [Bitcoin](https://github.com/bitcoin/bitcoin) and [LND](https://github.com/lightningnetwork/lnd). 5 | 6 | **This toolkit has been built with educational purposes in mind!** 7 | It is meant to play around with different schemes and algorithms to understand how they work. 8 | However, you must be **extremely careful** when using real/live/mainnet data/keys/credentials! 9 | A web browser usually is not a safe environment to either create strong cryptographic keys and/or 10 | paste sensitive information into. So consider yourself warned. 11 | 12 | [Live version](https://guggero.github.io/cryptography-toolkit/) 13 | 14 | ## Tools 15 | * [Elliptic Curve Cryptography / Key Pair page](https://guggero.github.io/cryptography-toolkit/#!/ecc) 16 | * [Hierarchical Deterministic Wallet page](https://guggero.github.io/cryptography-toolkit/#!/hd-wallet) 17 | * [Bitcoin Block Parser page](https://guggero.github.io/cryptography-toolkit/#!/bitcoin-block) 18 | * [Shamir's Secret Sharing Scheme page](https://guggero.github.io/cryptography-toolkit/#!/shamir-secret-sharing) 19 | * [BIP Schnorr Signatures page](https://guggero.github.io/cryptography-toolkit/#!/schnorr) 20 | * [MuSig: Key Aggregation for Schnorr Signatures page](https://guggero.github.io/cryptography-toolkit/#!/mu-sig) 21 | * [Transaction Creator page](https://guggero.github.io/cryptography-toolkit/#!/transaction-creator) 22 | * [aezeed Cipher Seed Scheme page](https://guggero.github.io/cryptography-toolkit/#!/aezeed) 23 | * [Macaroons page](https://guggero.github.io/cryptography-toolkit/#!/macaroon) 24 | * [Wallet Import helper page](https://guggero.github.io/cryptography-toolkit/#!/wallet-import) 25 | 26 | ## Send Thanks 27 | 28 | Created by [Oliver Gugger](https://github.com/guggero): 29 | * BTC tip address: `bc1qfgua5vhwm6myajak9p4crhwmwm2k6mczf789eh` 30 | -------------------------------------------------------------------------------- /app.css: -------------------------------------------------------------------------------- 1 | html { 2 | position: relative; 3 | min-height: 100%; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | font-family: 'Raleway', sans-serif; 9 | font-size: 16px; 10 | font-weight: 400; 11 | color: #888; 12 | line-height: 18px; 13 | } 14 | 15 | body app > .container { 16 | padding: 60px 15px 0; 17 | } 18 | 19 | .container .text-muted { 20 | margin: 20px 0; 21 | } 22 | 23 | body app > .container-fluid { 24 | padding: 60px 15px 0; 25 | } 26 | 27 | .container-fluid .text-muted { 28 | margin: 20px 0; 29 | } 30 | 31 | input, textarea, pre, .form-control { 32 | font-family: monospace; 33 | font-size: 13px; 34 | } 35 | 36 | block .well-sm { 37 | font-size: 14px; 38 | font-weight: 300; 39 | } 40 | 41 | .panel .panel-body, intro-page { 42 | line-height: 24px; 43 | } 44 | 45 | .well-success { 46 | transition: background 0.6s; 47 | background: rgba(147, 255, 75, 0.27); 48 | } 49 | 50 | .well-error { 51 | transition: background 0.6s; 52 | background: rgba(238, 4, 30, 0.21); 53 | } 54 | 55 | .github-ribbon { 56 | position: absolute; 57 | top: 0; 58 | left: 0; 59 | border: 0; 60 | z-index: 20000; 61 | } 62 | 63 | .navbar-text { 64 | margin-top: 10px; 65 | margin-bottom: 8px; 66 | } 67 | 68 | h1 chain-info, h3 peer-info { 69 | font-size: 14px; 70 | font-family: monospace; 71 | padding-left: 5px; 72 | } 73 | 74 | strong { 75 | font-weight: 700; 76 | } 77 | 78 | a, a:hover, a:focus { 79 | color: #89cc2d; 80 | text-decoration: none; 81 | -o-transition: all .6s; 82 | -moz-transition: all .6s; 83 | -webkit-transition: all .6s; 84 | -ms-transition: all .6s; 85 | transition: all .6s; 86 | cursor: pointer; 87 | } 88 | 89 | h1, h2 { 90 | margin-top: 10px; 91 | font-size: 36px; 92 | font-weight: 700; 93 | color: #5a4d45; 94 | line-height: 44px; 95 | } 96 | 97 | h3 { 98 | font-size: 22px; 99 | font-weight: 400; 100 | color: #5a4d45; 101 | line-height: 30px; 102 | } 103 | 104 | img { 105 | max-width: 100%; 106 | } 107 | 108 | .medium-paragraph { 109 | font-size: 18px; 110 | line-height: 32px; 111 | } 112 | 113 | ::-moz-selection { 114 | background: #89cc2d; 115 | color: #fff; 116 | text-shadow: none; 117 | } 118 | 119 | ::selection { 120 | background: #89cc2d; 121 | color: #fff; 122 | text-shadow: none; 123 | } 124 | 125 | .navbar { 126 | margin-bottom: 0; 127 | background: #5a4d45; 128 | border: 0; 129 | -moz-border-radius: 0; 130 | -webkit-border-radius: 0; 131 | border-radius: 0; 132 | -o-transition: all .6s; 133 | -moz-transition: all .6s; 134 | -webkit-transition: all .6s; 135 | -ms-transition: all .6s; 136 | transition: all .6s; 137 | } 138 | 139 | ul.navbar-nav { 140 | font-size: 15px; 141 | color: #fff; 142 | } 143 | 144 | .navbar-inverse ul.navbar-nav li a { 145 | color: #fff; 146 | opacity: 0.8; 147 | border: 0; 148 | } 149 | 150 | .navbar-inverse ul.navbar-nav li a:hover { 151 | color: #fff; 152 | opacity: 1; 153 | border: 0; 154 | } 155 | 156 | .navbar-inverse ul.navbar-nav li a:focus { 157 | color: #fff; 158 | outline: 0; 159 | opacity: 1; 160 | border: 0; 161 | } 162 | 163 | .dropdown-menu { 164 | background-color: #5a4d45; 165 | } 166 | 167 | .dropdown-menu > li > a:hover, 168 | .dropdown-menu > .active > a, 169 | .dropdown-menu > .active > a:hover { 170 | color: #fff; 171 | background: #000; 172 | } 173 | 174 | :not(.input-group-btn) > button.btn { 175 | height: 40px; 176 | margin: 0; 177 | padding: 0 15px; 178 | vertical-align: middle; 179 | background: #89cc2d; 180 | border: 0; 181 | font-family: 'Raleway', sans-serif; 182 | font-size: 15px; 183 | font-weight: 400; 184 | line-height: 40px; 185 | color: #fff; 186 | -moz-border-radius: 0; 187 | -webkit-border-radius: 0; 188 | border-radius: 0; 189 | text-shadow: none; 190 | -moz-box-shadow: none; 191 | -webkit-box-shadow: none; 192 | box-shadow: none; 193 | -o-transition: all .6s; 194 | -moz-transition: all .6s; 195 | -webkit-transition: all .6s; 196 | -ms-transition: all .6s; 197 | transition: all .6s; 198 | } 199 | 200 | .input-group-btn button.btn { 201 | font-family: 'Raleway', sans-serif; 202 | background: #89cc2d; 203 | color: #fff; 204 | border: 1px solid #89cc2d; 205 | text-shadow: none; 206 | -moz-box-shadow: none; 207 | -webkit-box-shadow: none; 208 | box-shadow: none; 209 | -o-transition: all .6s; 210 | -moz-transition: all .6s; 211 | -webkit-transition: all .6s; 212 | -ms-transition: all .6s; 213 | transition: all .6s; 214 | } 215 | 216 | .input-group[class*=col-] { 217 | padding-right: 15px; 218 | } 219 | 220 | span.input-group-addon { 221 | font-size: 13px; 222 | } 223 | 224 | .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control, fieldset[readonly] .form-control { 225 | cursor: text; 226 | } 227 | 228 | button.btn:hover { 229 | opacity: 0.6; 230 | color: #fff; 231 | } 232 | 233 | button.btn:active { 234 | outline: 0; 235 | opacity: 0.6; 236 | color: #fff; 237 | -moz-box-shadow: none; 238 | -webkit-box-shadow: none; 239 | box-shadow: none; 240 | } 241 | 242 | button.btn:focus { 243 | outline: 0; 244 | opacity: 0.6; 245 | background: #89cc2d; 246 | color: #fff; 247 | } 248 | 249 | button.btn:active:focus, button.btn.active:focus { 250 | outline: 0; 251 | opacity: 0.6; 252 | background: #89cc2d; 253 | color: #fff; 254 | } 255 | 256 | button i { 257 | padding-left: 5px; 258 | vertical-align: middle; 259 | font-size: 20px; 260 | line-height: 20px; 261 | } 262 | 263 | .no-left-padding { 264 | padding-left: 0; 265 | } 266 | 267 | svg .link { 268 | fill: none; 269 | stroke: #ccc; 270 | stroke-width: 1.5px; 271 | } 272 | 273 | div.tooltip { 274 | position: absolute; 275 | min-width: 400px; 276 | min-height: 50px; 277 | pointer-events: none; 278 | } 279 | 280 | hr { 281 | color: black; 282 | border-bottom: 1px solid black; 283 | } 284 | 285 | div.as-block.input-group { 286 | display: inline-block; 287 | float: left; 288 | } 289 | 290 | div.as-block.input-group span { 291 | line-height: 20px; 292 | } 293 | 294 | div.as-block.input-group input, 295 | div.as-block.input-group span, 296 | div.as-block.input-group select.form-control { 297 | display: inline-block; 298 | float: left; 299 | } 300 | -------------------------------------------------------------------------------- /app.html: -------------------------------------------------------------------------------- 1 | 70 | 72 | Fork me on GitHub 74 | 75 |
76 | 77 |
78 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | angular.element(document.body).ready(function () { 2 | angular.bootstrap(document.body, ['app']) 3 | }); 4 | 5 | angular 6 | .module('app', [ 7 | 'ngRoute' 8 | ]) 9 | .constant('moment', window.moment) 10 | .constant('lodash', window._) 11 | .constant('allNetworks', window.allNetworks) 12 | .constant('bitcoinNetworks', window.bitcoinNetworks) 13 | .constant('bitcoin', window.bitcoin) 14 | .constant('Buffer', window.bitcoin.Buffer) 15 | .filter('ago', function (moment) { 16 | return function (input) { 17 | var duration = moment.duration(moment().diff(moment(input))); 18 | return duration.humanize() 19 | } 20 | }) 21 | .component('app', { 22 | templateUrl: 'app.html' 23 | }) 24 | .config(routeConfig) 25 | .run(run); 26 | 27 | function routeConfig($locationProvider, $routeProvider) { 28 | $locationProvider.hashPrefix('!'); 29 | $locationProvider.html5Mode({enabled: false, requireBase: false}); 30 | 31 | $routeProvider 32 | .when('/', {template: '', containerClass: 'container'}) 33 | .when('/ecc', {template: '', containerClass: 'container'}) 34 | .when('/hd-wallet', {template: '', containerClass: 'container'}) 35 | .when('/bitcoin-block', {template: '', containerClass: 'container'}) 36 | .when('/shamir-secret-sharing', {template: '', containerClass: 'container'}) 37 | .when('/encoding-decoding', {template: '', containerClass: 'container'}) 38 | .when('/mu-sig', {template: '', containerClass: 'container'}) 39 | .when('/schnorr', {template: '', containerClass: 'container'}) 40 | .when('/transaction-creator', {template: '', containerClass: 'container'}) 41 | .when('/aezeed', {template: '', containerClass: 'container'}) 42 | .when('/macaroon', {template: '', containerClass: 'container'}) 43 | .when('/wallet-import', {template: '', containerClass: 'container'}) 44 | .otherwise({redirectTo: '/'}) 45 | } 46 | 47 | function run($location, $rootScope, $route, lodash) { 48 | var id = 0; 49 | $rootScope.$route = $route; 50 | 51 | $rootScope.sha256 = function (input) { 52 | return CryptoJS.SHA256(input).toString(); 53 | }; 54 | 55 | $rootScope.isActive = function (route) { 56 | return route === $location.path(); 57 | }; 58 | 59 | $rootScope.newId = function () { 60 | return (id++); 61 | }; 62 | 63 | $rootScope.difficultyPrefix = function (difficulty) { 64 | var diff = difficulty || $rootScope.difficulty; 65 | var result = ''; 66 | for (var i = 1; i <= diff; i++) { 67 | result += '0'; 68 | } 69 | return result; 70 | }; 71 | 72 | $rootScope.formatString = function (str) { 73 | var args = [].slice.call(arguments, 1), 74 | i = 0; 75 | 76 | return str.replace(/%s/g, function () { 77 | return args[i++]; 78 | }); 79 | }; 80 | 81 | $rootScope.hexPubKeyToBitcoinAddr = function (hex) { 82 | var buffer = bitcoin.Buffer.from(hex, 'hex'); 83 | return bitcoin.address.toBase58Check(buffer, bitcoin.networks.bitcoin.pubKeyHash); 84 | }; 85 | 86 | $rootScope.round = function (number, digits) { 87 | var exp = Math.pow(10, digits); 88 | return (Math.round(number * exp) / exp).toString().replace(/\B(?=(\d{3})+(?!\d))/g, '\''); 89 | }; 90 | 91 | lodash.mixin({ 92 | deeply: function (map) { 93 | return function (obj, fn) { 94 | return map(lodash.mapValues(obj, function (v) { 95 | return lodash.isPlainObject(v) ? lodash.deeply(map)(v, fn) : v; 96 | }), fn); 97 | } 98 | } 99 | }); 100 | } 101 | -------------------------------------------------------------------------------- /bitcoin-networks.js: -------------------------------------------------------------------------------- 1 | window.allNetworks = [{ 2 | label: 'BTC (Bitcoin Regtest, legacy, BIP32/44)', 3 | config: { 4 | messagePrefix: '\u0018Bitcoin Signed Message:\n', 5 | bech32: 'bcrt', 6 | bip32: {public: 0x043587cf, private: 0x04358394}, 7 | pubKeyHash: 111, 8 | scriptHash: 196, 9 | wif: 239, 10 | bip44: 0x01 11 | } 12 | }, { 13 | label: 'BTC (Bitcoin Regtest, SegWit, BIP49)', 14 | config: { 15 | messagePrefix: '\u0018Bitcoin Signed Message:\n', 16 | bech32: 'bcrt', 17 | bip32: {public: 0x044a5262, private: 0x044a4e28}, 18 | pubKeyHash: 111, 19 | scriptHash: 196, 20 | wif: 239, 21 | bip44: 0x01 22 | } 23 | }, { 24 | label: 'BTC (Bitcoin Regtest, Native SegWit, BIP84)', 25 | config: { 26 | messagePrefix: '\u0018Bitcoin Signed Message:\n', 27 | bech32: 'bcrt', 28 | bip32: {public: 0x045f1cf6, private: 0x045f18bc}, 29 | pubKeyHash: 111, 30 | scriptHash: 196, 31 | wif: 239, 32 | bip44: 0x01 33 | } 34 | }, { 35 | label: 'BTC (Bitcoin Signet, legacy, BIP32/44)', 36 | config: { 37 | messagePrefix: '\u0018Bitcoin Signed Message:\n', 38 | bech32: 'tb', 39 | bip32: {public: 0x043587cf, private: 0x04358394}, 40 | pubKeyHash: 111, 41 | scriptHash: 196, 42 | wif: 239, 43 | bip44: 0x01 44 | } 45 | }, { 46 | label: 'BTC (Bitcoin Signet, SegWit, BIP49)', 47 | config: { 48 | messagePrefix: '\u0018Bitcoin Signed Message:\n', 49 | bech32: 'tb', 50 | bip32: {public: 0x044a5262, private: 0x044a4e28}, 51 | pubKeyHash: 111, 52 | scriptHash: 196, 53 | wif: 239, 54 | bip44: 0x01 55 | } 56 | }, { 57 | label: 'BTC (Bitcoin Signet, Native SegWit, BIP84)', 58 | config: { 59 | messagePrefix: '\u0018Bitcoin Signed Message:\n', 60 | bech32: 'tb', 61 | bip32: {public: 0x045f1cf6, private: 0x045f18bc}, 62 | pubKeyHash: 111, 63 | scriptHash: 196, 64 | wif: 239, 65 | bip44: 0x01 66 | } 67 | }, { 68 | label: 'BTC (Bitcoin Testnet, legacy, BIP32/44)', 69 | config: { 70 | messagePrefix: '\u0018Bitcoin Signed Message:\n', 71 | bech32: 'tb', 72 | bip32: {public: 0x043587cf, private: 0x04358394}, 73 | pubKeyHash: 111, 74 | scriptHash: 196, 75 | wif: 239, 76 | bip44: 0x01 77 | } 78 | }, { 79 | label: 'BTC (Bitcoin Testnet, SegWit, BIP49)', 80 | config: { 81 | messagePrefix: '\u0018Bitcoin Signed Message:\n', 82 | bech32: 'tb', 83 | bip32: {public: 0x044a5262, private: 0x044a4e28}, 84 | pubKeyHash: 111, 85 | scriptHash: 196, 86 | wif: 239, 87 | bip44: 0x01 88 | } 89 | }, { 90 | label: 'BTC (Bitcoin Testnet, Native SegWit, BIP84)', 91 | config: { 92 | messagePrefix: '\u0018Bitcoin Signed Message:\n', 93 | bech32: 'tb', 94 | bip32: {public: 0x045f1cf6, private: 0x045f18bc}, 95 | pubKeyHash: 111, 96 | scriptHash: 196, 97 | wif: 239, 98 | bip44: 0x01 99 | } 100 | }, { 101 | label: 'BTC (Bitcoin, legacy, BIP32/44)', 102 | config: { 103 | messagePrefix: '\u0018Bitcoin Signed Message:\n', 104 | bech32: 'bc', 105 | bip32: {public: 0x0488b21e, private: 0x0488ade4}, 106 | pubKeyHash: 0, 107 | scriptHash: 5, 108 | wif: 128, 109 | bip44: 0x00 110 | } 111 | }, { 112 | label: 'BTC (Bitcoin, SegWit, BIP49)', 113 | config: { 114 | messagePrefix: '\u0018Bitcoin Signed Message:\n', 115 | bech32: 'bc', 116 | bip32: {public: 0x049d7cb2, private: 0x049d7878}, 117 | pubKeyHash: 0, 118 | scriptHash: 5, 119 | wif: 128, 120 | bip44: 0x00 121 | } 122 | }, { 123 | label: 'BTC (Bitcoin, Native SegWit, BIP84)', 124 | config: { 125 | messagePrefix: '\u0018Bitcoin Signed Message:\n', 126 | bech32: 'bc', 127 | bip32: {public: 0x04b24746, private: 0x04b2430c}, 128 | pubKeyHash: 0, 129 | scriptHash: 5, 130 | wif: 128, 131 | bip44: 0x00 132 | } 133 | }]; 134 | 135 | window.bitcoinNetworks = _.filter(allNetworks, n => n.config.bech32 && (n.config.bip44 === 0x00 || n.config.bip44 === 0x01)); 136 | 137 | function customToWIF(keyPair, network) { 138 | return keyPair.toWIF(); 139 | } 140 | 141 | function getP2PKHAddress(keyPair, network) { 142 | return bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: network }).address; 143 | } 144 | 145 | function getP2WPKHAddress(keyPair, network) { 146 | return bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: network }).address; 147 | } 148 | 149 | function getNestedP2WPKHAddress(keyPair, network) { 150 | const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: network }); 151 | return bitcoin.payments.p2sh({ redeem: p2wpkh }).address; 152 | } 153 | 154 | function getP2TRAddress(keyPair, network) { 155 | const pubKey = bitcoin.ecurve.Point.decodeFrom(bitcoin.secp256k1, keyPair.publicKey); 156 | const taprootPubkey = bitcoin.schnorr.taproot.taprootConstruct(pubKey); 157 | const words = bitcoin.bech32.toWords(taprootPubkey); 158 | words.unshift(1); 159 | return bitcoin.bech32m.encode(network.bech32, words); 160 | } 161 | 162 | function calculateAddresses(keyPair, network) { 163 | keyPair.address = getP2PKHAddress(keyPair, network); 164 | if (network.bech32) { 165 | keyPair.nestedP2WPKHAddress = getNestedP2WPKHAddress(keyPair, network); 166 | keyPair.P2WPKHAddress = getP2WPKHAddress(keyPair, network); 167 | keyPair.P2TRAddress = getP2TRAddress(keyPair, network); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /browserify.cmd: -------------------------------------------------------------------------------- 1 | node_modules\.bin\browserify.cmd -o libs\js\bitcoin.js browserify.js --standalone bitcoin -------------------------------------------------------------------------------- /browserify.js: -------------------------------------------------------------------------------- 1 | var crypto = require('bitcoinjs-lib/src/crypto'); 2 | var Buffer = require('safe-buffer').Buffer; 3 | var ecurve = require('ecurve'); 4 | var secp256k1 = ecurve.getCurveByName('secp256k1'); 5 | 6 | module.exports = { 7 | Block: require('bitcoinjs-lib/src/block').Block, 8 | ECPair: require('bitcoinjs-lib/src/ecpair'), 9 | Transaction: require('bitcoinjs-lib/src/transaction').Transaction, 10 | TransactionBuilder: require('bitcoinjs-lib/src/transaction_builder'), 11 | 12 | address: require('bitcoinjs-lib/src/address'), 13 | crypto: crypto, 14 | networks: require('bitcoinjs-lib/src/networks'), 15 | opcodes: require('bitcoin-ops'), 16 | script: require('bitcoinjs-lib/src/script'), 17 | payments: require('bitcoinjs-lib/src/payments'), 18 | descriptors: require('@bitcoinerlab/descriptors'), 19 | descriptorsSecp: require('@bitcoinerlab/secp256k1'), 20 | ecurve: ecurve, 21 | secp256k1: secp256k1, 22 | tinySecp256k1: require('tiny-secp256k1'), 23 | varuint: require('varuint-bitcoin'), 24 | BigInteger: require('bigi'), 25 | Buffer: Buffer, 26 | fastRoot: require('merkle-lib/fastRoot'), 27 | bs58check: require('bs58check'), 28 | wif: require('wif'), 29 | bech32: require('bech32').bech32, 30 | bech32m: require('bech32').bech32m, 31 | bip32: require('bip32'), 32 | bip38: require('bip38'), 33 | bip39: require('bip39'), 34 | bip66: require('bip66'), 35 | bip39wordlist: require('bip39/wordlists/english.json'), 36 | pbkdf2: require('pbkdf2'), 37 | secrets: require('secrets.js-grempe'), 38 | schnorr: require('bip-schnorr'), 39 | randomBytes: require('random-bytes').sync, 40 | scrypt: require('scrypt-js').scrypt, 41 | aez: require('aez'), 42 | crc32: require('fast-crc32c/impls/js_crc32c'), 43 | unorm: require('unorm'), 44 | macaroon: require('macaroon'), 45 | createHmac: require('create-hmac'), 46 | 47 | protobuf: require('google-protobuf'), 48 | macaroonIdProtobuf: require('./pages/macaroon/id-protobuf'), 49 | }; 50 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guggero/cryptography-toolkit/d54e835b643fa8aa5ac0bc6e7a17e6f6a279ba9d/favicon.ico -------------------------------------------------------------------------------- /images/fork-me-on-github-ribbon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guggero/cryptography-toolkit/d54e835b643fa8aa5ac0bc6e7a17e6f6a279ba9d/images/fork-me-on-github-ribbon.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | Cryptography Toolkit 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /libs/css/bootstrap-horizon.css: -------------------------------------------------------------------------------- 1 | .row-horizon { 2 | overflow-x: scroll; 3 | overflow-y: hidden; 4 | white-space: nowrap; 5 | } 6 | .row-horizon > [class*="col-lg"], .row-horizon > [class*="col-md"], .row-horizon > [class*="col-sm"], .row-horizon > [class*="col-xs"] { 7 | float: none; 8 | display: inline-block; 9 | white-space: normal; 10 | vertical-align: top; 11 | } 12 | .row-horizon > .col-xs-12 { 13 | width: 75%; 14 | } 15 | .row-horizon > .col-xs-11 { 16 | width: 67.5%; 17 | } 18 | .row-horizon > .col-xs-10 { 19 | width: 60%; 20 | } 21 | .row-horizon > .col-xs-9 { 22 | width: 52.5%; 23 | } 24 | .row-horizon > .col-xs-8 { 25 | width: 45%; 26 | } 27 | .row-horizon > .col-xs-7 { 28 | width: 37.5%; 29 | } 30 | .row-horizon > .col-xs-6 { 31 | width: 30%; 32 | } 33 | .row-horizon > .col-xs-5 { 34 | width: 22.5%; 35 | } 36 | .row-horizon > .col-xs-4 { 37 | width: 15%; 38 | } 39 | .row-horizon > .col-xs-3 { 40 | width: 7.5%; 41 | } 42 | .row-horizon > .col-xs-2 { 43 | width: 5%; 44 | } 45 | .row-horizon > .col-xs-1 { 46 | width: 2.5%; 47 | } 48 | @media (min-width: 768px) { 49 | .row-horizon > .col-sm-12 { 50 | width: 75%; 51 | } 52 | .row-horizon > .col-sm-11 { 53 | width: 67.5%; 54 | } 55 | .row-horizon > .col-sm-10 { 56 | width: 60%; 57 | } 58 | .row-horizon > .col-sm-9 { 59 | width: 52.5%; 60 | } 61 | .row-horizon > .col-sm-8 { 62 | width: 45%; 63 | } 64 | .row-horizon > .col-sm-7 { 65 | width: 37.5%; 66 | } 67 | .row-horizon > .col-sm-6 { 68 | width: 30%; 69 | } 70 | .row-horizon > .col-sm-5 { 71 | width: 22.5%; 72 | } 73 | .row-horizon > .col-sm-4 { 74 | width: 15%; 75 | } 76 | .row-horizon > .col-sm-3 { 77 | width: 7.5%; 78 | } 79 | .row-horizon > .col-sm-2 { 80 | width: 5%; 81 | } 82 | .row-horizon > .col-sm-1 { 83 | width: 2.5%; 84 | } 85 | } 86 | @media (min-width: 992px) { 87 | .row-horizon > .col-md-12 { 88 | width: 75%; 89 | } 90 | .row-horizon > .col-md-11 { 91 | width: 67.5%; 92 | } 93 | .row-horizon > .col-md-10 { 94 | width: 60%; 95 | } 96 | .row-horizon > .col-md-9 { 97 | width: 52.5%; 98 | } 99 | .row-horizon > .col-md-8 { 100 | width: 45%; 101 | } 102 | .row-horizon > .col-md-7 { 103 | width: 37.5%; 104 | } 105 | .row-horizon > .col-md-6 { 106 | width: 30%; 107 | } 108 | .row-horizon > .col-md-5 { 109 | width: 22.5%; 110 | } 111 | .row-horizon > .col-md-4 { 112 | width: 15%; 113 | } 114 | .row-horizon > .col-md-3 { 115 | width: 7.5%; 116 | } 117 | .row-horizon > .col-md-2 { 118 | width: 5%; 119 | } 120 | .row-horizon > .col-md-1 { 121 | width: 2.5%; 122 | } 123 | } 124 | @media (min-width: 1200px) { 125 | .row-horizon > .col-lg-12 { 126 | width: 75%; 127 | } 128 | .row-horizon > .col-lg-11 { 129 | width: 67.5%; 130 | } 131 | .row-horizon > .col-lg-10 { 132 | width: 60%; 133 | } 134 | .row-horizon > .col-lg-9 { 135 | width: 52.5%; 136 | } 137 | .row-horizon > .col-lg-8 { 138 | width: 45%; 139 | } 140 | .row-horizon > .col-lg-7 { 141 | width: 37.5%; 142 | } 143 | .row-horizon > .col-lg-6 { 144 | width: 30%; 145 | } 146 | .row-horizon > .col-lg-5 { 147 | width: 22.5%; 148 | } 149 | .row-horizon > .col-lg-4 { 150 | width: 15%; 151 | } 152 | .row-horizon > .col-lg-3 { 153 | width: 7.5%; 154 | } 155 | .row-horizon > .col-lg-2 { 156 | width: 5%; 157 | } 158 | .row-horizon > .col-lg-1 { 159 | width: 2.5%; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /libs/css/ie10-viewport-bug-workaround.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * IE10 viewport hack for Surface/desktop Windows 8 bug 3 | * Copyright 2014-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | /* 8 | * See the Getting Started docs for more information: 9 | * http://getbootstrap.com/getting-started/#support-ie10-width 10 | */ 11 | @-ms-viewport { 12 | width: device-width; 13 | } 14 | 15 | @-o-viewport { 16 | width: device-width; 17 | } 18 | 19 | @viewport { 20 | width: device-width; 21 | } 22 | -------------------------------------------------------------------------------- /libs/css/ladda-themeless.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Ladda 3 | * http://lab.hakim.se/ladda 4 | * MIT licensed 5 | * 6 | * Copyright (C) 2014 Hakim El Hattab, http://hakim.se 7 | */.ladda-button{position:relative}.ladda-button .ladda-spinner{position:absolute;z-index:2;display:inline-block;width:32px;height:32px;top:50%;margin-top:0;opacity:0;pointer-events:none}.ladda-button .ladda-label{position:relative;z-index:3}.ladda-button .ladda-progress{position:absolute;width:0;height:100%;left:0;top:0;background:rgba(0,0,0,0.2);visibility:hidden;opacity:0;-webkit-transition:0.1s linear all !important;-moz-transition:0.1s linear all !important;-ms-transition:0.1s linear all !important;-o-transition:0.1s linear all !important;transition:0.1s linear all !important}.ladda-button[data-loading] .ladda-progress{opacity:1;visibility:visible}.ladda-button,.ladda-button .ladda-spinner,.ladda-button .ladda-label{-webkit-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-moz-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-ms-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;-o-transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important;transition:0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) all !important}.ladda-button[data-style=zoom-in],.ladda-button[data-style=zoom-in] .ladda-spinner,.ladda-button[data-style=zoom-in] .ladda-label,.ladda-button[data-style=zoom-out],.ladda-button[data-style=zoom-out] .ladda-spinner,.ladda-button[data-style=zoom-out] .ladda-label{-webkit-transition:0.3s ease all !important;-moz-transition:0.3s ease all !important;-ms-transition:0.3s ease all !important;-o-transition:0.3s ease all !important;transition:0.3s ease all !important}.ladda-button[data-style=expand-right] .ladda-spinner{right:-6px}.ladda-button[data-style=expand-right][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-right][data-size="xs"] .ladda-spinner{right:-12px}.ladda-button[data-style=expand-right][data-loading]{padding-right:56px}.ladda-button[data-style=expand-right][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-right][data-loading][data-size="s"],.ladda-button[data-style=expand-right][data-loading][data-size="xs"]{padding-right:40px}.ladda-button[data-style=expand-left] .ladda-spinner{left:26px}.ladda-button[data-style=expand-left][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-left][data-size="xs"] .ladda-spinner{left:4px}.ladda-button[data-style=expand-left][data-loading]{padding-left:56px}.ladda-button[data-style=expand-left][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-left][data-loading][data-size="s"],.ladda-button[data-style=expand-left][data-loading][data-size="xs"]{padding-left:40px}.ladda-button[data-style=expand-up]{overflow:hidden}.ladda-button[data-style=expand-up] .ladda-spinner{top:-32px;left:50%;margin-left:0}.ladda-button[data-style=expand-up][data-loading]{padding-top:54px}.ladda-button[data-style=expand-up][data-loading] .ladda-spinner{opacity:1;top:26px;margin-top:0}.ladda-button[data-style=expand-up][data-loading][data-size="s"],.ladda-button[data-style=expand-up][data-loading][data-size="xs"]{padding-top:32px}.ladda-button[data-style=expand-up][data-loading][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-up][data-loading][data-size="xs"] .ladda-spinner{top:4px}.ladda-button[data-style=expand-down]{overflow:hidden}.ladda-button[data-style=expand-down] .ladda-spinner{top:62px;left:50%;margin-left:0}.ladda-button[data-style=expand-down][data-size="s"] .ladda-spinner,.ladda-button[data-style=expand-down][data-size="xs"] .ladda-spinner{top:40px}.ladda-button[data-style=expand-down][data-loading]{padding-bottom:54px}.ladda-button[data-style=expand-down][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=expand-down][data-loading][data-size="s"],.ladda-button[data-style=expand-down][data-loading][data-size="xs"]{padding-bottom:32px}.ladda-button[data-style=slide-left]{overflow:hidden}.ladda-button[data-style=slide-left] .ladda-label{position:relative}.ladda-button[data-style=slide-left] .ladda-spinner{left:100%;margin-left:0}.ladda-button[data-style=slide-left][data-loading] .ladda-label{opacity:0;left:-100%}.ladda-button[data-style=slide-left][data-loading] .ladda-spinner{opacity:1;left:50%}.ladda-button[data-style=slide-right]{overflow:hidden}.ladda-button[data-style=slide-right] .ladda-label{position:relative}.ladda-button[data-style=slide-right] .ladda-spinner{right:100%;margin-left:0;left:16px}.ladda-button[data-style=slide-right][data-loading] .ladda-label{opacity:0;left:100%}.ladda-button[data-style=slide-right][data-loading] .ladda-spinner{opacity:1;left:50%}.ladda-button[data-style=slide-up]{overflow:hidden}.ladda-button[data-style=slide-up] .ladda-label{position:relative}.ladda-button[data-style=slide-up] .ladda-spinner{left:50%;margin-left:0;margin-top:1em}.ladda-button[data-style=slide-up][data-loading] .ladda-label{opacity:0;top:-1em}.ladda-button[data-style=slide-up][data-loading] .ladda-spinner{opacity:1;margin-top:0}.ladda-button[data-style=slide-down]{overflow:hidden}.ladda-button[data-style=slide-down] .ladda-label{position:relative}.ladda-button[data-style=slide-down] .ladda-spinner{left:50%;margin-left:0;margin-top:-2em}.ladda-button[data-style=slide-down][data-loading] .ladda-label{opacity:0;top:1em}.ladda-button[data-style=slide-down][data-loading] .ladda-spinner{opacity:1;margin-top:0}.ladda-button[data-style=zoom-out]{overflow:hidden}.ladda-button[data-style=zoom-out] .ladda-spinner{left:50%;margin-left:32px;-webkit-transform:scale(2.5);-moz-transform:scale(2.5);-ms-transform:scale(2.5);-o-transform:scale(2.5);transform:scale(2.5)}.ladda-button[data-style=zoom-out] .ladda-label{position:relative;display:inline-block}.ladda-button[data-style=zoom-out][data-loading] .ladda-label{opacity:0;-webkit-transform:scale(0.5);-moz-transform:scale(0.5);-ms-transform:scale(0.5);-o-transform:scale(0.5);transform:scale(0.5)}.ladda-button[data-style=zoom-out][data-loading] .ladda-spinner{opacity:1;margin-left:0;-webkit-transform:none;-moz-transform:none;-ms-transform:none;-o-transform:none;transform:none}.ladda-button[data-style=zoom-in]{overflow:hidden}.ladda-button[data-style=zoom-in] .ladda-spinner{left:50%;margin-left:-16px;-webkit-transform:scale(0.2);-moz-transform:scale(0.2);-ms-transform:scale(0.2);-o-transform:scale(0.2);transform:scale(0.2)}.ladda-button[data-style=zoom-in] .ladda-label{position:relative;display:inline-block}.ladda-button[data-style=zoom-in][data-loading] .ladda-label{opacity:0;-webkit-transform:scale(2.2);-moz-transform:scale(2.2);-ms-transform:scale(2.2);-o-transform:scale(2.2);transform:scale(2.2)}.ladda-button[data-style=zoom-in][data-loading] .ladda-spinner{opacity:1;margin-left:0;-webkit-transform:none;-moz-transform:none;-ms-transform:none;-o-transform:none;transform:none}.ladda-button[data-style=contract]{overflow:hidden;width:100px}.ladda-button[data-style=contract] .ladda-spinner{left:50%;margin-left:0}.ladda-button[data-style=contract][data-loading]{border-radius:50%;width:52px}.ladda-button[data-style=contract][data-loading] .ladda-label{opacity:0}.ladda-button[data-style=contract][data-loading] .ladda-spinner{opacity:1}.ladda-button[data-style=contract-overlay]{overflow:hidden;width:100px;box-shadow:0px 0px 0px 2000px transparent}.ladda-button[data-style=contract-overlay] .ladda-spinner{left:50%;margin-left:0}.ladda-button[data-style=contract-overlay][data-loading]{border-radius:50%;width:52px;box-shadow:0px 0px 0px 2000px rgba(0,0,0,0.8)}.ladda-button[data-style=contract-overlay][data-loading] .ladda-label{opacity:0}.ladda-button[data-style=contract-overlay][data-loading] .ladda-spinner{opacity:1} 8 | -------------------------------------------------------------------------------- /libs/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guggero/cryptography-toolkit/d54e835b643fa8aa5ac0bc6e7a17e6f6a279ba9d/libs/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /libs/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guggero/cryptography-toolkit/d54e835b643fa8aa5ac0bc6e7a17e6f6a279ba9d/libs/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /libs/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guggero/cryptography-toolkit/d54e835b643fa8aa5ac0bc6e7a17e6f6a279ba9d/libs/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /libs/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guggero/cryptography-toolkit/d54e835b643fa8aa5ac0bc6e7a17e6f6a279ba9d/libs/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /libs/js/ie10-viewport-bug-workaround.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * IE10 viewport hack for Surface/desktop Windows 8 bug 3 | * Copyright 2014-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */ 6 | 7 | // See the Getting Started docs for more information: 8 | // http://getbootstrap.com/getting-started/#support-ie10-width 9 | 10 | (function () { 11 | 'use strict'; 12 | 13 | if (navigator.userAgent.match(/IEMobile\/10\.0/)) { 14 | var msViewportStyle = document.createElement('style') 15 | msViewportStyle.appendChild( 16 | document.createTextNode( 17 | '@-ms-viewport{width:auto!important}' 18 | ) 19 | ) 20 | document.querySelector('head').appendChild(msViewportStyle) 21 | } 22 | 23 | })(); 24 | -------------------------------------------------------------------------------- /libs/js/ladda.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Ladda 0.9.4 (2014-06-21, 11:24) 3 | * http://lab.hakim.se/ladda 4 | * MIT licensed 5 | * 6 | * Copyright (C) 2014 Hakim El Hattab, http://hakim.se 7 | */ 8 | (function(t,e){"object"==typeof exports?module.exports=e(require("spin.js")):"function"==typeof define&&define.amd?define(["spin"],e):t.Ladda=e(t.Spinner)})(this,function(t){"use strict";function e(t){if(t===void 0)return console.warn("Ladda button target must be defined."),void 0;t.querySelector(".ladda-label")||(t.innerHTML=''+t.innerHTML+"");var e,n=document.createElement("span");n.className="ladda-spinner",t.appendChild(n);var r,a={start:function(){return e||(e=o(t)),t.setAttribute("disabled",""),t.setAttribute("data-loading",""),clearTimeout(r),e.spin(n),this.setProgress(0),this},startAfter:function(t){return clearTimeout(r),r=setTimeout(function(){a.start()},t),this},stop:function(){return t.removeAttribute("disabled"),t.removeAttribute("data-loading"),clearTimeout(r),e&&(r=setTimeout(function(){e.stop()},1e3)),this},toggle:function(){return this.isLoading()?this.stop():this.start(),this},setProgress:function(e){e=Math.max(Math.min(e,1),0);var n=t.querySelector(".ladda-progress");0===e&&n&&n.parentNode?n.parentNode.removeChild(n):(n||(n=document.createElement("div"),n.className="ladda-progress",t.appendChild(n)),n.style.width=(e||0)*t.offsetWidth+"px")},enable:function(){return this.stop(),this},disable:function(){return this.stop(),t.setAttribute("disabled",""),this},isLoading:function(){return t.hasAttribute("data-loading")},remove:function(){clearTimeout(r),t.removeAttribute("disabled",""),t.removeAttribute("data-loading",""),e&&(e.stop(),e=null);for(var n=0,i=u.length;i>n;n++)if(a===u[n]){u.splice(n,1);break}}};return u.push(a),a}function n(t,e){for(;t.parentNode&&t.tagName!==e;)t=t.parentNode;return e===t.tagName?t:void 0}function r(t){for(var e=["input","textarea"],n=[],r=0;e.length>r;r++)for(var a=t.getElementsByTagName(e[r]),i=0;a.length>i;i++)a[i].hasAttribute("required")&&n.push(a[i]);return n}function a(t,a){a=a||{};var i=[];"string"==typeof t?i=s(document.querySelectorAll(t)):"object"==typeof t&&"string"==typeof t.nodeName&&(i=[t]);for(var o=0,u=i.length;u>o;o++)(function(){var t=i[o];if("function"==typeof t.addEventListener){var s=e(t),u=-1;t.addEventListener("click",function(){var e=!0,i=n(t,"FORM");if(i!==void 0)for(var o=r(i),d=0;o.length>d;d++)""===o[d].value.replace(/^\s+|\s+$/g,"")&&(e=!1);e&&(s.startAfter(1),"number"==typeof a.timeout&&(clearTimeout(u),u=setTimeout(s.stop,a.timeout)),"function"==typeof a.callback&&a.callback.apply(null,[s]))},!1)}})()}function i(){for(var t=0,e=u.length;e>t;t++)u[t].stop()}function o(e){var n,r=e.offsetHeight;0===r&&(r=parseFloat(window.getComputedStyle(e).height)),r>32&&(r*=.8),e.hasAttribute("data-spinner-size")&&(r=parseInt(e.getAttribute("data-spinner-size"),10)),e.hasAttribute("data-spinner-color")&&(n=e.getAttribute("data-spinner-color"));var a=12,i=.2*r,o=.6*i,s=7>i?2:3;return new t({color:n||"#fff",lines:a,radius:i,length:o,width:s,zIndex:"auto",top:"auto",left:"auto",className:""})}function s(t){for(var e=[],n=0;t.length>n;n++)e.push(t[n]);return e}var u=[];return{bind:a,create:e,stopAll:i}}); -------------------------------------------------------------------------------- /libs/js/sha256.js: -------------------------------------------------------------------------------- 1 | /* 2 | CryptoJS v3.1.2 3 | code.google.com/p/crypto-js 4 | (c) 2009-2013 by Jeff Mott. All rights reserved. 5 | code.google.com/p/crypto-js/wiki/License 6 | */ 7 | var CryptoJS=CryptoJS||function(h,s){var f={},t=f.lib={},g=function(){},j=t.Base={extend:function(a){g.prototype=this;var c=new g;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, 8 | q=t.WordArray=j.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||u).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< 9 | 32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=j.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>3]|=parseInt(a.substr(b, 10 | 2),16)<<24-4*(b%8);return new q.init(d,c/2)}},k=v.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new q.init(d,c)}},l=v.Utf8={stringify:function(a){try{return decodeURIComponent(escape(k.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return k.parse(unescape(encodeURIComponent(a)))}}, 11 | x=t.BufferedBlockAlgorithm=j.extend({reset:function(){this._data=new q.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=l.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var m=0;mk;){var l;a:{l=u;for(var x=h.sqrt(l),w=2;w<=x;w++)if(!(l%w)){l=!1;break a}l=!0}l&&(8>k&&(j[k]=v(h.pow(u,0.5))),q[k]=v(h.pow(u,1/3)),k++);u++}var a=[],f=f.SHA256=g.extend({_doReset:function(){this._hash=new t.init(j.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],m=b[2],h=b[3],p=b[4],j=b[5],k=b[6],l=b[7],n=0;64>n;n++){if(16>n)a[n]= 15 | c[d+n]|0;else{var r=a[n-15],g=a[n-2];a[n]=((r<<25|r>>>7)^(r<<14|r>>>18)^r>>>3)+a[n-7]+((g<<15|g>>>17)^(g<<13|g>>>19)^g>>>10)+a[n-16]}r=l+((p<<26|p>>>6)^(p<<21|p>>>11)^(p<<7|p>>>25))+(p&j^~p&k)+q[n]+a[n];g=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&m^f&m);l=k;k=j;j=p;p=h+r|0;h=m;m=f;f=e;e=r+g|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+m|0;b[3]=b[3]+h|0;b[4]=b[4]+p|0;b[5]=b[5]+j|0;b[6]=b[6]+k|0;b[7]=b[7]+l|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes; 16 | d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=g.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=g._createHelper(f);s.HmacSHA256=g._createHmacHelper(f)})(Math); 17 | -------------------------------------------------------------------------------- /libs/js/spin.min.js: -------------------------------------------------------------------------------- 1 | (function(t,e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):t.Spinner=e()})(this,function(){"use strict";function t(t,e){var i,n=document.createElement(t||"div");for(i in e)n[i]=e[i];return n}function e(t){for(var e=1,i=arguments.length;i>e;e++)t.appendChild(arguments[e]);return t}function i(t,e,i,n){var r=["opacity",e,~~(100*t),i,n].join("-"),o=.01+100*(i/n),a=Math.max(1-(1-t)/e*(100-o),t),s=u.substring(0,u.indexOf("Animation")).toLowerCase(),l=s&&"-"+s+"-"||"";return c[r]||(p.insertRule("@"+l+"keyframes "+r+"{"+"0%{opacity:"+a+"}"+o+"%{opacity:"+t+"}"+(o+.01)+"%{opacity:1}"+(o+e)%100+"%{opacity:"+t+"}"+"100%{opacity:"+a+"}"+"}",p.cssRules.length),c[r]=1),r}function n(t,e){var i,n,r=t.style;for(e=e.charAt(0).toUpperCase()+e.slice(1),n=0;d.length>n;n++)if(i=d[n]+e,void 0!==r[i])return i;return void 0!==r[e]?e:void 0}function r(t,e){for(var i in e)t.style[n(t,i)||i]=e[i];return t}function o(t){for(var e=1;arguments.length>e;e++){var i=arguments[e];for(var n in i)void 0===t[n]&&(t[n]=i[n])}return t}function a(t,e){return"string"==typeof t?t:t[e%t.length]}function s(t){this.opts=o(t||{},s.defaults,f)}function l(){function i(e,i){return t("<"+e+' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">',i)}p.addRule(".spin-vml","behavior:url(#default#VML)"),s.prototype.lines=function(t,n){function o(){return r(i("group",{coordsize:d+" "+d,coordorigin:-u+" "+-u}),{width:d,height:d})}function s(t,s,l){e(p,e(r(o(),{rotation:360/n.lines*t+"deg",left:~~s}),e(r(i("roundrect",{arcsize:n.corners}),{width:u,height:n.width,left:n.radius,top:-n.width>>1,filter:l}),i("fill",{color:a(n.color,t),opacity:n.opacity}),i("stroke",{opacity:0}))))}var l,u=n.length+n.width,d=2*u,c=2*-(n.width+n.length)+"px",p=r(o(),{position:"absolute",top:c,left:c});if(n.shadow)for(l=1;n.lines>=l;l++)s(l,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(l=1;n.lines>=l;l++)s(l);return e(t,p)},s.prototype.opacity=function(t,e,i,n){var r=t.firstChild;n=n.shadow&&n.lines||0,r&&r.childNodes.length>e+n&&(r=r.childNodes[e+n],r=r&&r.firstChild,r=r&&r.firstChild,r&&(r.opacity=i))}}var u,d=["webkit","Moz","ms","O"],c={},p=function(){var i=t("style",{type:"text/css"});return e(document.getElementsByTagName("head")[0],i),i.sheet||i.styleSheet}(),f={lines:12,length:7,width:5,radius:10,rotate:0,corners:1,color:"#000",direction:1,speed:1,trail:100,opacity:.25,fps:20,zIndex:2e9,className:"spinner",top:"50%",left:"50%",position:"absolute"};s.defaults={},o(s.prototype,{spin:function(e){this.stop();var i=this,n=i.opts,o=i.el=r(t(0,{className:n.className}),{position:n.position,width:0,zIndex:n.zIndex});if(n.radius+n.length+n.width,r(o,{left:n.left,top:n.top}),e&&e.insertBefore(o,e.firstChild||null),o.setAttribute("role","progressbar"),i.lines(o,i.opts),!u){var a,s=0,l=(n.lines-1)*(1-n.direction)/2,d=n.fps,c=d/n.speed,p=(1-n.opacity)/(c*n.trail/100),f=c/n.lines;(function h(){s++;for(var t=0;n.lines>t;t++)a=Math.max(1-(s+(n.lines-t)*f)%c*p,n.opacity),i.opacity(o,t*n.direction+l,a,n);i.timeout=i.el&&setTimeout(h,~~(1e3/d))})()}return i},stop:function(){var t=this.el;return t&&(clearTimeout(this.timeout),t.parentNode&&t.parentNode.removeChild(t),this.el=void 0),this},lines:function(n,o){function s(e,i){return r(t(),{position:"absolute",width:o.length+o.width+"px",height:o.width+"px",background:e,boxShadow:i,transformOrigin:"left",transform:"rotate("+~~(360/o.lines*d+o.rotate)+"deg) translate("+o.radius+"px"+",0)",borderRadius:(o.corners*o.width>>1)+"px"})}for(var l,d=0,c=(o.lines-1)*(1-o.direction)/2;o.lines>d;d++)l=r(t(),{position:"absolute",top:1+~(o.width/2)+"px",transform:o.hwaccel?"translate3d(0,0,0)":"",opacity:o.opacity,animation:u&&i(o.opacity,o.trail,c+d*o.direction,o.lines)+" "+1/o.speed+"s linear infinite"}),o.shadow&&e(l,r(s("#000","0 0 4px #000"),{top:"2px"})),e(n,e(l,s(a(o.color,d),"0 0 1px rgba(0,0,0,.1)")));return n},opacity:function(t,e,i){t.childNodes.length>e&&(t.childNodes[e].style.opacity=i)}});var h=r(t("group"),{behavior:"url(#default#VML)"});return!n(h,"transform")&&h.adj?l():u=n(h,"animation"),s}); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cryptography-toolkit", 3 | "version": "1.3.0", 4 | "main": "browserify.js", 5 | "repository": "git@github.com:guggero/cryptography-toolkit.git", 6 | "author": "Oliver Gugger ", 7 | "license": "MIT", 8 | "devDependencies": { 9 | "aez": "^0.0.1", 10 | "babel-core": "^6.26.0", 11 | "babel-preset-env": "^1.6.1", 12 | "babelify": "^8.0.0", 13 | "bech32": "2.0.0", 14 | "bigi": "^1.4.0", 15 | "bip-schnorr": "0.6.7", 16 | "bip32": "^2.0.6", 17 | "bip38": "^2.0.1", 18 | "bip39": "^2.3.0", 19 | "bip66": "^1.1.0", 20 | "bitcoin-ops": "^1.4.1", 21 | "bitcoinjs-lib": "^5.2.0", 22 | "@bitcoinerlab/descriptors": "^2.2.0", 23 | "@bitcoinerlab/secp256k1": "^1.1.1", 24 | "browserify": "^17.0.0", 25 | "bs58check": "^2.1.0", 26 | "create-hash": "^1.1.0", 27 | "create-hmac": "^1.1.3", 28 | "ecurve": "^1.0.0", 29 | "ecpair": "^2.1.0", 30 | "fast-crc32c": "^1.0.4", 31 | "google-protobuf": "^3.7.1", 32 | "grpc-web": "^1.0.4", 33 | "grunt": "^1.0.4", 34 | "grunt-angular-templates": "^1.1.0", 35 | "grunt-cli": "^1.3.2", 36 | "husky": "^2.1.0", 37 | "ledger-bitcoin": "^0.2.3", 38 | "macaroon": "guggero/js-macaroon", 39 | "merkle-lib": "^2.0.10", 40 | "pbkdf2": "^3.0.13", 41 | "pushdata-bitcoin": "^1.0.1", 42 | "random-bytes": "^1.0.0", 43 | "safe-buffer": "^5.0.1", 44 | "scrypt-js": "^3.0.0", 45 | "secrets.js-grempe": "^1.1.0", 46 | "typeforce": "^1.11.3", 47 | "unorm": "^1.4.1", 48 | "varuint-bitcoin": "^1.0.4", 49 | "wif": "^2.0.1" 50 | }, 51 | "scripts": { 52 | "browserify": "browserify -o libs/js/bitcoin.js browserify.js --standalone bitcoin", 53 | "templates": "grunt ngtemplates", 54 | "maketargz": "tar --exclude=release --exclude=.idea --exclude=node_modules -czvf release/cryptography-toolkit.offline.v$npm_package_version.tar.gz *", 55 | "release": "rm -rf release && mkdir release && yarn maketargz && cd release && sha256sum -b * > cryptography-toolkit.offline.v$npm_package_version.sum", 56 | "sign": "gpg --clearsign release/cryptography-toolkit.offline.v$npm_package_version.sum" 57 | }, 58 | "husky": { 59 | "hooks": { 60 | "pre-commit": "yarn browserify && yarn templates && git add ." 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /pages/aezeed/aezeed.html: -------------------------------------------------------------------------------- 1 |

aezeed Cipher Seed Scheme

2 | 3 |
4 |
5 |

6 | Explanation 7 |

8 |
9 |
10 |
11 | The aezeed Cipher Seed Scheme is a scheme to create versioned seeds for crypto currency wallets, based on 12 | aez.

13 | This new scheme was first introduced with lnd, one of the 14 | implementations of Lightning Network wallet software. 15 | 16 |

Links:

17 | 26 |
27 |
28 |
29 | 30 |
31 | Warning: Any generated keys are for demonstration only. 32 | Your browser's random number generator might be too predictable to trust! 33 |
34 | 35 |

Generate mnemonic

36 |
37 |
38 | 39 | 40 |
41 | 42 |
43 |
aezeed version
44 | 48 |
Internal version
49 | 54 |
Birthday (days since Bitcoin genesis block)
55 | 60 |
61 |
62 | 63 | 64 |
65 | 66 |
67 | 72 | 73 | 76 | 77 |
78 |
79 | 80 | 81 |
82 | 83 |
84 | 88 | 89 | 92 | 93 | 97 | 98 | 101 | 102 |
103 |
104 | 105 | 106 |
107 | 108 |
109 | 113 | {{vm.error}} 114 |
115 |
116 | 117 | 118 |
119 | 120 |
121 | 125 | 131 |
132 |
133 | 134 |
135 |
136 | 137 |

Decode mnemonic

138 |
139 |
140 | 141 | 142 |
143 | 144 |
145 | 150 | <-- paste mnemonic words here to decode 151 | {{vm.error2}} 152 |
153 |
154 | 155 | 156 |
157 | 158 |
159 | 164 | 165 | 168 | 169 |
170 |
171 | 172 | 173 |
174 | 175 |
176 |
aezeed version
177 | 181 |
Internal version
182 | 185 |
Birthday (days since Bitcoin genesis block)
186 | 189 |
190 |
191 | 192 | 193 | 194 |
195 | 196 |
197 |
Entropy
198 | 201 |
Salt
202 | 205 |
206 |
207 | 208 | 209 |
210 | 211 |
212 | 216 | 222 |
223 |
224 | 225 |
226 |
227 | -------------------------------------------------------------------------------- /pages/aezeed/aezeed.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('app') 3 | .component('aezeedPage', { 4 | templateUrl: 'pages/aezeed/aezeed.html', 5 | controller: AezeedPageController, 6 | controllerAs: 'vm', 7 | bindings: {} 8 | }); 9 | 10 | const AEZEED_DEFAULT_PASSPHRASE = 'aezeed', 11 | AEZEED_VERSION = 0, 12 | BITCOIN_GENESIS_BLOCK_TIMESTAMP = 1231006505, 13 | SCRYPT_N = 32768, 14 | SCRYPT_R = 8, 15 | SCRYPT_P = 1, 16 | SCRYPT_KEY_LENGTH = 32, 17 | PLAINTEXT_LENGTH = 19, 18 | ENCIPHERED_LENGTH = 33, 19 | NUM_WORDS = 24, 20 | SALT_LENGTH = 5, 21 | AD_LENGTH = SALT_LENGTH + 1, 22 | AEZ_TAU = 4, 23 | CHECKSUM_LENGTH = 4, 24 | CHECKSUM_OFFSET = ENCIPHERED_LENGTH - CHECKSUM_LENGTH, 25 | SALT_OFFSET = CHECKSUM_OFFSET - SALT_LENGTH; 26 | 27 | function AezeedPageController($timeout, lodash, bitcoin, bitcoinNetworks, Buffer) { 28 | const vm = this; 29 | 30 | const BITCOIN = lodash.find(bitcoinNetworks, ['label', 'BTC (Bitcoin, legacy, BIP32/44)']); 31 | 32 | vm.networks = bitcoinNetworks; 33 | vm.network = BITCOIN; 34 | vm.network2 = BITCOIN; 35 | vm.asPassword = true; 36 | vm.version = AEZEED_VERSION; 37 | vm.internalVersion = 1; 38 | vm.birthday = 0; 39 | 40 | vm.$onInit = function () { 41 | vm.birthday = vm.calculateBirthday(); 42 | vm.generateEntropy(); 43 | vm.generateSalt(); 44 | vm.generateSeed(); 45 | }; 46 | 47 | vm.generateEntropy = function () { 48 | vm.entropy = bitcoin.randomBytes(16).toString('hex'); 49 | vm.formatBase58(); 50 | }; 51 | 52 | vm.formatBase58 = function () { 53 | vm.nodeBase58 = bitcoin.bip32.fromSeed(Buffer.from(vm.entropy, 'hex'), vm.network.config).toBase58(); 54 | }; 55 | 56 | vm.generateSalt = function () { 57 | vm.salt = bitcoin.randomBytes(5).toString('hex'); 58 | }; 59 | 60 | vm.generateSeed = function () { 61 | vm.formatBase58(); 62 | vm.error = null; 63 | 64 | const password = Buffer.from(vm.passphrase || AEZEED_DEFAULT_PASSPHRASE, 'utf8'); 65 | const salt = Buffer.from(vm.salt, 'hex'); 66 | vm.mnemonic = 'please wait...'; 67 | bitcoin.scrypt(password, salt, SCRYPT_N, SCRYPT_R, SCRYPT_P, SCRYPT_KEY_LENGTH).then(key => { 68 | if (key) { 69 | const cipherText = bitcoin.aez.encrypt(key, null, [vm.getAD(salt)], AEZ_TAU, vm.getSeedBytes()); 70 | const mnemonicBytes = vm.getMnemonicBytes(cipherText); 71 | $timeout(function () { 72 | vm.mnemonic = vm.seedToMnemonic(mnemonicBytes); 73 | }); 74 | } 75 | }); 76 | }; 77 | 78 | vm.calculateBirthday = function () { 79 | const unixTimestamp = Math.round((new Date()).getTime() / 1000); 80 | return Math.floor((unixTimestamp - BITCOIN_GENESIS_BLOCK_TIMESTAMP) / (60 * 60 * 24)); 81 | }; 82 | 83 | vm.getSeedBytes = function () { 84 | const seedBytes = Buffer.alloc(PLAINTEXT_LENGTH); 85 | seedBytes.writeUInt8(vm.internalVersion); 86 | seedBytes.writeUInt16BE(vm.birthday, 1); 87 | Buffer.from(vm.entropy, 'hex').copy(seedBytes, 3); 88 | return seedBytes; 89 | }; 90 | 91 | vm.getAD = function (salt, version) { 92 | const ad = Buffer.alloc(AD_LENGTH, 0); 93 | ad[0] = version; 94 | salt.copy(ad, 1); 95 | return ad; 96 | }; 97 | 98 | vm.getMnemonicBytes = function (cipherText) { 99 | const mnemonicBytes = Buffer.alloc(ENCIPHERED_LENGTH); 100 | mnemonicBytes.writeUInt8(vm.version); 101 | cipherText.copy(mnemonicBytes, 1); 102 | Buffer.from(vm.salt, 'hex').copy(mnemonicBytes, SALT_OFFSET); 103 | const checkSum = bitcoin.crc32.calculate(mnemonicBytes.slice(0, CHECKSUM_OFFSET)); 104 | mnemonicBytes.writeUInt32BE(checkSum, CHECKSUM_OFFSET); 105 | return mnemonicBytes; 106 | }; 107 | 108 | vm.seedToMnemonic = function (seed) { 109 | const entropyBits = bytesToBinary([].slice.call(seed)); 110 | const words = entropyBits.match(/(.{1,11})/g).map(function (binary) { 111 | const index = parseInt(binary, 2); 112 | return bitcoin.bip39wordlist[index]; 113 | }); 114 | return words.join(' '); 115 | }; 116 | 117 | vm.fromMnemonic = function () { 118 | vm.error2 = null; 119 | if (!vm.mnemonic2) { 120 | return; 121 | } 122 | const words = vm.mnemonic2.split(' '); 123 | 124 | if (words.length !== NUM_WORDS) { 125 | vm.error2 = 'Must be 24 words!'; 126 | vm.decoded = {}; 127 | return; 128 | } 129 | 130 | const belongToList = words.every(word => bitcoin.bip39wordlist.indexOf(word) > -1); 131 | if (!belongToList) { 132 | vm.error2 = 'Some words are not in the wordlist!'; 133 | vm.decoded = {}; 134 | return; 135 | } 136 | 137 | const bits = words 138 | .map(word => { 139 | const index = bitcoin.bip39wordlist.indexOf(word); 140 | return lpad(index.toString(2), '0', 11) 141 | }) 142 | .join(''); 143 | const seedBytes = bits.match(/(.{1,8})/g).map(bin => parseInt(bin, 2)); 144 | vm.decodeSeed(Buffer.from(seedBytes)); 145 | }; 146 | 147 | vm.decodeSeed = function (seed) { 148 | if (!seed || seed.length === 0 || seed[0] !== AEZEED_VERSION) { 149 | vm.error2 = 'Invalid seed or version!'; 150 | vm.decoded = {}; 151 | return; 152 | } 153 | 154 | const salt = seed.slice(SALT_OFFSET, SALT_OFFSET + SALT_LENGTH); 155 | const password = Buffer.from(vm.passphrase2 || AEZEED_DEFAULT_PASSPHRASE, 'utf8'); 156 | const cipherSeed = seed.slice(1, SALT_OFFSET); 157 | const checksum = seed.slice(CHECKSUM_OFFSET); 158 | 159 | const newChecksum = bitcoin.crc32.calculate(seed.slice(0, CHECKSUM_OFFSET)); 160 | if (newChecksum !== checksum.readUInt32BE(0)) { 161 | vm.error2 = 'Invalid seed checksum!'; 162 | vm.decoded = {}; 163 | return; 164 | } 165 | 166 | const ad = vm.getAD(salt, AEZEED_VERSION); 167 | 168 | vm.decoded = { 169 | salt: salt.toString('hex'), 170 | entropy: 'please wait...' 171 | }; 172 | bitcoin.scrypt(password, salt, SCRYPT_N, SCRYPT_R, SCRYPT_P, SCRYPT_KEY_LENGTH).then(key => { 173 | if (key) { 174 | const plainSeedBytes = bitcoin.aez.decrypt(key, null, [ad], AEZ_TAU, cipherSeed); 175 | if (plainSeedBytes == null) { 176 | $timeout(() => { 177 | vm.decoded = {}; 178 | vm.error2 = 'Decryption failed. Invalid passphrase?'; 179 | }); 180 | } else { 181 | $timeout(() => { 182 | vm.decoded.version = AEZEED_VERSION; 183 | vm.decoded.internalVersion = plainSeedBytes.readUInt8(0); 184 | vm.decoded.birthday = plainSeedBytes.readUInt16BE(1); 185 | vm.decoded.entropy = plainSeedBytes.slice(3).toString('hex'); 186 | vm.fromEntropy(); 187 | }); 188 | } 189 | } 190 | }); 191 | }; 192 | 193 | vm.fromEntropy = function () { 194 | vm.decoded.nodeBase58 = bitcoin.bip32.fromSeed(Buffer.from(vm.decoded.entropy, 'hex'), vm.network2.config).toBase58(); 195 | }; 196 | 197 | function bytesToBinary(bytes) { 198 | return bytes.map(x => lpad(x.toString(2), '0', 8)) 199 | .join(''); 200 | } 201 | 202 | function lpad(str, padString, length) { 203 | while (str.length < length) { 204 | str = padString + str; 205 | } 206 | return str; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /pages/bitcoin-block/bitcoin-block.html: -------------------------------------------------------------------------------- 1 |

Bitcoin Block Parser

2 | 3 |
4 |
5 |

6 | Explanation 7 |

8 |
9 |
10 |
11 | Parse and examine a block from Bitcoin's network or by pasting the binary content 12 | as a hex string. 13 | 14 |

Sources, tools and other useful information:

15 | 20 |
21 |
22 |
23 | 24 |

Block data

25 |
26 |
27 |
28 | 29 |
30 | 35 |
36 |
37 |
38 | 39 |
40 | 45 | <-- paste here to examine 46 | {{vm.error}} 47 |
48 |
49 |
50 |
51 | Load other examples (might take a while to load/render!): 52 | 69 |
70 |
71 | 72 |
73 | 74 |
75 | 83 |
84 |
85 | 86 |
87 | 90 |
91 | 92 |
93 |
94 | 96 |
97 |
98 | 99 |
100 | 101 |
102 | 103 |
104 |
105 |
106 | 107 |
108 | 109 |
110 |
111 |
112 | 113 |
114 | 115 |
116 |
117 |
118 |
119 | 120 |
121 |

Merkle Tree

122 |
123 |
124 | 125 | Only shown up to 200 transactions due to performance reasons 126 | 127 |
128 | 129 |
130 |
131 |

TX {{$index}}

132 |
133 |
134 | 135 |
136 | 137 |
138 | 139 |
140 |
141 | 142 | 143 |
144 | 145 |
146 | 147 |
148 |
149 | 150 | 151 |
152 | 153 |
154 | 155 | Version: {{::tx.version}}, Locktime: {{::tx.locktime}}, is coinbase TX: {{::origTx.isCoinbase()}}, weight: {{::origTx.weight()}} 156 | 157 |
158 |
159 | 160 | 161 |
162 | 163 |
164 |
165 |
166 |
167 | Referencing TX ID: 168 | 169 |
170 |
171 | Referencing TX Output Index: 172 | 173 | Sequence Number: 174 | 175 |
176 |
177 | Script: 178 | 179 |
180 |
181 | Data: 182 | 183 |
184 |
185 | Witness: 186 | 187 |
188 |
189 |
190 |
191 |
192 | 193 | 194 |
195 | 196 |
197 |
198 |
199 |
200 | Value (Raw/Satoshis): 201 | 202 | Value (BTCs): 203 | 204 |
205 |
206 | Script: 207 | 208 |
209 |
210 | Address: 211 | 214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 | -------------------------------------------------------------------------------- /pages/bitcoin-block/bitcoin-block.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('app') 3 | .component('bitcoinBlockPage', { 4 | templateUrl: 'pages/bitcoin-block/bitcoin-block.html', 5 | controller: BitcoinBlockPageController, 6 | controllerAs: 'vm', 7 | bindings: {} 8 | }); 9 | 10 | const BLOCK_SOURCE_API_URLS = [{ 11 | label: 'blockchain.info', 12 | url: 'https://blockchain.info/rawblock/%s?format=hex&cors=true' 13 | }, { 14 | label: 'bitcoin.gugger.guru', 15 | url: 'https://bitcoin.gugger.guru/rest/block/%s.hex' 16 | }]; 17 | 18 | function BitcoinBlockPageController($rootScope, $http, lodash) { 19 | const vm = this; 20 | 21 | vm.hash = '0000000000000000079c58e8b5bce4217f7515a74b170049398ed9b8428beb4a'; 22 | vm.sources = BLOCK_SOURCE_API_URLS; 23 | vm.selectedBlockSource = vm.sources[0]; 24 | vm.raw = null; 25 | vm.decodedBlock = null; 26 | 27 | vm.$onInit = function () { 28 | vm.downloadBlock(); 29 | }; 30 | 31 | vm.downloadBlock = function () { 32 | vm.error = null; 33 | vm.raw = 'loading...'; 34 | $http.get($rootScope.formatString(vm.selectedBlockSource.url, vm.hash)) 35 | .then(function (response) { 36 | vm.raw = response.data.trim(); 37 | vm.parseBlock(); 38 | }) 39 | .catch(function (error) { 40 | vm.error = error.data; 41 | }); 42 | }; 43 | 44 | vm.parseBlock = function () { 45 | vm.error = null; 46 | vm.decodedBlock = 'loading...'; 47 | 48 | try { 49 | vm.block = bitcoin.Block.fromHex(vm.raw); 50 | vm.block.weight = lodash.sumBy(vm.block.transactions, function (tx) { 51 | return tx.weight(); 52 | }); 53 | vm.block.legacySize = 80 + bitcoin.varuint.encodingLength(vm.block.transactions.length) + vm.block.transactions.reduce(function (a, x) { 54 | return a + x.byteLength(false); 55 | }, 0); 56 | vm.decodedBlock = lodash.deeply(lodash.mapValues)(angular.copy(vm.block), bufferToString); 57 | paintMerkleTree(); 58 | } catch (e) { 59 | vm.error = e; 60 | vm.decodedBlock = e; 61 | console.error(e); 62 | } 63 | }; 64 | 65 | vm.getP2PKH = function (script) { 66 | var chunks = bitcoin.script.decompile(script); 67 | var decoded = bitcoin.script.toASM(chunks); 68 | if (decoded.indexOf('OP_DUP OP_HASH160 ') === 0) { 69 | return chunks[2]; 70 | } 71 | return null; 72 | }; 73 | 74 | vm.isP2PKH = function (script) { 75 | return vm.getP2PKH(script) !== null; 76 | }; 77 | 78 | vm.getTxId = function (hex) { 79 | return bitcoin.Buffer.from(hex, 'hex').reverse().toString('hex'); 80 | }; 81 | 82 | vm.getRawString = function (hex) { 83 | return bitcoin.Buffer.from(hex, 'hex').toString(); 84 | }; 85 | 86 | function isArrayBuffer(value) { 87 | return value && value.buffer instanceof ArrayBuffer && value.byteLength !== undefined; 88 | } 89 | 90 | function bufferToString(val, key) { 91 | if (isArrayBuffer(val)) { 92 | return val.toString('hex'); 93 | } else if (Array.isArray(val) && key === 'transactions') { 94 | lodash.forEach(val, function (tx) { 95 | lodash.forEach(tx.ins, function (txIn, index) { 96 | txIn.hash = txIn.hash.toString('hex'); 97 | txIn.script = txIn.script.toString('hex'); 98 | txIn.witness = txIn.witness.map(function (witness) { 99 | return witness.toString('hex'); 100 | }); 101 | }); 102 | lodash.forEach(tx.outs, function (txOut, index) { 103 | var chunks = bitcoin.script.decompile(txOut.script); 104 | txOut.script = bitcoin.script.toASM(chunks); 105 | }); 106 | }); 107 | return val; 108 | } else { 109 | return val; 110 | } 111 | } 112 | 113 | function calculateTree() { 114 | var txs = vm.block.transactions; 115 | 116 | var bottom = txs.map(function (tx, index) { 117 | var hash = tx.getHash(); 118 | return { 119 | leave: true, 120 | hash: hash, 121 | name: 'TX ' + index, 122 | info: '
TX ' + index + ': ' + shortHash(hash) + '
' 123 | }; 124 | }); 125 | var nextLevel = []; 126 | do { 127 | for (var i = 0; i < bottom.length; i += 2) { 128 | var left = bottom[i]; 129 | var right = i + 1 === bottom.length ? angular.copy(left) : bottom[i + 1]; 130 | var hash = bitcoin.crypto.hash256(bitcoin.Buffer.concat([left.hash, right.hash])); 131 | nextLevel.push({ 132 | leave: false, 133 | hash: hash, 134 | name: shortHash(hash), 135 | info: '
sha256(\n  sha256(\n    ' + shortHash(left.hash) + ' + ' + shortHash(right.hash) + '\n  )\n)  =  ' + shortHash(hash) + '
', 136 | children: [left, right] 137 | }); 138 | } 139 | bottom = nextLevel; 140 | nextLevel = []; 141 | } while (bottom.length > 1); 142 | 143 | return bottom[0]; 144 | } 145 | 146 | function paintMerkleTree() { 147 | if (vm.decodedBlock.transactions > 200) { 148 | return; 149 | } 150 | var numLeaves = vm.decodedBlock.transactions.length; 151 | var width = (numLeaves * 100) + 200; 152 | var height = ((Math.log2(numLeaves) + 1) * 100) + 200; 153 | 154 | var svg = d3.select('#merkleTree') 155 | .html('') 156 | .append('svg:svg') 157 | .attr('width', width) 158 | .attr('height', height) 159 | .append('svg:g') 160 | .attr('transform', 'translate(-40, 30)'); 161 | 162 | var tree = d3.layout.tree().size([width - 100, height - 100]); 163 | var diagonal = d3.svg.diagonal(); 164 | 165 | var nodes = tree.nodes(calculateTree()); 166 | var links = tree.links(nodes); 167 | 168 | // Add tooltip div 169 | var div = d3.select('#tooltip').style('opacity', 1e-6); 170 | 171 | var link = svg.selectAll('pathlink') 172 | .data(links) 173 | .enter().append('svg:path') 174 | .attr('class', 'link') 175 | .attr('d', diagonal); 176 | 177 | var node = svg.selectAll('g.node') 178 | .data(nodes) 179 | .enter().append('svg:g') 180 | .attr('transform', function (d) { 181 | return 'translate(' + d.x + ',' + d.y + ')'; 182 | }); 183 | 184 | // Add the dot at every node 185 | node.append('svg:circle') 186 | .on('mouseover', function () { 187 | div.transition().duration(300).style('opacity', 1); 188 | }) 189 | .on('mousemove', function (d) { 190 | div.html(d.info).style('left', (d3.event.pageX + 20) + 'px').style('top', (d3.event.pageY + 20) + 'px'); 191 | }) 192 | .on('mouseout', function () { 193 | div.transition().duration(300).style('opacity', 1e-6); 194 | }) 195 | .attr('fill', 'red') 196 | .attr('r', 5.5); 197 | 198 | node.append('svg:text') 199 | .attr('dx', 8) 200 | .attr('dy', 3) 201 | .text(function (d) { 202 | return d.name; 203 | }); 204 | } 205 | 206 | function shortHash(hash) { 207 | return hash.toString('hex').substring(0, 16) + '...'; 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /pages/ecc/ecc.html: -------------------------------------------------------------------------------- 1 |

Elliptic Curve Cryptography / Key Pair

2 | 3 |
4 |
5 |

6 | Explanation 7 |

8 |
9 |
10 |
11 | This page shows the relationship between private keys, public keys, addresses and transactions.

12 | The first part shows how the elliptic curve private and public keys are formatted for the different 13 | digital coin networks. The parameters for these networks can be found in the GitHub repos of the coin 14 | wallet software.
15 | A private key can either be generated (using your browser's 16 | window.crypto.getRandomValues() function) or 17 | imported (by pasting into the text field "Private key (WIF format, uncompressed)"). It doesn't matter if 18 | the pasted key is compressed or uncompressed since that is auto detected by the underlying algorithm. 19 | 20 |

Sources, tools and other useful information:

21 | 32 |
33 |
34 |
35 | 36 |
37 | Warning: Any generated keys are for demonstration only. 38 | Your browser's random number generator might be too predictable to trust! 39 |
40 | 41 |

Elliptic Curve key pair

42 |
43 |
44 |
45 | 46 |
47 | 48 | 49 | 50 | 51 |
52 |
53 | 54 |
55 | 56 |
57 | 58 |
59 |
60 | 61 |
62 | 63 |
64 | 65 |
66 |
67 | 68 |
69 | 70 |
71 | 72 |
73 |
74 | 75 |
76 | 77 |
78 | 83 |
84 |
85 | 86 |
87 | 90 |
91 | 92 |
93 |
94 |
95 | 96 |
97 | 100 |
101 |
106 | <-- paste here to import from WIF 107 | {{vm.error}} 108 |
109 |
110 |
111 | 112 |
113 | 116 |
117 | 118 |
119 |
120 |
121 | 122 |
123 | 124 |
125 | 126 |
127 |
128 | 129 |
130 | 131 |
132 | 133 |
134 |
135 | 136 |
137 | 138 |
139 | 140 |
141 |
142 |
143 |
144 | 145 |

Elliptic Curve Digital Signature Algorithm (ECDSA): Sign message

146 |
147 |
148 |
149 | 150 |
151 | 152 |
153 |
154 | 155 |
156 | 157 |
158 | 159 |
160 |
161 | 162 |
163 | 164 |
165 | 166 |
167 |
168 |
169 |
170 | 171 |

Elliptic Curve Digital Signature Algorithm (ECDSA): Verify signature

172 |
173 |
174 |
175 | 176 |
177 | 181 |
182 |
183 | 184 |
185 | 186 |
187 | 191 |
192 |
193 |
194 |
195 | 196 |

ECC Multiply

197 |
198 |
199 |
200 | 201 |
202 | 206 |
207 |
208 |
Multiplicand is private key:
209 |
210 | 213 |
214 | 217 |
218 |
219 | 220 |
221 | 222 |
223 | 227 |
228 |
229 | 230 |
231 | 232 |
233 | 234 |
235 |
236 |
237 |
238 | 239 |

Taproot keys

240 |
241 |
242 |
243 | 244 |
245 | 249 |
250 |
251 | 252 |
253 | 254 |
255 | 259 |
260 |
261 | 262 |
263 | 264 |
265 | 266 |
267 |
268 | 269 |
270 | 271 |
272 | 273 |
274 |
275 |
276 |
277 | -------------------------------------------------------------------------------- /pages/ecc/ecc.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('app') 3 | .component('eccPage', { 4 | templateUrl: 'pages/ecc/ecc.html', 5 | controller: EccPageController, 6 | controllerAs: 'vm', 7 | bindings: {} 8 | }); 9 | 10 | function EccPageController(lodash, allNetworks) { 11 | const vm = this; 12 | const HASH_TYPE = bitcoin.Transaction.SIGHASH_ALL; 13 | 14 | vm.networks = allNetworks; 15 | vm.network = lodash.find(vm.networks, ['label', 'BTC (Bitcoin, legacy, BIP32/44)']); 16 | vm.message = 'Insert famous quote here!'; 17 | vm.qrPrivUncompressed = new QRCode('qrPrivUncompressed'); 18 | vm.qrPrivCompressed = new QRCode('qrPrivCompressed'); 19 | vm.qrPubkey = new QRCode('qrPubkey'); 20 | vm.trMerkleRoot = ''; 21 | vm.trAddress = ''; 22 | 23 | vm.$onInit = function () { 24 | vm.newPrivateKey(); 25 | vm.formatKeyForNetwork(); 26 | vm.signMessage(); 27 | vm.eccMultiply(); 28 | }; 29 | 30 | vm.newPrivateKey = function () { 31 | vm.keyPair = bitcoin.ECPair.makeRandom({ compressed: true, network: vm.network.config }); 32 | vm.formatKeyForNetwork(); 33 | vm.signMessage(); 34 | }; 35 | 36 | vm.formatKeyForNetwork = function () { 37 | vm.error = null; 38 | const network = vm.network.config; 39 | vm.privKeyDecimal = bitcoin.BigInteger.fromBuffer(vm.keyPair.privateKey); 40 | vm.keyPair.wif = customToWIF(vm.keyPair, network); 41 | vm.keyPair.address = getP2PKHAddress(vm.keyPair, network); 42 | if (network.bech32) { 43 | vm.keyPair.nestedP2WPKHAddress = getNestedP2WPKHAddress(vm.keyPair, network); 44 | vm.keyPair.P2WPKHAddress = getP2WPKHAddress(vm.keyPair, network); 45 | vm.keyPair.P2TRAddress = getP2TRAddress(vm.keyPair, network); 46 | } 47 | vm.pubKey = vm.keyPair.publicKey; 48 | vm.pubKeyDecimal = bitcoin.BigInteger.fromBuffer(vm.pubKey); 49 | vm.keyPairUncompressed = bitcoin.ECPair.fromPrivateKey(vm.keyPair.privateKey, { compressed: false, network: network }); 50 | vm.keyPairUncompressed.wif = customToWIF(vm.keyPairUncompressed, network); 51 | vm.eccMultiplicand = vm.pubKey.toString('hex'); 52 | vm.eccMultiplier = 'aabbccddeeff00112233445566778899'; 53 | vm.multiplicandPrivKey = false; 54 | vm.trInternalKey = vm.keyPair.publicKey.toString('hex'); 55 | 56 | // update QR codes 57 | vm.qrPrivUncompressed.makeCode(vm.keyPairUncompressed.wif); 58 | vm.qrPrivCompressed.makeCode(vm.keyPair.wif); 59 | vm.qrPubkey.makeCode(vm.keyPair.address); 60 | 61 | vm.trTweak(); 62 | }; 63 | 64 | vm.importFromWif = function () { 65 | const network = vm.network.config; 66 | try { 67 | vm.keyPair = bitcoin.ECPair.fromWIF(vm.keyPairUncompressed.wif, network); 68 | vm.formatKeyForNetwork(); 69 | vm.signMessage(); 70 | vm.eccMultiply(); 71 | } catch (e) { 72 | try { 73 | const privKey = bitcoin.Buffer.from(vm.keyPairUncompressed.wif, 'hex'); 74 | vm.keyPair = bitcoin.ECPair.fromPrivateKey(privKey, { compressed: true, network: network }); 75 | vm.formatKeyForNetwork(); 76 | vm.signMessage(); 77 | vm.eccMultiply(); 78 | return; 79 | } catch (e2) { 80 | console.log(e2); 81 | } 82 | vm.error = e; 83 | } 84 | }; 85 | 86 | vm.signMessage = function () { 87 | vm.messageHash = bitcoin.crypto.sha256(vm.message); 88 | vm.signature = bitcoin.script.signature.encode(vm.keyPair.sign(vm.messageHash), HASH_TYPE).toString('hex'); 89 | vm.messageHashToVerify = vm.messageHash.toString('hex'); 90 | vm.signatureToVerify = vm.signature; 91 | vm.verifySignature(); 92 | }; 93 | 94 | vm.verifySignature = function () { 95 | try { 96 | const hash = bitcoin.Buffer.from(vm.messageHashToVerify, 'hex'); 97 | const signatureWithHashType = bitcoin.script.signature.decode(bitcoin.Buffer.from(vm.signatureToVerify, 'hex')); 98 | vm.signatureValid = vm.keyPair.verify(hash, signatureWithHashType.signature); 99 | } catch (e) { 100 | console.error(e); 101 | vm.signatureValid = false; 102 | } 103 | }; 104 | 105 | vm.eccMultiply = function () { 106 | const a = bitcoin.Buffer.from(vm.eccMultiplicand, 'hex'); 107 | const b = bitcoin.Buffer.from(vm.eccMultiplier, 'hex'); 108 | 109 | let resultPoint = null; 110 | if (vm.multiplicandPrivKey) { 111 | const bPoint = bitcoin.secp256k1.G.multiply(bitcoin.BigInteger.fromBuffer(b)); 112 | resultPoint = bPoint.multiply(bitcoin.BigInteger.fromBuffer(a)); 113 | } else { 114 | const aPoint = bitcoin.ecurve.Point.decodeFrom(bitcoin.secp256k1, a); 115 | resultPoint = aPoint.multiply(bitcoin.BigInteger.fromBuffer(b)); 116 | } 117 | vm.eccResult = resultPoint.getEncoded(true).toString('hex'); 118 | } 119 | 120 | vm.trTweak = function () { 121 | const internalKeyBuf = bitcoin.Buffer.from(vm.trInternalKey, 'hex'); 122 | const internalKey = bitcoin.ecurve.Point.decodeFrom(bitcoin.secp256k1, internalKeyBuf) 123 | let merkleRoot = bitcoin.Buffer.alloc(0, 0); 124 | if (vm.trMerkleRoot !== "") { 125 | merkleRoot = bitcoin.Buffer.from(vm.trMerkleRoot, 'hex'); 126 | } 127 | const taprootPubkey = bitcoin.schnorr.taproot.taprootConstruct(internalKey, merkleRoot); 128 | vm.trOutputKey = taprootPubkey.toString('hex'); 129 | const words = bitcoin.bech32.toWords(taprootPubkey); 130 | words.unshift(1); 131 | vm.trAddress = bitcoin.bech32m.encode(vm.network.config.bech32, words); 132 | }; 133 | } 134 | -------------------------------------------------------------------------------- /pages/encoding-decoding/encoding-decoding.html: -------------------------------------------------------------------------------- 1 |

Encoding/Decoding

2 | 3 |

Hex

4 |
5 |
6 | 7 |
8 | 9 |
10 | 17 |
18 |
19 |
{{vm.error}}
20 |
21 |
22 | 23 |
24 | 25 |
26 | 33 |
34 |
35 |
{{vm.error2}}
36 |
37 |
38 |
39 |
40 | 41 |

Base64

42 |
43 |
44 | 45 |
46 | 47 |
48 | 55 |
56 |
57 |
{{vm.error3}}
58 |
59 |
60 | 61 |
62 | 63 |
64 | 71 |
72 |
73 |
{{vm.error4}}
74 |
75 |
76 |
77 |
78 | 79 |

Bitcoin Outpoint

80 |
81 |
82 | 83 |
84 | 86 |
87 | 92 |
93 |
94 |
{{vm.error5}}
95 |
96 |
97 | 98 |
99 | 102 |
103 | 108 |
109 |
110 |
{{vm.error6}}
111 |
112 |
113 |
114 |
115 | 116 |

Lightning Short Channel ID (SCID)

117 |
118 |
119 | 120 |
121 | 123 |
124 | 129 |
130 |
131 |
{{vm.error6}}
132 |
133 |
134 | 135 |
136 | 139 |
140 | 145 |
146 |
147 |
{{vm.error7}}
148 |
149 |
150 |
151 |
152 | -------------------------------------------------------------------------------- /pages/encoding-decoding/encoding-decoding.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('app') 3 | .component('encodingDecodingPage', { 4 | templateUrl: 'pages/encoding-decoding/encoding-decoding.html', 5 | controller: EncodingDecodingPageController, 6 | controllerAs: 'vm', 7 | bindings: {} 8 | }); 9 | 10 | function EncodingDecodingPageController() { 11 | const vm = this; 12 | const Buffer = bitcoin.Buffer; 13 | 14 | vm.hexString = '68656c6c6f'; 15 | vm.hexDecodedString = ''; 16 | vm.base64String = 'aGVsbG8='; 17 | vm.base64DecodedString = ''; 18 | vm.outpointString = '8f900c6414c5c27231080f46168105e9fec20f9fb2f11b25ee0312d89bc022c0:8'; 19 | vm.outpointEncodedString = ''; 20 | vm.scidUint64 = '774909407114231809'; 21 | vm.scidHumanReadable = ''; 22 | vm.error = null; 23 | vm.error2 = null; 24 | 25 | vm.$onInit = function () { 26 | vm.parseHexString(); 27 | vm.parseBase64String(); 28 | vm.parseOutpointString(); 29 | vm.parseScidUint64(); 30 | }; 31 | 32 | vm.parseHexString = function () { 33 | try { 34 | vm.error = null; 35 | vm.hexDecodedString = Buffer.from(vm.hexString, 'hex').toString('utf8'); 36 | } catch (e) { 37 | vm.error = e.message; 38 | } 39 | }; 40 | 41 | vm.encodeHexString = function () { 42 | try { 43 | vm.error2 = null; 44 | vm.hexString = Buffer.from(vm.hexDecodedString, 'utf8').toString('hex'); 45 | } catch (e) { 46 | vm.error2 = e.message; 47 | } 48 | } 49 | 50 | vm.parseBase64String = function () { 51 | try { 52 | vm.error3 = null; 53 | vm.base64DecodedString = Buffer.from(vm.base64String, 'base64').toString('utf8'); 54 | } catch (e) { 55 | vm.error3 = e.message; 56 | } 57 | }; 58 | 59 | vm.encodeBase64String = function () { 60 | try { 61 | vm.error4 = null; 62 | vm.base64String = Buffer.from(vm.base64DecodedString, 'utf8').toString('base64'); 63 | } catch (e) { 64 | vm.error4 = e.message; 65 | } 66 | } 67 | 68 | vm.parseOutpointString = function () { 69 | try { 70 | vm.error5 = null; 71 | 72 | const [txid, vout] = vm.outpointString.split(':'); 73 | if (txid === undefined || vout === undefined) { 74 | throw new Error('Invalid outpoint string, must be in form :'); 75 | } 76 | 77 | const txidBuffer = Buffer.from(txid, 'hex'); 78 | if (txidBuffer.length !== 32) { 79 | throw new Error('Invalid txid, must be 32 bytes'); 80 | } 81 | 82 | // A TXID is the reverse of the hash of the transaction. 83 | txidBuffer.reverse(); 84 | 85 | const voutInt = parseInt(vout, 10); 86 | 87 | const resultBuffer = Buffer.alloc(36); 88 | txidBuffer.copy(resultBuffer); 89 | resultBuffer.writeInt32LE(voutInt, 32); 90 | 91 | vm.outpointEncodedString = resultBuffer.toString('hex'); 92 | } catch (e) { 93 | vm.error5 = e.message; 94 | } 95 | }; 96 | 97 | vm.decodeEncodedOutpoint = function () { 98 | try { 99 | vm.error6 = null; 100 | const parsedBuffer = Buffer.from(vm.outpointEncodedString, 'hex'); 101 | 102 | if (parsedBuffer.length !== 36) { 103 | throw new Error('Invalid encoded outpoint, must be 36 bytes'); 104 | } 105 | 106 | const txidBuffer = parsedBuffer.slice(0, 32); 107 | txidBuffer.reverse(); 108 | 109 | const vout = parsedBuffer.readInt32LE(32); 110 | 111 | vm.outpointString = `${txidBuffer.toString('hex')}:${vout}`; 112 | } catch (e) { 113 | vm.error6 = e.message; 114 | } 115 | } 116 | 117 | vm.parseScidUint64 = function () { 118 | try { 119 | vm.error7 = null; 120 | 121 | const chanID = BigInt(vm.scidUint64); 122 | let blockHeight = chanID >> BigInt(40); 123 | let txIndex = (chanID >> BigInt(16)) & BigInt(0xFFFFFF); 124 | let txPosition = chanID & BigInt(0xFFFF); 125 | 126 | vm.scidHumanReadable = `${blockHeight.toString(10)}:${txIndex.toString(10)}:${txPosition.toString(10)}`; 127 | } catch (e) { 128 | vm.error7 = e.message; 129 | } 130 | } 131 | 132 | vm.decodeScidHumanReadable = function () { 133 | try { 134 | vm.error8 = null; 135 | 136 | const parts = vm.scidHumanReadable.split(':'); 137 | if (parts.length !== 3) { 138 | throw new Error('Invalid SCID format, must be ::'); 139 | } 140 | 141 | const blockHeight = BigInt(parts[0]); 142 | const txIndex = BigInt(parts[1]); 143 | const txPosition = BigInt(parts[2]); 144 | 145 | if (blockHeight < 0 || txIndex < 0 || txPosition < 0) { 146 | throw new Error('Block height, transaction index, and position must be non-negative integers'); 147 | } 148 | 149 | const chanID = (blockHeight << BigInt(40)) | (txIndex << BigInt(16)) | txPosition; 150 | vm.scidUint64 = chanID.toString(10); 151 | } catch (e) { 152 | vm.error8 = e.message; 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /pages/hd-wallet/hd-wallet.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('app') 3 | .component('hdWalletPage', { 4 | templateUrl: 'pages/hd-wallet/hd-wallet.html', 5 | controller: HdWalletPageController, 6 | controllerAs: 'vm', 7 | bindings: {} 8 | }); 9 | 10 | const calculatePath = function (bip, coinType, account, change, index) { 11 | let path = bip.path; 12 | if (bip.hasCoinType) { 13 | path = path.replace(/coin/g, coinType.config.bip44); 14 | } 15 | path = path.replace(/account/g, account); 16 | path = path.replace(/change/g, change); 17 | path = path.replace(/index/g, index); 18 | return path; 19 | }; 20 | 21 | const deepCopy = function (obj) { 22 | return JSON.parse(JSON.stringify(obj)); 23 | }; 24 | 25 | function HdWalletPageController(lodash, allNetworks) { 26 | const vm = this; 27 | 28 | const PBKDF2_SALT = 'Digital Bitbox', 29 | PBKDF2_HMACLEN = 64, 30 | PBKDF2_ROUNDS_APP = 20480; 31 | const METHOD_NONE = 0, 32 | METHOD_PBKDF2 = 1, 33 | METHOD_COINOMI = 2; 34 | const BITCOIN = lodash.find(allNetworks, ['label', 'BTC (Bitcoin, legacy, BIP32/44)']); 35 | 36 | vm.coinTypes = allNetworks; 37 | vm.coinType = BITCOIN; 38 | vm.networks = allNetworks; 39 | vm.network = BITCOIN; 40 | vm.mnemonic = null; 41 | vm.asPassword = true; 42 | vm.passphrase = null; 43 | vm.seed = null; 44 | vm.seedHex = null; 45 | vm.node = null; 46 | vm.nodeBase58 = null; 47 | vm.privKeyWif = null; 48 | vm.xPub = null; 49 | vm.address = null; 50 | vm.account = 0; 51 | vm.change = 0; 52 | vm.index = 0; 53 | vm.bips = [ 54 | { 55 | id: 0, label: 'BIP32 (Bitcoin Core)', bip: '32', hasCoinType: false, 56 | path: 'm/account\'/change\'/index', base58Prefixes: [ 57 | {public: 0x0488b21e, private: 0x0488ade4}, // xpub, xprv 58 | {public: 0x043587cf, private: 0x04358394} // tpub, tprv 59 | ], 60 | }, 61 | { 62 | id: 1, label: 'BIP44 (Legacy wallets, multi coin wallets)', bip: '44', hasCoinType: true, 63 | path: 'm/44\'/coin\'/account\'/change/index',base58Prefixes: [ 64 | {public: 0x0488b21e, private: 0x0488ade4}, // xpub, xprv 65 | {public: 0x043587cf, private: 0x04358394} // tpub, tprv 66 | ], 67 | }, 68 | { 69 | id: 2, label: 'BIP49 (SegWit P2SH-P2WPKH)', bip: '49', hasCoinType: true, 70 | path: 'm/49\'/coin\'/account\'/change/index',base58Prefixes: [ 71 | {public: 0x049d7cb2, private: 0x049d7878}, // ypub, yprv 72 | {public: 0x044a5262, private: 0x044a4e28} // upub, uprv 73 | ], 74 | }, 75 | { 76 | id: 3, label: 'BIP84 (Native SegWit bech32 P2WPKH)', bip: '84', hasCoinType: true, 77 | path: 'm/84\'/coin\'/account\'/change/index',base58Prefixes: [ 78 | {public: 0x04b24746, private: 0x04b2430c}, // zpub, zprv 79 | {public: 0x045f1cf6, private: 0x045f18bc} // vpub, vprv 80 | ], 81 | }, 82 | { 83 | id: 4, label: 'BIP86 (Native SegWit v1 bech32m P2TR)', bip: '86', hasCoinType: true, 84 | path: 'm/86\'/coin\'/account\'/change/index',base58Prefixes: [ 85 | {public: 0x0488b21e, private: 0x0488ade4}, // xpub, xprv 86 | {public: 0x043587cf, private: 0x04358394} // tpub, tprv 87 | ], 88 | }, 89 | ]; 90 | vm.selectedBip = vm.bips[1]; 91 | vm.path = calculatePath(vm.selectedBip, vm.coinType, vm.account, vm.change, vm.index); 92 | vm.customPath = '0/0'; 93 | vm.strenghteningMethods = [ 94 | { label: 'BIP39 default (like Coinomi)', id: METHOD_COINOMI }, 95 | { label: 'BIP39 custom (passhprase to hex)', id: METHOD_NONE }, 96 | { label: 'PBKDF2 (Digital Bitbox)', id: METHOD_PBKDF2 } 97 | 98 | ]; 99 | vm.strenghtening = vm.strenghteningMethods[0]; 100 | vm.seedLengths = [ 101 | { label: '128bit / 12 words', id: 128 }, 102 | { label: '160bit / 15 words', id: 160 }, 103 | { label: '192bit / 18 words', id: 192 }, 104 | { label: '224bit / 21 words', id: 224 }, 105 | { label: '256bit / 24 words', id: 256 } 106 | ]; 107 | vm.mnemonicLength = vm.seedLengths[0]; 108 | 109 | vm.$onInit = function () { 110 | vm.newSeed(); 111 | }; 112 | 113 | vm.newSeed = function () { 114 | vm.mnemonic = bitcoin.bip39.generateMnemonic(vm.mnemonicLength.id); 115 | vm.fromMnemonic(); 116 | }; 117 | 118 | vm.fromMnemonic = function () { 119 | let pw = null; 120 | if (vm.passphrase) { 121 | if (vm.strenghtening.id === METHOD_PBKDF2) { 122 | pw = bitcoin.pbkdf2.pbkdf2Sync( 123 | bitcoin.Buffer.from(vm.passphrase, 'utf8'), 124 | PBKDF2_SALT, 125 | PBKDF2_ROUNDS_APP, 126 | PBKDF2_HMACLEN, 127 | 'sha512' 128 | ).toString('hex'); 129 | } else if (vm.strenghtening.id === METHOD_COINOMI) { 130 | pw = vm.passphrase; 131 | } else { 132 | pw = bitcoin.Buffer.from(vm.passphrase, 'utf8').toString('hex'); 133 | } 134 | } 135 | vm.seed = bitcoin.bip39.mnemonicToSeed(vm.mnemonic, pw); 136 | vm.fromSeed(); 137 | }; 138 | 139 | vm.fromSeed = function () { 140 | if (vm.seed) { 141 | vm.seedHex = vm.seed.toString('hex'); 142 | 143 | vm.node = bitcoin.bip32.fromSeed(vm.seed, vm.network.config); 144 | vm.nodeBase58 = vm.node.toBase58(); 145 | vm.customParentBase58 = vm.node.toBase58(); 146 | vm.fromNode(); 147 | vm.fromCustomParent(); 148 | } 149 | }; 150 | 151 | vm.fromHexSeed = function () { 152 | vm.seed = bitcoin.Buffer.from(vm.seedHex, 'hex'); 153 | vm.mnemonic = 'Cannot be reversed! Mnemonic to seed is a one way street...'; 154 | vm.fromSeed(); 155 | }; 156 | 157 | vm.fromBase58Seed = function () { 158 | vm.error = null; 159 | try { 160 | vm.node = bitcoin.bip32.fromBase58(vm.nodeBase58, vm.network.config); 161 | vm.seed = null; 162 | vm.seedHex = 'Cannot be reversed! Seed is hashed to create HD node'; 163 | vm.mnemonic = 'Cannot be reversed! Mnemonic to seed is a one way street...'; 164 | vm.fromNode(); 165 | } catch (e) { 166 | vm.error = e; 167 | } 168 | }; 169 | 170 | vm.fromNode = function () { 171 | let configCopy = deepCopy(vm.network.config); 172 | configCopy.bip32 = vm.selectedBip.base58Prefixes[configCopy.bip44]; 173 | vm.customNode = new bitcoin.bip32.fromPrivateKey(vm.node.privateKey, vm.node.chainCode, configCopy); 174 | vm.xPub = vm.customNode.neutered().toBase58(); 175 | vm.address = getP2PKHAddress(vm.customNode, vm.network.config); 176 | vm.calculatePath(); 177 | }; 178 | 179 | vm.calculatePath = function () { 180 | vm.path = calculatePath(vm.selectedBip, vm.coinType, vm.account, vm.change, vm.index); 181 | vm.fromPath(); 182 | }; 183 | 184 | vm.fromPath = function () { 185 | vm.derivedKey = vm.customNode.derivePath(vm.path); 186 | vm.derivedXPub = vm.derivedKey.neutered().toBase58(); 187 | calculateAddresses(vm.derivedKey, vm.network.config); 188 | }; 189 | 190 | vm.fromCustomParent = function () { 191 | vm.customParentError = null; 192 | try { 193 | vm.customParent = bitcoin.bip32.fromBase58(vm.customParentBase58, vm.network.config); 194 | vm.customPath = '0/0'; 195 | vm.fromCustomPath(); 196 | } catch (e) { 197 | vm.customParentError = e; 198 | } 199 | }; 200 | 201 | vm.fromCustomPath = function () { 202 | vm.customDerivedKey = vm.customParent.derivePath(vm.customPath, vm.network.config); 203 | calculateAddresses(vm.customDerivedKey, vm.network.config); 204 | }; 205 | } 206 | -------------------------------------------------------------------------------- /pages/intro/intro.html: -------------------------------------------------------------------------------- 1 |

Cryptography Toolkit

2 | 3 | A web-based collection of cryptography tools for schemes/algorithms used in 4 | Bitcoin and LND.

5 | 6 | This toolkit has been built with educational purposes in mind!
7 | It is meant to play around with different schemes and algorithms to understand how they work.
8 | However, you must be extremely careful when using real/live/mainnet data/keys/credentials!
9 | A web browser usually is not a safe environment to either create strong cryptographic keys and/or 10 | paste sensitive information into. So consider yourself warned. 11 | 12 |

Tools

13 | 25 | 26 |

27 | by Oliver Gugger
28 | BTC tip address: bc1qfgua5vhwm6myajak9p4crhwmwm2k6mczf789eh
29 |

30 | -------------------------------------------------------------------------------- /pages/intro/intro.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('app') 3 | .component('introPage', { 4 | templateUrl: 'pages/intro/intro.html', 5 | controller: IntroPageController, 6 | controllerAs: 'vm', 7 | bindings: {} 8 | }); 9 | 10 | function IntroPageController() { 11 | var vm = this; 12 | 13 | } -------------------------------------------------------------------------------- /pages/macaroon/id-protobuf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview 3 | * @enhanceable 4 | * @suppress {messageConventions} JS Compiler reports an error if a variable or 5 | * field starts with 'MSG_' and isn't a translatable message. 6 | * @public 7 | */ 8 | // GENERATED CODE -- DO NOT EDIT! 9 | 10 | var jspb = require('google-protobuf'); 11 | var goog = jspb; 12 | var global = Function('return this')(); 13 | 14 | goog.exportSymbol('proto.MacaroonId', null, global); 15 | goog.exportSymbol('proto.Op', null, global); 16 | 17 | /** 18 | * Generated by JsPbCodeGenerator. 19 | * @param {Array=} opt_data Optional initial data array, typically from a 20 | * server response, or constructed directly in Javascript. The array is used 21 | * in place and becomes part of the constructed object. It is not cloned. 22 | * If no data is provided, the constructed object will be empty, but still 23 | * valid. 24 | * @extends {jspb.Message} 25 | * @constructor 26 | */ 27 | proto.MacaroonId = function(opt_data) { 28 | jspb.Message.initialize(this, opt_data, 0, -1, proto.MacaroonId.repeatedFields_, null); 29 | }; 30 | goog.inherits(proto.MacaroonId, jspb.Message); 31 | if (goog.DEBUG && !COMPILED) { 32 | proto.MacaroonId.displayName = 'proto.MacaroonId'; 33 | } 34 | /** 35 | * List of repeated fields within this message type. 36 | * @private {!Array} 37 | * @const 38 | */ 39 | proto.MacaroonId.repeatedFields_ = [3]; 40 | 41 | 42 | 43 | if (jspb.Message.GENERATE_TO_OBJECT) { 44 | /** 45 | * Creates an object representation of this proto suitable for use in Soy templates. 46 | * Field names that are reserved in JavaScript and will be renamed to pb_name. 47 | * To access a reserved field use, foo.pb_, eg, foo.pb_default. 48 | * For the list of reserved names please see: 49 | * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. 50 | * @param {boolean=} opt_includeInstance Whether to include the JSPB instance 51 | * for transitional soy proto support: http://goto/soy-param-migration 52 | * @return {!Object} 53 | */ 54 | proto.MacaroonId.prototype.toObject = function(opt_includeInstance) { 55 | return proto.MacaroonId.toObject(opt_includeInstance, this); 56 | }; 57 | 58 | 59 | /** 60 | * Static version of the {@see toObject} method. 61 | * @param {boolean|undefined} includeInstance Whether to include the JSPB 62 | * instance for transitional soy proto support: 63 | * http://goto/soy-param-migration 64 | * @param {!proto.MacaroonId} msg The msg instance to transform. 65 | * @return {!Object} 66 | * @suppress {unusedLocalVariables} f is only used for nested messages 67 | */ 68 | proto.MacaroonId.toObject = function(includeInstance, msg) { 69 | var f, obj = { 70 | nonce: msg.getNonce_asB64(), 71 | storageid: msg.getStorageid_asB64(), 72 | opsList: jspb.Message.toObjectList(msg.getOpsList(), 73 | proto.Op.toObject, includeInstance) 74 | }; 75 | 76 | if (includeInstance) { 77 | obj.$jspbMessageInstance = msg; 78 | } 79 | return obj; 80 | }; 81 | } 82 | 83 | 84 | /** 85 | * Deserializes binary data (in protobuf wire format). 86 | * @param {jspb.ByteSource} bytes The bytes to deserialize. 87 | * @return {!proto.MacaroonId} 88 | */ 89 | proto.MacaroonId.deserializeBinary = function(bytes) { 90 | var reader = new jspb.BinaryReader(bytes); 91 | var msg = new proto.MacaroonId; 92 | return proto.MacaroonId.deserializeBinaryFromReader(msg, reader); 93 | }; 94 | 95 | 96 | /** 97 | * Deserializes binary data (in protobuf wire format) from the 98 | * given reader into the given message object. 99 | * @param {!proto.MacaroonId} msg The message object to deserialize into. 100 | * @param {!jspb.BinaryReader} reader The BinaryReader to use. 101 | * @return {!proto.MacaroonId} 102 | */ 103 | proto.MacaroonId.deserializeBinaryFromReader = function(msg, reader) { 104 | while (reader.nextField()) { 105 | if (reader.isEndGroup()) { 106 | break; 107 | } 108 | var field = reader.getFieldNumber(); 109 | switch (field) { 110 | case 1: 111 | var value = /** @type {!Uint8Array} */ (reader.readBytes()); 112 | msg.setNonce(value); 113 | break; 114 | case 2: 115 | var value = /** @type {!Uint8Array} */ (reader.readBytes()); 116 | msg.setStorageid(value); 117 | break; 118 | case 3: 119 | var value = new proto.Op; 120 | reader.readMessage(value,proto.Op.deserializeBinaryFromReader); 121 | msg.addOps(value); 122 | break; 123 | default: 124 | reader.skipField(); 125 | break; 126 | } 127 | } 128 | return msg; 129 | }; 130 | 131 | 132 | /** 133 | * Serializes the message to binary data (in protobuf wire format). 134 | * @return {!Uint8Array} 135 | */ 136 | proto.MacaroonId.prototype.serializeBinary = function() { 137 | var writer = new jspb.BinaryWriter(); 138 | proto.MacaroonId.serializeBinaryToWriter(this, writer); 139 | return writer.getResultBuffer(); 140 | }; 141 | 142 | 143 | /** 144 | * Serializes the given message to binary data (in protobuf wire 145 | * format), writing to the given BinaryWriter. 146 | * @param {!proto.MacaroonId} message 147 | * @param {!jspb.BinaryWriter} writer 148 | * @suppress {unusedLocalVariables} f is only used for nested messages 149 | */ 150 | proto.MacaroonId.serializeBinaryToWriter = function(message, writer) { 151 | var f = undefined; 152 | f = message.getNonce_asU8(); 153 | if (f.length > 0) { 154 | writer.writeBytes( 155 | 1, 156 | f 157 | ); 158 | } 159 | f = message.getStorageid_asU8(); 160 | if (f.length > 0) { 161 | writer.writeBytes( 162 | 2, 163 | f 164 | ); 165 | } 166 | f = message.getOpsList(); 167 | if (f.length > 0) { 168 | writer.writeRepeatedMessage( 169 | 3, 170 | f, 171 | proto.Op.serializeBinaryToWriter 172 | ); 173 | } 174 | }; 175 | 176 | 177 | /** 178 | * optional bytes nonce = 1; 179 | * @return {string} 180 | */ 181 | proto.MacaroonId.prototype.getNonce = function() { 182 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); 183 | }; 184 | 185 | 186 | /** 187 | * optional bytes nonce = 1; 188 | * This is a type-conversion wrapper around `getNonce()` 189 | * @return {string} 190 | */ 191 | proto.MacaroonId.prototype.getNonce_asB64 = function() { 192 | return /** @type {string} */ (jspb.Message.bytesAsB64( 193 | this.getNonce())); 194 | }; 195 | 196 | 197 | /** 198 | * optional bytes nonce = 1; 199 | * Note that Uint8Array is not supported on all browsers. 200 | * @see http://caniuse.com/Uint8Array 201 | * This is a type-conversion wrapper around `getNonce()` 202 | * @return {!Uint8Array} 203 | */ 204 | proto.MacaroonId.prototype.getNonce_asU8 = function() { 205 | return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8( 206 | this.getNonce())); 207 | }; 208 | 209 | 210 | /** @param {!(string|Uint8Array)} value */ 211 | proto.MacaroonId.prototype.setNonce = function(value) { 212 | jspb.Message.setField(this, 1, value); 213 | }; 214 | 215 | 216 | /** 217 | * optional bytes storageId = 2; 218 | * @return {string} 219 | */ 220 | proto.MacaroonId.prototype.getStorageid = function() { 221 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); 222 | }; 223 | 224 | 225 | /** 226 | * optional bytes storageId = 2; 227 | * This is a type-conversion wrapper around `getStorageid()` 228 | * @return {string} 229 | */ 230 | proto.MacaroonId.prototype.getStorageid_asB64 = function() { 231 | return /** @type {string} */ (jspb.Message.bytesAsB64( 232 | this.getStorageid())); 233 | }; 234 | 235 | 236 | /** 237 | * optional bytes storageId = 2; 238 | * Note that Uint8Array is not supported on all browsers. 239 | * @see http://caniuse.com/Uint8Array 240 | * This is a type-conversion wrapper around `getStorageid()` 241 | * @return {!Uint8Array} 242 | */ 243 | proto.MacaroonId.prototype.getStorageid_asU8 = function() { 244 | return /** @type {!Uint8Array} */ (jspb.Message.bytesAsU8( 245 | this.getStorageid())); 246 | }; 247 | 248 | 249 | /** @param {!(string|Uint8Array)} value */ 250 | proto.MacaroonId.prototype.setStorageid = function(value) { 251 | jspb.Message.setField(this, 2, value); 252 | }; 253 | 254 | 255 | /** 256 | * repeated Op ops = 3; 257 | * @return {!Array.} 258 | */ 259 | proto.MacaroonId.prototype.getOpsList = function() { 260 | return /** @type{!Array.} */ ( 261 | jspb.Message.getRepeatedWrapperField(this, proto.Op, 3)); 262 | }; 263 | 264 | 265 | /** @param {!Array.} value */ 266 | proto.MacaroonId.prototype.setOpsList = function(value) { 267 | jspb.Message.setRepeatedWrapperField(this, 3, value); 268 | }; 269 | 270 | 271 | /** 272 | * @param {!proto.Op=} opt_value 273 | * @param {number=} opt_index 274 | * @return {!proto.Op} 275 | */ 276 | proto.MacaroonId.prototype.addOps = function(opt_value, opt_index) { 277 | return jspb.Message.addToRepeatedWrapperField(this, 3, opt_value, proto.Op, opt_index); 278 | }; 279 | 280 | 281 | proto.MacaroonId.prototype.clearOpsList = function() { 282 | this.setOpsList([]); 283 | }; 284 | 285 | 286 | 287 | /** 288 | * Generated by JsPbCodeGenerator. 289 | * @param {Array=} opt_data Optional initial data array, typically from a 290 | * server response, or constructed directly in Javascript. The array is used 291 | * in place and becomes part of the constructed object. It is not cloned. 292 | * If no data is provided, the constructed object will be empty, but still 293 | * valid. 294 | * @extends {jspb.Message} 295 | * @constructor 296 | */ 297 | proto.Op = function(opt_data) { 298 | jspb.Message.initialize(this, opt_data, 0, -1, proto.Op.repeatedFields_, null); 299 | }; 300 | goog.inherits(proto.Op, jspb.Message); 301 | if (goog.DEBUG && !COMPILED) { 302 | proto.Op.displayName = 'proto.Op'; 303 | } 304 | /** 305 | * List of repeated fields within this message type. 306 | * @private {!Array} 307 | * @const 308 | */ 309 | proto.Op.repeatedFields_ = [2]; 310 | 311 | 312 | 313 | if (jspb.Message.GENERATE_TO_OBJECT) { 314 | /** 315 | * Creates an object representation of this proto suitable for use in Soy templates. 316 | * Field names that are reserved in JavaScript and will be renamed to pb_name. 317 | * To access a reserved field use, foo.pb_, eg, foo.pb_default. 318 | * For the list of reserved names please see: 319 | * com.google.apps.jspb.JsClassTemplate.JS_RESERVED_WORDS. 320 | * @param {boolean=} opt_includeInstance Whether to include the JSPB instance 321 | * for transitional soy proto support: http://goto/soy-param-migration 322 | * @return {!Object} 323 | */ 324 | proto.Op.prototype.toObject = function(opt_includeInstance) { 325 | return proto.Op.toObject(opt_includeInstance, this); 326 | }; 327 | 328 | 329 | /** 330 | * Static version of the {@see toObject} method. 331 | * @param {boolean|undefined} includeInstance Whether to include the JSPB 332 | * instance for transitional soy proto support: 333 | * http://goto/soy-param-migration 334 | * @param {!proto.Op} msg The msg instance to transform. 335 | * @return {!Object} 336 | * @suppress {unusedLocalVariables} f is only used for nested messages 337 | */ 338 | proto.Op.toObject = function(includeInstance, msg) { 339 | var f, obj = { 340 | entity: jspb.Message.getFieldWithDefault(msg, 1, ""), 341 | actionsList: jspb.Message.getRepeatedField(msg, 2) 342 | }; 343 | 344 | if (includeInstance) { 345 | obj.$jspbMessageInstance = msg; 346 | } 347 | return obj; 348 | }; 349 | } 350 | 351 | 352 | /** 353 | * Deserializes binary data (in protobuf wire format). 354 | * @param {jspb.ByteSource} bytes The bytes to deserialize. 355 | * @return {!proto.Op} 356 | */ 357 | proto.Op.deserializeBinary = function(bytes) { 358 | var reader = new jspb.BinaryReader(bytes); 359 | var msg = new proto.Op; 360 | return proto.Op.deserializeBinaryFromReader(msg, reader); 361 | }; 362 | 363 | 364 | /** 365 | * Deserializes binary data (in protobuf wire format) from the 366 | * given reader into the given message object. 367 | * @param {!proto.Op} msg The message object to deserialize into. 368 | * @param {!jspb.BinaryReader} reader The BinaryReader to use. 369 | * @return {!proto.Op} 370 | */ 371 | proto.Op.deserializeBinaryFromReader = function(msg, reader) { 372 | while (reader.nextField()) { 373 | if (reader.isEndGroup()) { 374 | break; 375 | } 376 | var field = reader.getFieldNumber(); 377 | switch (field) { 378 | case 1: 379 | var value = /** @type {string} */ (reader.readString()); 380 | msg.setEntity(value); 381 | break; 382 | case 2: 383 | var value = /** @type {string} */ (reader.readString()); 384 | msg.addActions(value); 385 | break; 386 | default: 387 | reader.skipField(); 388 | break; 389 | } 390 | } 391 | return msg; 392 | }; 393 | 394 | 395 | /** 396 | * Serializes the message to binary data (in protobuf wire format). 397 | * @return {!Uint8Array} 398 | */ 399 | proto.Op.prototype.serializeBinary = function() { 400 | var writer = new jspb.BinaryWriter(); 401 | proto.Op.serializeBinaryToWriter(this, writer); 402 | return writer.getResultBuffer(); 403 | }; 404 | 405 | 406 | /** 407 | * Serializes the given message to binary data (in protobuf wire 408 | * format), writing to the given BinaryWriter. 409 | * @param {!proto.Op} message 410 | * @param {!jspb.BinaryWriter} writer 411 | * @suppress {unusedLocalVariables} f is only used for nested messages 412 | */ 413 | proto.Op.serializeBinaryToWriter = function(message, writer) { 414 | var f = undefined; 415 | f = message.getEntity(); 416 | if (f.length > 0) { 417 | writer.writeString( 418 | 1, 419 | f 420 | ); 421 | } 422 | f = message.getActionsList(); 423 | if (f.length > 0) { 424 | writer.writeRepeatedString( 425 | 2, 426 | f 427 | ); 428 | } 429 | }; 430 | 431 | 432 | /** 433 | * optional string entity = 1; 434 | * @return {string} 435 | */ 436 | proto.Op.prototype.getEntity = function() { 437 | return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); 438 | }; 439 | 440 | 441 | /** @param {string} value */ 442 | proto.Op.prototype.setEntity = function(value) { 443 | jspb.Message.setField(this, 1, value); 444 | }; 445 | 446 | 447 | /** 448 | * repeated string actions = 2; 449 | * @return {!Array.} 450 | */ 451 | proto.Op.prototype.getActionsList = function() { 452 | return /** @type {!Array.} */ (jspb.Message.getRepeatedField(this, 2)); 453 | }; 454 | 455 | 456 | /** @param {!Array.} value */ 457 | proto.Op.prototype.setActionsList = function(value) { 458 | jspb.Message.setField(this, 2, value || []); 459 | }; 460 | 461 | 462 | /** 463 | * @param {!string} value 464 | * @param {number=} opt_index 465 | */ 466 | proto.Op.prototype.addActions = function(value, opt_index) { 467 | jspb.Message.addToRepeatedField(this, 2, value, opt_index); 468 | }; 469 | 470 | 471 | proto.Op.prototype.clearActionsList = function() { 472 | this.setActionsList([]); 473 | }; 474 | 475 | 476 | goog.object.extend(exports, proto); 477 | -------------------------------------------------------------------------------- /pages/macaroon/id.proto: -------------------------------------------------------------------------------- 1 | syntax="proto3"; 2 | 3 | option go_package = "macaroonpb"; 4 | 5 | message MacaroonId { 6 | bytes nonce = 1; 7 | bytes storageId = 2; 8 | repeated Op ops = 3; 9 | } 10 | 11 | message Op { 12 | string entity = 1; 13 | repeated string actions = 2; 14 | } 15 | -------------------------------------------------------------------------------- /pages/macaroon/macaroon.html: -------------------------------------------------------------------------------- 1 |

Macaroons

2 | 3 |
4 |
5 |

6 | Explanation 7 |

8 |
9 |
10 |
11 | Macaroons are Cookies with Contextual Caveats for Decentralized Authorization in the Cloud.

12 | They are used, for example, in the lnd implementation of the Lightning Network.
13 | A Caveat (or First Party Caveat) is a condition that is either added by the issuer of the 14 | macaroon or the user of the caveat. Because of the used cryptographic one-way function (HMAC), conditions can be added 15 | by anyone holding the macaroon, but nobody can remove any condition.
16 | That way, a user can further restrict the access rights of a macaroon that she obtained (for example, add a condition that 17 | the macaroon is only valid for the next 3 seconds while transmitting it over the internet and therefore restricting 18 | a potential eavesdropper's chance of using a stolen macaroon).
19 | The issuer of the macaroon (who is the holder of the private root key) can verify a signature even if further caveats have 20 | been added.

21 | Third Party Caveats are conditions that have to be met by a third party. For example, a node operator wants 22 | to give all users of her website limited access to her LND node. She would then set up the LND node and the website with a 23 | Shared Key. The LND node would only issue macaroons that have a Third Party Caveat added for the website.
24 | This basically tells the macaroon validator that "this macaroon is only valid if the user can also present a discharge macaroon 25 | from the service website".
26 | A user that is logged in to the website would then get a discharge macaroon that basically states "I have been authorized by the 27 | service website" and can prove that cryptographically.
28 | When the user wants to connect to the LND node and use its functionality, she would present both macaroons to the node that can 29 | then verify they both are valid, bound to each other and meet all conditions. 30 | 31 | 32 |

Sources, tools and other useful information:

33 | 37 |
38 |
39 |
40 | 41 |

Create macaroon

42 |
43 |
44 | 45 | 46 |
47 | 48 |
49 | 54 | <-- paste hex 55 | {{vm.error2}} 56 | 57 | 58 | 59 |
60 |
61 | 62 | 63 |
64 | 65 |
66 | 67 |
68 |
69 | 70 | 71 |
72 | 73 |
74 | 75 |
76 |
77 | 78 | 79 |
80 | 81 |
82 | 83 | 84 | 85 | 86 |
87 |
88 |
89 |
90 |
91 | 92 | 95 | 98 |
99 |
100 |
101 | 102 | 103 |
104 | 105 |
106 |
107 |
Shared Root key (hex):
108 | 112 | <-- paste hex 113 | {{vm.error4}} 114 | 115 | 116 | 117 |
118 |
119 |
Identifier:
120 | 121 |
122 |
123 |
Location:
124 | 125 |
126 |
127 |
128 | 129 | 130 |
131 | 132 |
133 | 134 |
135 |
136 | 137 |
138 | 139 |
140 | 143 |
144 | Show as JSON 145 |
146 |
147 |
148 |
149 | 150 |

Decode macaroon

151 |
152 |
153 | 154 |
155 | 156 |
157 | 162 | <-- paste here to decode 163 | {{vm.error}} 164 |
165 |
166 | 167 |
168 | 169 |
170 | 171 | Try to decode identifier 172 |
173 |
174 | 175 | 176 |
177 | 178 |
179 | 184 | <-- paste hex 185 | {{vm.error3}} 186 |
187 |
188 | 189 | 190 |
191 | 192 |
193 | 197 | <-- paste hex 198 |
199 |
200 |
201 |
202 | -------------------------------------------------------------------------------- /pages/macaroon/macaroon.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('app') 3 | .component('macaroonPage', { 4 | templateUrl: 'pages/macaroon/macaroon.html', 5 | controller: MacaroonPageController, 6 | controllerAs: 'vm', 7 | bindings: {} 8 | }); 9 | 10 | function MacaroonPageController() { 11 | const vm = this; 12 | const Buffer = bitcoin.Buffer; 13 | const macaroon = bitcoin.macaroon; 14 | const randomBuffer = (len) => Buffer.from(bitcoin.randomBytes(len)); 15 | 16 | vm.macaroon = null; 17 | vm.macaroon2 = null; 18 | vm.showJson = true; 19 | vm.tryDecodingId = true; 20 | vm.identifier = 'demo-identifier'; 21 | vm.location = 'https://some.location'; 22 | vm.caveats = ['ip = 127.0.0.1']; 23 | vm.rootKey = null; 24 | vm.encodedMacaroon = ''; 25 | vm.verificationRootKey = ''; 26 | vm.thirdPartyMac = null; 27 | vm.verificationDischarge = ''; 28 | 29 | vm.$onInit = function () { 30 | vm.randomRootKey(); 31 | }; 32 | 33 | vm.randomRootKey = function () { 34 | vm.rootKey = randomBuffer(32).toString('hex'); 35 | vm.newMacaroon(); 36 | }; 37 | 38 | vm.newMacaroon = function () { 39 | vm.error2 = null; 40 | try { 41 | const keyBytes = Buffer.from(vm.rootKey, 'hex'); 42 | vm.macaroon2 = macaroon.newMacaroon({ identifier: vm.identifier, location: vm.location, rootKey: keyBytes, version: 2 }); 43 | vm.caveats.forEach(c => vm.macaroon2.addFirstPartyCaveat(c)); 44 | if (vm.thirdPartyMac) { 45 | const thirdPartyKeyBytes = Buffer.from(vm.thirdPartyMac.rootKey, 'hex'); 46 | vm.macaroon2.addThirdPartyCaveat(thirdPartyKeyBytes, vm.thirdPartyMac.identifier, vm.thirdPartyMac.location); 47 | 48 | vm.thirdPartyMac.macaroon = macaroon.newMacaroon({ 49 | identifier: vm.thirdPartyMac.identifier, 50 | location: vm.thirdPartyMac.location, 51 | rootKey: thirdPartyKeyBytes, 52 | version: 2, 53 | }); 54 | vm.thirdPartyMac.macaroon.bindToRoot(vm.macaroon2.signature); 55 | } 56 | } catch (e) { 57 | vm.error2 = e; 58 | } 59 | }; 60 | 61 | vm.serializeMacaroon = function (mac, asJson) { 62 | if (!mac) { 63 | return ''; 64 | } 65 | if (asJson) { 66 | const macJson = mac.exportJSON(); 67 | if (macJson.i64 && vm.tryDecodingId) { 68 | try { 69 | const identBytes = Buffer.from(macaroon.base64ToBytes(macJson.i64)); 70 | if (identBytes[0] === 0x03) { 71 | const id = bitcoin.macaroonIdProtobuf.MacaroonId.deserializeBinary(identBytes.slice(1)); 72 | macJson.identifier_decoded = { 73 | nonce: Buffer.from(id.getNonce_asU8()).toString('hex'), 74 | storageId: Buffer.from(id.getStorageid_asU8()).toString('hex'), 75 | ops: id.getOpsList().map(op => ({ 76 | entity: op.getEntity(), 77 | actions: op.getActionsList(), 78 | })), 79 | }; 80 | } 81 | } catch (e) { 82 | } 83 | } 84 | return JSON.stringify(macJson, null, 2); 85 | } else { 86 | return Buffer.from(mac.exportBinary()).toString('hex'); 87 | } 88 | }; 89 | 90 | vm.removeCaveat = function (index) { 91 | vm.caveats.splice(index, 1); 92 | vm.newMacaroon(); 93 | }; 94 | 95 | vm.addCaveat = function () { 96 | vm.caveats.push('condition = value'); 97 | vm.newMacaroon(); 98 | }; 99 | 100 | vm.decodeMacaroon = function () { 101 | vm.error = null; 102 | if (!vm.encodedMacaroon) { 103 | return; 104 | } 105 | try { 106 | const buffer = Buffer.from(vm.encodedMacaroon.replace(/\s*/gi, ''), 'hex'); 107 | vm.macaroon = macaroon.importMacaroon(buffer); 108 | } catch (e) { 109 | vm.error = e; 110 | } 111 | }; 112 | 113 | vm.verifyMacaroon = function () { 114 | vm.error3 = null; 115 | vm.valid = false; 116 | if (!vm.verificationRootKey) { 117 | return; 118 | } 119 | try { 120 | const buffer = Buffer.from(vm.verificationRootKey, 'hex'); 121 | const dischargeMacaroons = []; 122 | if (vm.verificationDischarge) { 123 | const dmBuffer = Buffer.from(vm.verificationDischarge.replace(/\s*/gi, ''), 'hex'); 124 | dischargeMacaroons.push(macaroon.importMacaroon(dmBuffer)); 125 | } 126 | vm.macaroon.verify(buffer, () => null, dischargeMacaroons); 127 | vm.valid = true; 128 | } catch (e) { 129 | vm.error3 = e; 130 | } 131 | }; 132 | 133 | vm.addThirdPartyCaveat = function () { 134 | vm.thirdPartyMac = { 135 | identifier: 'other-party', 136 | location: 'http://other.party' 137 | }; 138 | vm.randomTpmRootKey(); 139 | }; 140 | 141 | vm.removeThirdPartyCaveat = function () { 142 | vm.thirdPartyMac = null; 143 | vm.newMacaroon(); 144 | }; 145 | 146 | vm.randomTpmRootKey = function () { 147 | vm.thirdPartyMac.rootKey = randomBuffer(32).toString('hex'); 148 | vm.newMacaroon(); 149 | }; 150 | } 151 | -------------------------------------------------------------------------------- /pages/mu-sig/mu-sig.html: -------------------------------------------------------------------------------- 1 |

MuSig: Key Aggregation for Schnorr Signatures

2 | 3 |
4 |
5 |

6 | Explanation 7 |

8 |
9 |
10 |
11 | MuSig is a key aggregation scheme for Schnorr signatures that is secured agains rogue-key-attacks. 12 | 13 |

Sources, tools and other useful information:

14 | 19 |
20 |
21 |
22 | 23 |

Interactive demo

24 | 25 |

Message and participant's key pairs

26 |
27 |
28 | 29 | 30 |
31 | 32 |
33 | 34 |
35 |
Hash
36 | 37 |
38 |
39 |
40 | 41 | 42 |
43 | 49 |
50 |
51 |
Private key
52 | 53 | <-- paste hex 54 | 55 | 56 | 57 |
58 |
59 |
Public key
60 | 61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | 73 |

74 | After you have set up the key pairs, click the following button to step through the signing process.
75 | Observe how the public and private data changes after each step.
76 | To reset the demo, please reload the page. 77 |

78 | 79 | 80 | 81 |

Public data

82 | This represents data that is known to all participants. In fact, every participant will calculate/store these values during the signing session. 83 |
84 |
85 |
86 |
87 | 88 |
89 |
90 |
91 |
92 | 93 |

Signer private data

94 | 95 | This represents data that is only known by the individual party and is never shared between the signers! 96 | So for example, the owner of the key pair 2 only knows the data shown here at index 1. 97 | 98 |
99 |
100 |
101 |
102 | 103 |
104 |
105 |
106 |
107 | -------------------------------------------------------------------------------- /pages/mu-sig/mu-sig.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('app') 3 | .component('muSigPage', { 4 | templateUrl: 'pages/mu-sig/mu-sig.html', 5 | controller: MuSigPageController, 6 | controllerAs: 'vm', 7 | bindings: {} 8 | }); 9 | 10 | function MuSigPageController(lodash, bitcoinNetworks) { 11 | const vm = this; 12 | const schnorr = bitcoin.schnorr; 13 | const muSig = schnorr.muSig; 14 | const Buffer = bitcoin.Buffer; 15 | const BigInteger = bitcoin.BigInteger; 16 | const network = lodash.find(bitcoinNetworks, ['label', 'BTC (Bitcoin, legacy, BIP32/44)']).config; 17 | const randomBuffer = (len) => Buffer.from(bitcoin.randomBytes(len)); 18 | 19 | vm.steps = [ 20 | {label: 'Step 1: Combine public keys', action: 'toStep1'}, 21 | {label: 'Step 2: Initialize sessions', action: 'toStep2'}, 22 | {label: 'Step 3: Exchange commitments', action: 'toStep3'}, 23 | {label: 'Step 4: Get public nonces', action: 'toStep4'}, 24 | {label: 'Step 5: Combine nonces', action: 'toStep5'}, 25 | {label: 'Step 6: Generate partial signatures', action: 'toStep6'}, 26 | {label: 'Step 7: Exchange partial signatures', action: 'toStep7'}, 27 | {label: 'Step 8: Combine partial signatures', action: 'toStep8'}, 28 | {label: 'Finished!', action: null}, 29 | ]; 30 | vm.message = 'Schnorr Signatures are awesome!'; 31 | vm.keyPairs = []; 32 | 33 | vm.step = 0; 34 | vm.publicData = { 35 | pubKeys: [], 36 | message: null, 37 | pubKeyHash: null, 38 | pubKeyCombined: null, 39 | pkParity: null, 40 | commitments: [], 41 | nonces: [], 42 | nonceCombined: null, 43 | partialSignatures: [], 44 | signature: null 45 | }; 46 | 47 | vm.signerPrivateData = []; 48 | vm.signerSession = null; 49 | 50 | vm.$onInit = function () { 51 | // initially, there are 2 key paris 52 | vm.newPrivateKey(); 53 | vm.newPrivateKey(); 54 | vm.hashMessage(); 55 | }; 56 | 57 | function bufferToString(val, key) { 58 | if (Buffer.isBuffer(val)) { 59 | return val.toString('hex'); 60 | } else if (BigInteger.isBigInteger(val)) { 61 | return val.toString(16); 62 | } else if (lodash.isArray(val)) { 63 | return val.map(bufferToString); 64 | } else { 65 | return val; 66 | } 67 | } 68 | 69 | vm.hexEncoded = function (obj) { 70 | return lodash.deeply(lodash.mapValues)(angular.copy(obj), bufferToString); 71 | }; 72 | 73 | vm.newPrivateKey = function () { 74 | const keyPair = bitcoin.ECPair.makeRandom(); 75 | keyPair.privateKeyHex = keyPair.privateKey.toString('hex'); 76 | keyPair.publicKeyHex = bitcoin.tinySecp256k1.pointCompress(keyPair.publicKey).slice(1, 33).toString('hex'); 77 | vm.keyPairs.push(keyPair); 78 | vm.keyPairsChanged(); 79 | }; 80 | 81 | vm.updateKeyPair = function (index) { 82 | const newPrivKey = bitcoin.Buffer.from(vm.keyPairs[index].privateKeyHex, 'hex'); 83 | vm.setPrivateKey(index, newPrivKey); 84 | }; 85 | 86 | vm.randomKeyPair = function (index) { 87 | const newPrivKey = bitcoin.ECPair.makeRandom().privateKey; 88 | vm.setPrivateKey(index, newPrivKey); 89 | }; 90 | 91 | vm.removeKeyPair = function (index) { 92 | vm.keyPairs.splice(index, 1); 93 | vm.keyPairsChanged(); 94 | }; 95 | 96 | vm.setPrivateKey = function (index, newPrivKey) { 97 | const keyPair = bitcoin.ECPair.fromPrivateKey(newPrivKey, null, { compressed: true, network: network }); 98 | keyPair.privateKeyHex = newPrivKey.toString('hex'); 99 | keyPair.publicKeyHex = bitcoin.tinySecp256k1.pointCompress(keyPair.publicKey).slice(1, 33).toString('hex'); 100 | vm.keyPairs[index] = keyPair; 101 | vm.keyPairsChanged(); 102 | }; 103 | 104 | vm.hashMessage = function () { 105 | vm.publicData.message = bitcoin.crypto.sha256(vm.message); 106 | }; 107 | 108 | vm.keyPairsChanged = function () { 109 | // public data 110 | vm.publicData.pubKeys = vm.keyPairs.map(p => p.publicKeyHex); 111 | 112 | // private data 113 | vm.signerPrivateData = vm.keyPairs.map((p, index) => ({ 114 | onlyKnownBy: 'Key pair ' + (index + 1), 115 | privateKey: BigInteger.fromBuffer(p.privateKey), 116 | session: null 117 | })); 118 | }; 119 | 120 | vm.nextStep = function () { 121 | const fn = vm[vm.steps[vm.step].action]; 122 | fn(); 123 | vm.step++; 124 | }; 125 | 126 | vm.toStep1 = function () { 127 | const pubKeyBuffers = vm.publicData.pubKeys.map(pk => Buffer.from(pk, 'hex')); 128 | vm.publicData.pubKeyHash = muSig.computeEll(pubKeyBuffers); 129 | const pkCombined = muSig.pubKeyCombine(pubKeyBuffers, vm.publicData.pubKeyHash); 130 | vm.publicData.pubKeyCombined = schnorr.convert.intToBuffer(pkCombined.affineX); 131 | vm.publicData.pkParity = schnorr.math.isEven(pkCombined); 132 | }; 133 | 134 | vm.toStep2 = function () { 135 | vm.signerPrivateData.forEach((data, idx) => { 136 | const sessionId = randomBuffer(32); // must never be reused between sessions! 137 | data.session = muSig.sessionInitialize( 138 | sessionId, 139 | data.privateKey, 140 | vm.publicData.message, 141 | vm.publicData.pubKeyCombined, 142 | vm.publicData.pkParity, 143 | vm.publicData.pubKeyHash, 144 | idx 145 | ); 146 | }); 147 | vm.signerSession = vm.signerPrivateData[0].session; 148 | vm.signerSession.isSignerSession = true; 149 | }; 150 | 151 | vm.toStep3 = function () { 152 | for (let i = 0; i < vm.publicData.pubKeys.length; i++) { 153 | vm.publicData.commitments[i] = vm.signerPrivateData[i].session.commitment; 154 | } 155 | }; 156 | 157 | vm.toStep4 = function () { 158 | for (let i = 0; i < vm.publicData.pubKeys.length; i++) { 159 | vm.publicData.nonces[i] = vm.signerPrivateData[i].session.nonce; 160 | } 161 | }; 162 | 163 | vm.toStep5 = function () { 164 | vm.publicData.nonceCombined = muSig.sessionNonceCombine(vm.signerSession, vm.publicData.nonces); 165 | vm.signerPrivateData.forEach(data => (data.session.combinedNonceParity = vm.signerSession.combinedNonceParity)); 166 | }; 167 | 168 | vm.toStep6 = function () { 169 | vm.signerPrivateData.forEach(data => { 170 | data.session.partialSignature = muSig.partialSign( 171 | data.session, 172 | vm.publicData.message, 173 | vm.publicData.nonceCombined, 174 | vm.publicData.pubKeyCombined 175 | ); 176 | }); 177 | }; 178 | 179 | vm.toStep7 = function () { 180 | for (let i = 0; i < vm.publicData.pubKeys.length; i++) { 181 | vm.publicData.partialSignatures[i] = vm.signerPrivateData[i].session.partialSignature; 182 | } 183 | }; 184 | 185 | vm.toStep8 = function () { 186 | vm.publicData.signature = muSig.partialSigCombine(vm.publicData.nonceCombined, vm.publicData.partialSignatures); 187 | }; 188 | } 189 | -------------------------------------------------------------------------------- /pages/schnorr/schnorr.html: -------------------------------------------------------------------------------- 1 |

BIP Schnorr Signatures

2 | 3 |
4 |
5 |

6 | Explanation 7 |

8 |
9 |
10 |
11 | Schnorr Signatures are a form of Digital Signature Algorithms (DSA) that produces short signatures even when combining multiple 12 | public keys (when using for multisig).

13 | Currently there is a BIP that has no number assigned yet that introduces a specific signature scheme for Schnorr that produces 14 | 64-byte signatures over the elliptic curve secp256k1.
15 | This demo page shows how this BIP could look like when implemented in Bitcoin. 16 | 17 |

Sources, tools and other useful information:

18 | 23 |
24 |
25 |
26 | 27 |

Sign message

28 |
29 |
30 | 31 | 32 |
33 | 34 |
35 |
36 |
Private key
37 | 38 |
39 |
40 |
Public key
41 | 42 |
43 |
44 |
45 | 46 | 47 |
48 | 49 |
50 | 51 |
52 |
53 | 54 | 55 |
56 | 57 |
58 | 59 |
60 |
61 | 62 | 63 |
64 | 65 |
66 | 67 |
68 |
69 | 70 | 71 |
72 | 73 |
74 | 75 | Signature size improvement: {{$root.round(vm.sizeImprovement, 1)}}% 76 |
77 |
78 |
79 |
80 | 81 |

Verify Schnorr signature

82 |
83 |
84 |
85 | 86 |
87 | 91 |
92 |
93 | 94 |
95 | 96 |
97 | 101 |
102 |
103 | 104 |
105 | 106 |
107 | 111 |
112 |
113 |
114 |
115 | -------------------------------------------------------------------------------- /pages/schnorr/schnorr.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('app') 3 | .component('schnorrPage', { 4 | templateUrl: 'pages/schnorr/schnorr.html', 5 | controller: SchnorrPageController, 6 | controllerAs: 'vm', 7 | bindings: {} 8 | }); 9 | 10 | function SchnorrPageController(lodash, bitcoinNetworks) { 11 | const vm = this; 12 | const HASH_TYPE = bitcoin.Transaction.SIGHASH_ALL; 13 | 14 | vm.network = lodash.find(bitcoinNetworks, ['label', 'BTC (Bitcoin, legacy, BIP32/44)']); 15 | vm.keyPair = null; 16 | vm.privateKey = null; 17 | vm.publicKey = null; 18 | vm.message = 'Schnorr Signatures are awesome!'; 19 | 20 | vm.$onInit = function () { 21 | vm.newPrivateKey(); 22 | }; 23 | 24 | vm.newPrivateKey = function () { 25 | vm.keyPair = bitcoin.ECPair.makeRandom(); 26 | vm.privateKey = vm.keyPair.privateKey.toString('hex'); 27 | vm.publicKey = vm.keyPair.publicKey.toString('hex'); 28 | vm.publicKeyToVerify = vm.publicKey.substring(2, 66); 29 | vm.signMessage(); 30 | }; 31 | 32 | vm.signMessage = function () { 33 | vm.messageHash = bitcoin.crypto.sha256(vm.message); 34 | vm.messageHashToVerify = vm.messageHash.toString('hex'); 35 | 36 | const privKeyInt = bitcoin.BigInteger.fromBuffer(vm.keyPair.privateKey); 37 | vm.signature = bitcoin.schnorr.sign(privKeyInt, bitcoin.Buffer.from(vm.messageHashToVerify, 'hex')).toString('hex'); 38 | const sig = vm.keyPair.sign(bitcoin.Buffer.from(vm.messageHashToVerify, 'hex')); 39 | vm.ecdsaSignature = bitcoin.script.signature.encode(sig, HASH_TYPE).toString('hex'); 40 | vm.sizeImprovement = 100 - ((vm.signature.length / vm.ecdsaSignature.length) * 100); 41 | vm.signatureToVerify = vm.signature; 42 | vm.verifySignature(); 43 | }; 44 | 45 | vm.verifySignature = function () { 46 | vm.signatureValid = false; 47 | try { 48 | const publicKey = bitcoin.Buffer.from(vm.publicKeyToVerify, 'hex'); 49 | const hash = bitcoin.Buffer.from(vm.messageHashToVerify, 'hex'); 50 | const signature = bitcoin.Buffer.from(vm.signatureToVerify, 'hex'); 51 | bitcoin.schnorr.verify(publicKey, hash, signature); 52 | vm.signatureValid = true; 53 | } catch (e) { 54 | vm.signatureValid = false; 55 | } 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /pages/shamir-secret-sharing/shamir-secret-sharing.html: -------------------------------------------------------------------------------- 1 |

Shamir's Secret Sharing Scheme

2 | 3 |
4 |
5 |

6 | Explanation 7 |

8 |
9 |
10 |
11 | Shamir's Secret Sharing Scheme is a way to split a secret (a password, or private key for example) 12 | into multiple parts, but only some parts (for example 5 out of 10) are needed to re-create the secret. 13 | 14 |

Sources, tools and other useful information:

15 | 19 |
20 |
21 |
22 | 23 |

Split secret into shares

24 |
25 |
26 | 27 | 28 |
29 | 30 |
31 | 32 | 33 | 34 | 35 |
36 |
37 | 38 | 39 |
40 | 41 |
42 |
Number of shares
43 | 48 |
Shares required to reconstruct
49 | 54 |
Padding length
55 | 60 |
61 |
62 |
{{vm.error}}
63 |
64 |
65 | 66 | 67 |
68 | 69 |
70 |
71 |
Share {{$index + 1}}
72 | 75 |
76 |
77 |
78 |
79 |
80 | 81 |

Combine shares into secret

82 |
83 |
84 | 85 | 86 |
87 | 88 |
89 | 96 |
97 |
98 |
{{vm.error2}}
99 |
100 |
101 | 102 | 103 |
104 | 105 |
106 | 107 |
108 |
109 |
110 |
111 | -------------------------------------------------------------------------------- /pages/shamir-secret-sharing/shamir-secret-sharing.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('app') 3 | .component('shamirSecretSharingPage', { 4 | templateUrl: 'pages/shamir-secret-sharing/shamir-secret-sharing.html', 5 | controller: ShamirSecretSharingPageController, 6 | controllerAs: 'vm', 7 | bindings: {} 8 | }); 9 | 10 | function ShamirSecretSharingPageController() { 11 | var vm = this; 12 | 13 | vm.secret = null; 14 | vm.numShares = 5; 15 | vm.sharesNeeded = 3; 16 | vm.shares = []; 17 | vm.shareLines = null; 18 | vm.paddingLengths = [ 19 | { label: '64bit', id: 64 }, 20 | { label: '128bit', id: 128 }, 21 | { label: '256bit', id: 256 }, 22 | { label: '512bit', id: 512 }, 23 | { label: '1024bit', id: 1024 } 24 | ]; 25 | vm.minPad = vm.paddingLengths[1]; 26 | vm.error = null; 27 | vm.error2 = null; 28 | 29 | vm.$onInit = function () { 30 | vm.newSecret(); 31 | }; 32 | 33 | vm.newSecret = function () { 34 | vm.secret = bitcoin.bip39.generateMnemonic(); 35 | vm.generateShares(); 36 | }; 37 | 38 | vm.generateShares = function () { 39 | var hex = bitcoin.Buffer.from(vm.secret, 'utf-8').toString('hex'); 40 | vm.error = null; 41 | try { 42 | vm.shares = bitcoin.secrets.share(hex, parseInt(vm.numShares, 10), parseInt(vm.sharesNeeded, 10), parseInt(vm.minPad.id, 10)); 43 | } catch (e) { 44 | vm.error = e; 45 | } 46 | }; 47 | 48 | vm.parseShares = function () { 49 | vm.error2 = null; 50 | try { 51 | var lines = vm.shareLines.split(/\s+/g); 52 | vm.combinedSecret = bitcoin.Buffer.from(bitcoin.secrets.combine(lines), 'hex').toString('utf-8'); 53 | } catch (e) { 54 | vm.error2 = e; 55 | } 56 | }; 57 | } 58 | -------------------------------------------------------------------------------- /pages/transaction-creator/transaction-creator.html: -------------------------------------------------------------------------------- 1 |

Transaction Creator

2 | 3 |
4 |
5 |

6 | Explanation 7 |

8 |
9 |
10 |
11 | On this page you can create a transaction for any network/blockchain that is configured.
12 | The idea is that you can use this tool to create any valid transaction offline and then just 13 | submit it to an online wallet. This way your private key can stay offline (only if you download this tool 14 | and use if offline of course!). 15 | 16 |

Sources, tools and other useful information:

17 | 31 |
32 |
33 |
34 | 35 |
36 | Warning: This is meant as a playground only! You should only use testnet keys and never paste real private keys 37 | into any web page! 38 |
39 | 40 |

Set target network and private key

41 |
42 |
43 | 44 | 45 |
46 | 47 |
48 | 52 |
53 |
54 | 55 | 56 |
57 | 58 |
59 |
64 | <-- paste compressed/uncompressed key here 65 | {{vm.error}} 66 |
67 |
68 | 69 | 70 |
71 | 72 |
73 |
74 |
P2PKH
75 | 76 |
77 |
78 |
79 | 80 | 81 |
82 | 83 |
84 |
85 |
P2SH-P2WPKH
86 | 87 |
bech32 P2WPKH
88 | 89 |
90 |
91 |
92 |
93 |
94 | 95 |
96 |

Input

97 |
98 |
99 | 100 | 101 |
102 | 103 |
104 |
105 |
Transaction ID (txId)
106 | 110 |
111 |
112 |
Index (vout)
113 | 117 |
118 |
119 |
Input is SegWit transaction:
120 |
121 | 122 |
123 | 126 |
127 |
128 |
Expected unspent amount in Satoshi (used for fee calculation)
129 | 133 |
134 |
135 |
136 |
137 |
138 | 139 |

Outputs

140 |
141 |
142 | 143 | 144 |
145 | 146 |
147 |
148 |
Any address
149 | 153 |
Amount (satoshi!)
154 | 158 |
159 |
160 |
161 | 162 | 163 |
164 | 165 |
166 |
167 |
Send change to address:
168 |
169 | 170 |
171 | 174 |
175 |
176 |
Change address
177 | 181 |
Amount (satoshi!)
182 | 186 |
187 |
188 |
189 |
Calculated fee (satoshi)
190 | 194 |
Calculated fee (satoshi/byte)
195 | 199 |
200 |
201 |
202 |
203 |
204 |
205 | 206 |

Outputs

207 |
208 |
209 | 210 | 211 |
212 | 213 |
214 | 220 |
221 |
222 | 223 | 224 |
225 | 226 |
227 | 228 |
229 |
230 |
231 |
232 |
233 | -------------------------------------------------------------------------------- /pages/transaction-creator/transaction-creator.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('app') 3 | .component('transactionCreatorPage', { 4 | templateUrl: 'pages/transaction-creator/transaction-creator.html', 5 | controller: TransactionCreatorPageController, 6 | controllerAs: 'vm', 7 | bindings: {} 8 | }); 9 | 10 | function TransactionCreatorPageController(lodash, allNetworks) { 11 | const vm = this; 12 | 13 | vm.networks = allNetworks; 14 | vm.network = lodash.find(vm.networks, ['label', 'BTC (Bitcoin Testnet, legacy, BIP32/44)']); 15 | vm.keyPair = {}; 16 | vm.keyValid = false; 17 | vm.inputTxVout = 0; 18 | vm.inputAmount = 0; 19 | vm.outputAmount = 0; 20 | vm.changeAmount = 0; 21 | vm.inputSegwit = false; 22 | vm.useChange = true; 23 | 24 | vm.importFromWif = function () { 25 | vm.error = null; 26 | vm.keyValid = false; 27 | const network = vm.network.config; 28 | try { 29 | vm.keyPair = bitcoin.ECPair.fromWIF(vm.keyPair.wif, network); 30 | vm.keyValid = true; 31 | vm.formatKeyForNetwork(); 32 | } catch (e) { 33 | console.error(e); 34 | vm.error = e; 35 | } 36 | }; 37 | 38 | vm.formatKeyForNetwork = function () { 39 | const network = vm.network.config; 40 | vm.keyPair.address = getP2PKHAddress(vm.keyPair, network); 41 | vm.keyPair.wif = customToWIF(vm.keyPair, network); 42 | if (network.bech32) { 43 | vm.keyPair.nestedP2WPKHAddress = getNestedP2WPKHAddress(vm.keyPair, network); 44 | vm.keyPair.P2WPKHAddress = getP2WPKHAddress(vm.keyPair, network); 45 | } 46 | vm.changeAddress = vm.keyPair.P2WPKHAddress; 47 | }; 48 | 49 | vm.calculateFee = function () { 50 | vm.feeError = null; 51 | try { 52 | var unspent = parseInt(vm.inputAmount, 10); 53 | var sendAmount = parseInt(vm.outputAmount, 10); 54 | var changeAmount = vm.useChange ? parseInt(vm.changeAmount, 10) : 0; 55 | vm.calculatedFee = unspent - sendAmount - changeAmount; 56 | vm.createTransaction(); 57 | } catch (e) { 58 | vm.feeError = e; 59 | } 60 | }; 61 | 62 | vm.createTransaction = function () { 63 | vm.txError = null; 64 | try { 65 | var pubKey = vm.keyPair.publicKey; 66 | 67 | var pubKeyHash = null; 68 | var redeemScript = null; 69 | if (vm.inputSegwit) { 70 | pubKeyHash = bitcoin.crypto.hash160(pubKey); 71 | redeemScript = bitcoin.script.witnessPubKeyHash.output.encode(pubKeyHash); 72 | } 73 | var unspent = parseInt(vm.inputAmount, 10); 74 | 75 | var txb = new bitcoin.TransactionBuilder(vm.network.config); 76 | txb.addInput(vm.inputTxId, parseInt(vm.inputTxVout, 10)); 77 | txb.addOutput(vm.outputAddress, parseInt(vm.outputAmount)); 78 | 79 | if (vm.useChange) { 80 | txb.addOutput(vm.changeAddress, parseInt(vm.changeAmount)); 81 | } 82 | 83 | if (vm.inputSegwit) { 84 | txb.sign(0, vm.keyPair, redeemScript, null, unspent); 85 | } else { 86 | txb.sign(0, vm.keyPair); 87 | } 88 | 89 | var tx = txb.build(); 90 | vm.raw = tx.toHex(); 91 | vm.txId = tx.getHash().reverse().toString('hex'); 92 | } catch (e) { 93 | vm.txError = e; 94 | } 95 | }; 96 | } 97 | -------------------------------------------------------------------------------- /pages/wallet-import/wallet-import.html: -------------------------------------------------------------------------------- 1 |

Import HD wallet into Bitcoin Core

2 | 3 |
4 |
5 |

6 | Explanation 7 |

8 |
9 |
10 |
11 | Currently, there is no easy way to import addresses from a HD seed that has been created by another software into Bitcoin Core.
12 | This tool helps you do that. 13 |
14 |
15 |
16 | 17 |
18 | Warning: This is meant as a playground only! You should only use testnet keys and never paste real private keys 19 | into any web page! 20 |
21 | 22 |

Import HD wallet

23 |
24 |
25 | 26 | 27 |
28 | 29 |
30 | 35 |
36 |
37 | 38 | 39 |
40 | 41 |
42 | 43 | <-- paste here to import. 44 |
45 |
46 | 47 | 48 |
49 | 50 |
51 | 55 | 56 | 59 | 60 |
Method
61 | 66 |
67 |
68 | 69 | 70 |
71 | 72 |
73 | 78 |
79 |
80 | 81 | 82 |
83 | 84 |
85 | 91 | <-- paste here to import. 92 | {{vm.error}} 93 |
94 |
95 | 96 | 97 |
98 | 99 |
100 |
Import type
101 | 103 |
Start Path
104 | 105 |
106 |
107 |
Change (_chg_): Start value
108 | 109 |
End value
110 | 111 |
112 |
113 |
Index (_idx_): Start value
114 | 115 |
End value
116 | 117 |
118 |
119 | 120 |
121 |
122 | 123 |
124 |
125 | 126 | 127 |
128 |
129 | 130 |
131 |
132 |
133 |
134 | 135 | -------------------------------------------------------------------------------- /pages/wallet-import/wallet-import.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('app') 3 | .component('walletImportPage', { 4 | templateUrl: 'pages/wallet-import/wallet-import.html', 5 | controller: WalletImportPageController, 6 | controllerAs: 'vm', 7 | bindings: {} 8 | }); 9 | 10 | function WalletImportPageController(lodash, bitcoin, allNetworks, Buffer) { 11 | const vm = this; 12 | const {Output} = bitcoin.descriptors.DescriptorsFactory(bitcoin.descriptorsSecp); 13 | 14 | const PBKDF2_SALT = 'Digital Bitbox', 15 | PBKDF2_HMACLEN = 64, 16 | PBKDF2_ROUNDS_APP = 20480; 17 | const METHOD_NONE = 0, 18 | METHOD_PBKDF2 = 1, 19 | METHOD_COINOMI = 2; 20 | const BITCOIN = lodash.find(allNetworks, ['label', 'BTC (Bitcoin, legacy, BIP32/44)']); 21 | const BITCOIN_TESTNET = lodash.find(allNetworks, ['label', 'BTC (Bitcoin Testnet, legacy, BIP32/44)']); 22 | const SCHEMES = [ 23 | { 24 | label: "Bitcoin xprv (P2PKH/P2SH, m/44'/0')", 25 | id: 'xprv', 26 | prv: 0x0488ade4, 27 | pub: 0x0488b21e, 28 | path: "m/44'/0'/0'/_chg_/_idx_", 29 | config: BITCOIN.config 30 | }, 31 | { 32 | label: "Bitcoin yprv (P2WPKH in P2SH, m/49'/0')", 33 | id: 'yprv', 34 | prv: 0x049d7878, 35 | pub: 0x049d7cb2, 36 | path: "m/49'/0'/0'/_chg_/_idx_", 37 | config: BITCOIN.config 38 | }, 39 | { 40 | label: "Bitcoin zprv (P2WPKH, m/84'/0')", 41 | id: 'zprv', 42 | prv: 0x04b2430c, 43 | pub: 0x04b24746, 44 | path: "m/84'/0'/0'/_chg_/_idx_", 45 | config: BITCOIN.config 46 | }, 47 | { 48 | label: "Bitcoin Testnet tprv (P2PKH/P2SH, m/44'/1')", 49 | id: 'tprv', 50 | prv: 0x04358394, 51 | pub: 0x043587cf, 52 | path: "m/44'/1'/0'/_chg_/_idx_", 53 | config: BITCOIN_TESTNET.config 54 | }, 55 | { 56 | label: "Bitcoin Testnet uprv (P2WPKH in P2SH, m/49'/1')", 57 | id: 'uprv', 58 | prv: 0x044a4e28, 59 | pub: 0x044a5262, 60 | path: "m/49'/1'/0'/_chg_/_idx_", 61 | config: BITCOIN_TESTNET.config 62 | }, 63 | { 64 | label: "Bitcoin Testnet vprv (P2WPKH, m/84'/1')", 65 | id: 'vprv', 66 | prv: 0x045f18bc, 67 | pub: 0x045f1cf6, 68 | path: "m/84'/1'/0'/_chg_/_idx_", 69 | config: BITCOIN_TESTNET.config 70 | }, 71 | ]; 72 | const TYPES = [ 73 | {label: 'Wallet Dump format (importwallet)', id: 'dump'}, 74 | {label: 'bitcoin-cli importprivkey', id: 'importprivkey'}, 75 | {label: 'bitcoin-cli importpubkey', id: 'importpubkey'}, 76 | {label: 'bitcoin-cli importdescriptors (wpkh)', id: 'importdescriptorswpkh'}, 77 | // {label: 'bitcoin-cli importdescriptors (tr)', id: 'importdescriptorstr'}, 78 | {label: 'Electrum', id: 'electrum'}, 79 | ]; 80 | const MODES = [ 81 | {label: 'Import from BIP39 Mnemonic', id: 'mnemonic'}, 82 | {label: 'Import from BIP32 HD master root key', id: 'hdroot'}, 83 | ]; 84 | 85 | vm.schemes = SCHEMES; 86 | vm.scheme = SCHEMES[0]; 87 | vm.importTypes = TYPES; 88 | vm.importType = TYPES[0]; 89 | vm.modes = MODES; 90 | vm.mode = MODES[0]; 91 | vm.mnemonic = null; 92 | vm.asPassword = true; 93 | vm.passphrase = null; 94 | vm.seed = null; 95 | vm.seedHex = null; 96 | vm.node = null; 97 | vm.nodeBase58 = null; 98 | vm.changeStart = 0; 99 | vm.changeEnd = 1; 100 | vm.indexStart = 0; 101 | vm.indexEnd = 50; 102 | vm.path = ''; 103 | vm.strenghteningMethods = [ 104 | {label: 'BIP39 default (like Coinomi)', id: METHOD_COINOMI}, 105 | {label: 'BIP39 custom (passhprase to hex)', id: METHOD_NONE}, 106 | {label: 'PBKDF2 (Digital Bitbox)', id: METHOD_PBKDF2} 107 | 108 | ]; 109 | vm.strenghtening = vm.strenghteningMethods[0]; 110 | vm.result = ''; 111 | 112 | vm.$onInit = function () { 113 | vm.mnemonic = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about'; 114 | vm.fromMnemonic(); 115 | }; 116 | 117 | vm.fromMnemonic = function () { 118 | var pw = null; 119 | if (vm.passphrase) { 120 | if (vm.strenghtening.id === METHOD_PBKDF2) { 121 | pw = bitcoin.pbkdf2.pbkdf2Sync( 122 | Buffer.from(vm.passphrase, 'utf8'), 123 | PBKDF2_SALT, 124 | PBKDF2_ROUNDS_APP, 125 | PBKDF2_HMACLEN, 126 | 'sha512' 127 | ).toString('hex'); 128 | } else if (vm.strenghtening.id === METHOD_COINOMI) { 129 | pw = vm.passphrase; 130 | } else { 131 | pw = Buffer.from(vm.passphrase, 'utf8').toString('hex'); 132 | } 133 | } 134 | vm.seed = bitcoin.bip39.mnemonicToSeed(vm.mnemonic, pw); 135 | vm.fromSeed(); 136 | }; 137 | 138 | vm.fromSeed = function () { 139 | if (vm.seed) { 140 | vm.seedHex = vm.seed.toString('hex'); 141 | 142 | vm.node = bitcoin.bip32.fromSeed(vm.seed, vm.getConfig()); 143 | vm.nodeBase58 = vm.node.toBase58(); 144 | } 145 | vm.path = vm.scheme.path; 146 | }; 147 | 148 | vm.fromBase58 = function () { 149 | vm.error = null; 150 | try { 151 | vm.node = bitcoin.bip32.fromBase58(vm.nodeBase58, vm.getConfig()); 152 | } catch (e) { 153 | vm.error = e; 154 | } 155 | }; 156 | 157 | vm.getConfig = function () { 158 | return angular.extend({}, vm.scheme.config, {bip32: {public: vm.scheme.pub, private: vm.scheme.prv}}); 159 | }; 160 | 161 | vm.getPath = function (change, index) { 162 | let path = vm.path; 163 | path = path.replace(/\/_chg_/, change === null ? '' : '/' + change); 164 | return path.replace(/\/_idx_/, index === null ? '' : '/' + index); 165 | }; 166 | 167 | vm.createExport = function () { 168 | const basePath = vm.path.substring(0, vm.path.indexOf('_') - 1); 169 | vm.baseKey = vm.node.derivePath(basePath); 170 | vm.result = vm['getResultAs' + lodash.capitalize(vm.importType.id)](vm.node, basePath, vm.getConfig()); 171 | }; 172 | 173 | vm.getResultAsDump = function (rootNode, basePath, network) { 174 | const date = new Date().toISOString(); 175 | const baseKey = rootNode.derivePath(basePath); 176 | let str = `# Wallet dump created by cryptography toolkit 177 | # * Created on ${date} 178 | 179 | # extended private masterkey: ${baseKey.toBase58()} 180 | 181 | # You might need to replace the timestamp for every address you see with the 182 | # timestamp when the wallet was originally created for the funds to be registered 183 | # with Bitcoin Core or do a full wallet rescan after importing this dump. 184 | `; 185 | for (let change = vm.changeStart; change <= vm.changeEnd; change++) { 186 | const changePath = `${change}${vm.path.indexOf('_chg_\'') >= 0 ? '\'' : ''}`; 187 | const changeKey = baseKey.derivePath(changePath); 188 | for (let index = vm.indexStart; index <= vm.indexEnd; index++) { 189 | const indexPath = `${index}${vm.path.indexOf('_idx_\'') >= 0 ? '\'' : ''}`; 190 | const key = changeKey.derivePath(indexPath); 191 | const addr = vm.getAddress(key, network); 192 | str += `${key.toWIF()} ${date} reserve=0 # addr=${addr} hdkeypath=${basePath}/${changePath}/${indexPath}\n`; 193 | } 194 | } 195 | return str; 196 | }; 197 | 198 | vm.getResultAsImportprivkey = function (rootNode, basePath) { 199 | const baseKey = rootNode.derivePath(basePath); 200 | let str = `# Paste the following lines into a command line window. 201 | # You might want to adjust the block number to rescan from at the bottom of the 202 | # file if the wallet was originally created before 2017-12-18 18:35:25. 203 | `; 204 | for (let change = vm.changeStart; change <= vm.changeEnd; change++) { 205 | const changePath = `${change}${vm.path.indexOf('_chg_\'') >= 0 ? '\'' : ''}`; 206 | const changeKey = baseKey.derivePath(changePath); 207 | for (let index = vm.indexStart; index <= vm.indexEnd; index++) { 208 | const indexPath = `${index}${vm.path.indexOf('_idx_\'') >= 0 ? '\'' : ''}`; 209 | const key = changeKey.derivePath(indexPath); 210 | str += `bitcoin-cli importprivkey ${key.toWIF()} "${basePath}/${changePath}/${indexPath}" false\n`; 211 | } 212 | } 213 | str += 'bitcoin-cli rescanblockchain 500000\n'; 214 | return str; 215 | }; 216 | 217 | vm.getResultAsImportpubkey = function (rootNode, basePath) { 218 | const baseKey = rootNode.derivePath(basePath); 219 | let str = `# Paste the following lines into a command line window. 220 | # You might want to adjust the block number to rescan from at the bottom of the 221 | # file if the wallet was originally created before 2017-12-18 18:35:25. 222 | `; 223 | for (let change = vm.changeStart; change <= vm.changeEnd; change++) { 224 | const changePath = `${change}${vm.path.indexOf('_chg_\'') >= 0 ? '\'' : ''}`; 225 | const changeKey = baseKey.derivePath(changePath); 226 | for (let index = vm.indexStart; index <= vm.indexEnd; index++) { 227 | const indexPath = `${index}${vm.path.indexOf('_idx_\'') >= 0 ? '\'' : ''}`; 228 | const key = changeKey.derivePath(indexPath); 229 | str += `bitcoin-cli importpubkey ${key.publicKey.toString('hex')} "${basePath}/${changePath}/${indexPath}" false\n`; 230 | } 231 | } 232 | str += 'bitcoin-cli rescanblockchain 500000\n'; 233 | return str; 234 | }; 235 | 236 | vm.getResultAsImportdescriptorswpkh = function (rootNode, basePath, network) { 237 | const baseKey = rootNode.derivePath(basePath); 238 | let str = `# Paste the following lines into a command line window. 239 | # You might want to adjust the block number to rescan from at the bottom of the 240 | # file if the wallet was originally created before 2017-12-18 18:35:25. 241 | `; 242 | for (let change = vm.changeStart; change <= vm.changeEnd; change++) { 243 | const changePath = `${change}${vm.path.indexOf('_chg_\'') >= 0 ? '\'' : ''}`; 244 | const changeKey = baseKey.derivePath(changePath); 245 | for (let index = vm.indexStart; index <= vm.indexEnd; index++) { 246 | const indexPath = `${index}${vm.path.indexOf('_idx_\'') >= 0 ? '\'' : ''}`; 247 | const key = changeKey.derivePath(indexPath); 248 | const desc = `wpkh(${key.toWIF()})`; 249 | const output = new Output({ 250 | descriptor: desc, 251 | network: network, 252 | }); 253 | const checksum = bitcoin.descriptors.checksum(desc); 254 | const addr = output.getAddress(); 255 | str += `bitcoin-cli importdescriptors '[{"desc":"wpkh(${key.toWIF()})#${checksum}","timestamp":"now"}]' # ${addr}\n`; 256 | } 257 | } 258 | str += 'bitcoin-cli rescanblockchain 500000\n'; 259 | return str; 260 | }; 261 | 262 | // TODO(guggero): Enable once @bitcoinerlab/descriptors supports P2TR descriptors. 263 | vm.getResultAsImportdescriptorstr = function (rootNode, basePath, network) { 264 | const baseKey = rootNode.derivePath(basePath); 265 | let str = `# Paste the following lines into a command line window. 266 | # You might want to adjust the block number to rescan from at the bottom of the 267 | # file if the wallet was originally created before 2017-12-18 18:35:25. 268 | `; 269 | for (let change = vm.changeStart; change <= vm.changeEnd; change++) { 270 | const changePath = `${change}${vm.path.indexOf('_chg_\'') >= 0 ? '\'' : ''}`; 271 | const changeKey = baseKey.derivePath(changePath); 272 | for (let index = vm.indexStart; index <= vm.indexEnd; index++) { 273 | const indexPath = `${index}${vm.path.indexOf('_idx_\'') >= 0 ? '\'' : ''}`; 274 | const key = changeKey.derivePath(indexPath); 275 | const desc = `tr(${key.toWIF()})`; 276 | const output = new Output({ 277 | descriptor: desc, 278 | network: network, 279 | }); 280 | const checksum = bitcoin.descriptors.checksum(desc); 281 | const addr = output.getAddress(); 282 | str += `bitcoin-cli importdescriptors '[{"desc":"tr(${key.toWIF()})#${checksum}","timestamp":"now"}]' # ${addr}\n`; 283 | } 284 | } 285 | str += 'bitcoin-cli rescanblockchain 500000\n'; 286 | return str; 287 | }; 288 | 289 | vm.getResultAsElectrum = function (rootNode, basePath) { 290 | const baseKey = rootNode.derivePath(basePath); 291 | let str = ``; 292 | for (let change = vm.changeStart; change <= vm.changeEnd; change++) { 293 | const changePath = `${change}${vm.path.indexOf('_chg_\'') >= 0 ? '\'' : ''}`; 294 | const changeKey = baseKey.derivePath(changePath); 295 | for (let index = vm.indexStart; index <= vm.indexEnd; index++) { 296 | const indexPath = `${index}${vm.path.indexOf('_idx_\'') >= 0 ? '\'' : ''}`; 297 | const key = changeKey.derivePath(indexPath); 298 | 299 | str += `p2wpkh:${key.toWIF()}\n`; 300 | str += `p2pkh:${key.toWIF()}\n`; 301 | str += `p2wpkh-p2sh:${key.toWIF()}\n`; 302 | } 303 | } 304 | return str; 305 | }; 306 | 307 | vm.getAddress = function (keyPair, network) { 308 | if (vm.scheme.id === 'xprv' || vm.scheme.id === 'tprv') { 309 | return getP2PKHAddress(keyPair, network); 310 | } else if (vm.scheme.id === 'yprv' || vm.scheme.id === 'uprv') { 311 | return getNestedP2WPKHAddress(keyPair, network); 312 | } else { 313 | return getP2WPKHAddress(keyPair, network); 314 | } 315 | } 316 | } 317 | --------------------------------------------------------------------------------