diff --git a/lnbits/extensions/watchonly/README.md b/lnbits/extensions/watchonly/README.md deleted file mode 100644 index 4a8f2841..00000000 --- a/lnbits/extensions/watchonly/README.md +++ /dev/null @@ -1,87 +0,0 @@ -# Onchain Wallet (watch-only) - -## Monitor an onchain wallet and generate addresses for onchain payments - -Monitor an extended public key and generate deterministic fresh public keys with this simple watch only wallet. Invoice payments can also be generated, both through a publically shareable page and API. - -You can now use this wallet on the LNbits [SatsPayServer](https://github.com/lnbits/lnbits/blob/master/lnbits/extensions/satspay/README.md) extension - -Video demo - -### Wallet Account - - a user can add one or more `xPubs` or `descriptors` - - the `xPub` must be unique per user - - such and entry is called an `Wallet Account` - - the addresses in a `Wallet Account` are split into `Receive Addresses` and `Change Address` - - the user interacts directly only with the `Receive Addresses` (by sharing them) - - see [BIP44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account-discovery) for more details - - same `xPub` will always generate the same addresses (deterministic) - - when a `Wallet Account` is created, there are generated `20 Receive Addresses` and `5 Change Address` - - the limits can be change from the `Config` page (see `screenshot 1`) - - regular wallets only scan up to `20` empty receive addresses. If the user generates addresses beyond this limit a warning is shown (see `screenshot 4`) - - an account can be added `From Hardware Device` - -### Scan Blockchain - - when the user clicks `Scan Blockchain`, the wallet will loop over the all addresses (for each account) - - if funds are found, then the list is extended - - will scan addresses for all wallet accounts - - the search is done on the client-side (using the `mempool.space` API). `mempool.space` has a limit on the number of req/sec, therefore it is expected for the scanning to start fast, but slow down as more HTTP requests have to be retried - - addresses can also be rescanned individually form the `Address Details` section (`Addresses` tab) of each address - -### New Receive Address - - the `New Receive Address` button show the user the NEXT un-used address - - un-used means funds have not already been sent to that address AND the address has not already been shared - - internally there is a counter that keeps track of the last shared address - - it is possible to add a `Note` to each address in order to remember when/with whom it was shared - - mind the gap (`screenshot 4`) - -### Addresses Tab -- the `Addresses` tab contains a list with the addresses for all the `Wallet Accounts` - - only one entry per address will be shown (even if there are multiple UTXOs at that address) - - several filter criteria can be applied - - unconfirmed funds are also taken into account - - `Address Details` can be viewed by clicking the `Expand` button - -### History Tap - - shows the chronological order of transactions - - it shows unconfirmed transactions at the top - - it can be exported as CSV file - -### Coins Tab - - shows the UTXOs for all wallets - - there can be multiple UTXOs for the same address - -### New Payment - - create a new `Partially Signed Bitcoin Transaction` - - multiple `Send Addresses` can be added - - the `Max` button next to an address is for sending the remaining funds to this address (no change) - - the user can select the inputs (UTXOs) manually, or it can use of the basic selection algorithms - - amounts have to be provided for the `Send Addresses` beforehand (so the algorithm knows the amount to be selected) - - `Show Change` allows to select from which account the change address will be selected (defaults to the first one) - - `Show Custom Fee` allows to manually select the fee - - it defaults to the `Medium` value at the moment the `New Payment` button was clicked - - it can be refreshed - - warnings are shown if the fee is too Low or to High - -### Check & Send - - creates the PSBT and sends it to the Hardware Wallet - - a confirmation will be shown for each Output and for the Fee - - after the user confirms the addresses and amounts, the transaction will be signed on the Hardware Device - -### Share PSBT - - Show the PSBT without sending it to the Hardware Wallet - -## Screensots -- screenshot 1: -![image](https://user-images.githubusercontent.com/2951406/177181611-eeeac70c-c245-4b45-b80b-8bbb511f6d1d.png) - -- screenshot 2: -![image](https://user-images.githubusercontent.com/2951406/183087898-b91f5243-8ed9-4a14-9e57-7bb4f1fd43ef.png) - -- screenshot 3: -![image](https://user-images.githubusercontent.com/2951406/177333755-4a9118fb-3eaf-43d6-bc7e-c3d8c80bc61e.png) - -- screenshot 4: -![image](https://user-images.githubusercontent.com/2951406/177337474-bfcf7a7c-501a-4ebb-916e-ca391e63f6a7.png) - - diff --git a/lnbits/extensions/watchonly/__init__.py b/lnbits/extensions/watchonly/__init__.py deleted file mode 100644 index 49adb462..00000000 --- a/lnbits/extensions/watchonly/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -from fastapi import APIRouter -from fastapi.staticfiles import StaticFiles - -from lnbits.db import Database -from lnbits.helpers import template_renderer - -db = Database("ext_watchonly") - -watchonly_static_files = [ - { - "path": "/watchonly/static", - "app": StaticFiles(directory="lnbits/extensions/watchonly/static"), - "name": "watchonly_static", - } -] - -watchonly_ext: APIRouter = APIRouter(prefix="/watchonly", tags=["watchonly"]) - - -def watchonly_renderer(): - return template_renderer(["lnbits/extensions/watchonly/templates"]) - - -from .views import * # noqa: F401,F403 -from .views_api import * # noqa: F401,F403 diff --git a/lnbits/extensions/watchonly/config.json b/lnbits/extensions/watchonly/config.json deleted file mode 100644 index c9fec893..00000000 --- a/lnbits/extensions/watchonly/config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "Onchain Wallet", - "short_description": "Onchain watch only wallets", - "tile": "/watchonly/static/bitcoin-wallet.png", - "contributors": [ - "arcbtc", - "motorina0" - ] -} diff --git a/lnbits/extensions/watchonly/crud.py b/lnbits/extensions/watchonly/crud.py deleted file mode 100644 index 8472fa01..00000000 --- a/lnbits/extensions/watchonly/crud.py +++ /dev/null @@ -1,246 +0,0 @@ -import json -from typing import List, Optional - -from lnbits.helpers import urlsafe_short_hash - -from . import db -from .helpers import derive_address -from .models import Address, Config, WalletAccount - -##########################WALLETS#################### - - -async def create_watch_wallet(user: str, w: WalletAccount) -> WalletAccount: - wallet_id = urlsafe_short_hash() - await db.execute( - """ - INSERT INTO watchonly.wallets ( - id, - "user", - masterpub, - fingerprint, - title, - type, - address_no, - balance, - network, - meta - ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - """, - ( - wallet_id, - user, - w.masterpub, - w.fingerprint, - w.title, - w.type, - w.address_no, - w.balance, - w.network, - w.meta, - ), - ) - wallet = await get_watch_wallet(wallet_id) - assert wallet - return wallet - - -async def get_watch_wallet(wallet_id: str) -> Optional[WalletAccount]: - row = await db.fetchone( - "SELECT * FROM watchonly.wallets WHERE id = ?", (wallet_id,) - ) - return WalletAccount.from_row(row) if row else None - - -async def get_watch_wallets(user: str, network: str) -> List[WalletAccount]: - rows = await db.fetchall( - """SELECT * FROM watchonly.wallets WHERE "user" = ? AND network = ?""", - (user, network), - ) - return [WalletAccount(**row) for row in rows] - - -async def update_watch_wallet(wallet_id: str, **kwargs) -> Optional[WalletAccount]: - q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - - await db.execute( - f"UPDATE watchonly.wallets SET {q} WHERE id = ?", (*kwargs.values(), wallet_id) - ) - row = await db.fetchone( - "SELECT * FROM watchonly.wallets WHERE id = ?", (wallet_id,) - ) - return WalletAccount.from_row(row) if row else None - - -async def delete_watch_wallet(wallet_id: str) -> None: - await db.execute("DELETE FROM watchonly.wallets WHERE id = ?", (wallet_id,)) - - -########################ADDRESSES####################### - - -async def get_fresh_address(wallet_id: str) -> Optional[Address]: - # todo: move logic to views_api after satspay refactoring - wallet = await get_watch_wallet(wallet_id) - - if not wallet: - return None - - wallet_addresses = await get_addresses(wallet_id) - receive_addresses = list( - filter( - lambda addr: addr.branch_index == 0 and addr.has_activity, wallet_addresses - ) - ) - last_receive_index = ( - receive_addresses.pop().address_index if receive_addresses else -1 - ) - address_index = ( - last_receive_index - if last_receive_index > wallet.address_no - else wallet.address_no - ) - - address = await get_address_at_index(wallet_id, 0, address_index + 1) - - if not address: - addresses = await create_fresh_addresses( - wallet_id, address_index + 1, address_index + 2 - ) - address = addresses.pop() - - await update_watch_wallet(wallet_id, **{"address_no": address_index + 1}) - - return address - - -async def create_fresh_addresses( - wallet_id: str, - start_address_index: int, - end_address_index: int, - change_address=False, -) -> List[Address]: - if start_address_index > end_address_index: - return [] - - wallet = await get_watch_wallet(wallet_id) - if not wallet: - return [] - - branch_index = 1 if change_address else 0 - - for address_index in range(start_address_index, end_address_index): - address = await derive_address(wallet.masterpub, address_index, branch_index) - - await db.execute( - """ - INSERT INTO watchonly.addresses ( - id, - address, - wallet, - amount, - branch_index, - address_index - ) - VALUES (?, ?, ?, ?, ?, ?) - """, - (urlsafe_short_hash(), address, wallet_id, 0, branch_index, address_index), - ) - - # return fresh addresses - rows = await db.fetchall( - """ - SELECT * FROM watchonly.addresses - WHERE wallet = ? AND branch_index = ? AND address_index >= ? AND address_index < ? - ORDER BY branch_index, address_index - """, - (wallet_id, branch_index, start_address_index, end_address_index), - ) - - return [Address(**row) for row in rows] - - -async def get_address(address: str) -> Optional[Address]: - row = await db.fetchone( - "SELECT * FROM watchonly.addresses WHERE address = ?", (address,) - ) - return Address.from_row(row) if row else None - - -async def get_address_at_index( - wallet_id: str, branch_index: int, address_index: int -) -> Optional[Address]: - row = await db.fetchone( - """ - SELECT * FROM watchonly.addresses - WHERE wallet = ? AND branch_index = ? AND address_index = ? - """, - ( - wallet_id, - branch_index, - address_index, - ), - ) - return Address.from_row(row) if row else None - - -async def get_addresses(wallet_id: str) -> List[Address]: - rows = await db.fetchall( - """ - SELECT * FROM watchonly.addresses WHERE wallet = ? - ORDER BY branch_index, address_index - """, - (wallet_id,), - ) - - return [Address(**row) for row in rows] - - -async def update_address(id: str, **kwargs) -> Optional[Address]: - q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) - - await db.execute( - f"""UPDATE watchonly.addresses SET {q} WHERE id = ? """, - (*kwargs.values(), id), - ) - row = await db.fetchone("SELECT * FROM watchonly.addresses WHERE id = ?", (id,)) - return Address.from_row(row) if row else None - - -async def delete_addresses_for_wallet(wallet_id: str) -> None: - await db.execute("DELETE FROM watchonly.addresses WHERE wallet = ?", (wallet_id,)) - - -######################CONFIG####################### -async def create_config(user: str) -> Config: - config = Config() - await db.execute( - """ - INSERT INTO watchonly.config ("user", json_data) - VALUES (?, ?) - """, - (user, json.dumps(config.dict())), - ) - row = await db.fetchone( - """SELECT json_data FROM watchonly.config WHERE "user" = ?""", (user,) - ) - return json.loads(row[0], object_hook=lambda d: Config(**d)) - - -async def update_config(config: Config, user: str) -> Optional[Config]: - await db.execute( - """UPDATE watchonly.config SET json_data = ? WHERE "user" = ?""", - (json.dumps(config.dict()), user), - ) - row = await db.fetchone( - """SELECT json_data FROM watchonly.config WHERE "user" = ?""", (user,) - ) - return json.loads(row[0], object_hook=lambda d: Config(**d)) - - -async def get_config(user: str) -> Optional[Config]: - row = await db.fetchone( - """SELECT json_data FROM watchonly.config WHERE "user" = ?""", (user,) - ) - return json.loads(row[0], object_hook=lambda d: Config(**d)) if row else None diff --git a/lnbits/extensions/watchonly/helpers.py b/lnbits/extensions/watchonly/helpers.py deleted file mode 100644 index 0ac36454..00000000 --- a/lnbits/extensions/watchonly/helpers.py +++ /dev/null @@ -1,76 +0,0 @@ -from typing import Optional, Tuple - -from embit.descriptor import Descriptor, Key -from embit.descriptor.arguments import AllowedDerivation -from embit.networks import NETWORKS - - -def detect_network(k): - version = k.key.version - for network_name in NETWORKS: - net = NETWORKS[network_name] - # not found in this network - if version in [net["xpub"], net["ypub"], net["zpub"], net["Zpub"], net["Ypub"]]: - return net - - -def parse_key(masterpub: str) -> Tuple[Descriptor, Optional[dict]]: - """Parses masterpub or descriptor and returns a tuple: (Descriptor, network) - To create addresses use descriptor.derive(num).address(network=network) - """ - network = None - desc = None - # probably a single key - if "(" not in masterpub: - k = Key.from_string(masterpub) - if not k.is_extended: - raise ValueError("The key is not a master public key") - if k.is_private: - raise ValueError("Private keys are not allowed") - # check depth - if k.key.depth != 3: - raise ValueError( - "Non-standard depth. Only bip44, bip49 and bip84 are supported with bare xpubs. For custom derivation paths use descriptors." - ) - # if allowed derivation is not provided use default /{0,1}/* - if k.allowed_derivation is None: - k.allowed_derivation = AllowedDerivation.default() - # get version bytes - version = k.key.version - for network_name in NETWORKS: - net = NETWORKS[network_name] - # not found in this network - if version in [net["xpub"], net["ypub"], net["zpub"]]: - network = net - if version == net["xpub"]: - desc = Descriptor.from_string("pkh(%s)" % str(k)) - elif version == net["ypub"]: - desc = Descriptor.from_string("sh(wpkh(%s))" % str(k)) - elif version == net["zpub"]: - desc = Descriptor.from_string("wpkh(%s)" % str(k)) - break - # we didn't find correct version - if not network: - raise ValueError("Unknown master public key version") - if not desc: - raise ValueError("descriptor not found, because version did not match") - - else: - desc = Descriptor.from_string(masterpub) - if not desc.is_wildcard: - raise ValueError("Descriptor should have wildcards") - for k in desc.keys: - if k.is_extended: - net = detect_network(k) - if net is None: - raise ValueError(f"Unknown version: {k}") - if network is not None and network != net: - raise ValueError("Keys from different networks") - network = net - - return desc, network - - -async def derive_address(masterpub: str, num: int, branch_index=0): - desc, network = parse_key(masterpub) - return desc.derive(num, branch_index).address(network=network) diff --git a/lnbits/extensions/watchonly/migrations.py b/lnbits/extensions/watchonly/migrations.py deleted file mode 100644 index bbef40a8..00000000 --- a/lnbits/extensions/watchonly/migrations.py +++ /dev/null @@ -1,102 +0,0 @@ -async def m001_initial(db): - """ - Initial wallet table. - """ - await db.execute( - f""" - CREATE TABLE watchonly.wallets ( - id TEXT NOT NULL PRIMARY KEY, - "user" TEXT, - masterpub TEXT NOT NULL, - title TEXT NOT NULL, - address_no INTEGER NOT NULL DEFAULT 0, - balance {db.big_int} NOT NULL - ); - """ - ) - - await db.execute( - f""" - CREATE TABLE watchonly.addresses ( - id TEXT NOT NULL PRIMARY KEY, - address TEXT NOT NULL, - wallet TEXT NOT NULL, - amount {db.big_int} NOT NULL - ); - """ - ) - - await db.execute( - """ - CREATE TABLE watchonly.mempool ( - "user" TEXT NOT NULL, - endpoint TEXT NOT NULL - ); - """ - ) - - -async def m002_add_columns_to_adresses(db): - """ - Add 'branch_index', 'address_index', 'has_activity' and 'note' columns to the 'addresses' table - """ - - await db.execute( - "ALTER TABLE watchonly.addresses ADD COLUMN branch_index INTEGER NOT NULL DEFAULT 0;" - ) - await db.execute( - "ALTER TABLE watchonly.addresses ADD COLUMN address_index INTEGER NOT NULL DEFAULT 0;" - ) - await db.execute( - "ALTER TABLE watchonly.addresses ADD COLUMN has_activity BOOLEAN DEFAULT false;" - ) - await db.execute("ALTER TABLE watchonly.addresses ADD COLUMN note TEXT;") - - -async def m003_add_columns_to_wallets(db): - """ - Add 'type' and 'fingerprint' columns to the 'wallets' table - """ - - await db.execute("ALTER TABLE watchonly.wallets ADD COLUMN type TEXT;") - await db.execute( - "ALTER TABLE watchonly.wallets ADD COLUMN fingerprint TEXT NOT NULL DEFAULT '';" - ) - - -async def m004_create_config_table(db): - """ - Allow the extension to persist and retrieve any number of config values. - Each user has its configurations saved as a JSON string - """ - - await db.execute( - """CREATE TABLE watchonly.config ( - "user" TEXT NOT NULL, - json_data TEXT NOT NULL - );""" - ) - - -async def m005_add_network_column_to_wallets(db): - """ - Add network' column to the 'wallets' table - """ - - await db.execute( - "ALTER TABLE watchonly.wallets ADD COLUMN network TEXT DEFAULT 'Mainnet';" - ) - - -async def m006_drop_mempool_table(db): - """ - Mempool data is now part of `config` - """ - await db.execute("DROP TABLE watchonly.mempool;") - - -async def m007_add_wallet_meta_data(db): - """ - Add 'meta' for storing various metadata about the wallet - """ - await db.execute("ALTER TABLE watchonly.wallets ADD COLUMN meta TEXT DEFAULT '{}';") diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py deleted file mode 100644 index 24d63bfd..00000000 --- a/lnbits/extensions/watchonly/models.py +++ /dev/null @@ -1,99 +0,0 @@ -from sqlite3 import Row -from typing import List, Optional - -from fastapi import Query -from pydantic import BaseModel - - -class CreateWallet(BaseModel): - masterpub: str = Query("") - title: str = Query("") - network: str = "Mainnet" - meta: str = "{}" - - -class WalletAccount(BaseModel): - id: str - masterpub: str - fingerprint: str - title: str - address_no: int - balance: int - type: Optional[str] = "" - network: str = "Mainnet" - meta: str = "{}" - - @classmethod - def from_row(cls, row: Row) -> "WalletAccount": - return cls(**dict(row)) - - -class Address(BaseModel): - id: str - address: str - wallet: str - amount: int = 0 - branch_index: int = 0 - address_index: int - note: Optional[str] = None - has_activity: bool = False - - @classmethod - def from_row(cls, row: Row) -> "Address": - return cls(**dict(row)) - - -class TransactionInput(BaseModel): - tx_id: str - vout: int - amount: int - address: str - branch_index: int - address_index: int - wallet: str - tx_hex: str - - -class TransactionOutput(BaseModel): - amount: int - address: str - branch_index: Optional[int] = None - address_index: Optional[int] = None - wallet: Optional[str] = None - - -class MasterPublicKey(BaseModel): - id: str - public_key: str - fingerprint: str - - -class CreatePsbt(BaseModel): - masterpubs: List[MasterPublicKey] - inputs: List[TransactionInput] - outputs: List[TransactionOutput] - fee_rate: int - tx_size: int - - -class SerializedTransaction(BaseModel): - tx_hex: str - - -class ExtractPsbt(BaseModel): - psbtBase64 = "" # // todo snake case - inputs: List[SerializedTransaction] - network = "Mainnet" - - -class SignedTransaction(BaseModel): - tx_hex: Optional[str] - tx_json: Optional[str] - - -class Config(BaseModel): - mempool_endpoint = "https://mempool.space" - receive_gap_limit = 20 - change_gap_limit = 5 - sats_denominated = True - network = "Mainnet" diff --git a/lnbits/extensions/watchonly/static/bitcoin-wallet.png b/lnbits/extensions/watchonly/static/bitcoin-wallet.png deleted file mode 100644 index 3cd5ac0f..00000000 Binary files a/lnbits/extensions/watchonly/static/bitcoin-wallet.png and /dev/null differ diff --git a/lnbits/extensions/watchonly/static/components/address-list/address-list.html b/lnbits/extensions/watchonly/static/components/address-list/address-list.html deleted file mode 100644 index f397ee97..00000000 --- a/lnbits/extensions/watchonly/static/components/address-list/address-list.html +++ /dev/null @@ -1,215 +0,0 @@ -
-
-
- -
-
- -
-
- - - -
-
- - - -
diff --git a/lnbits/extensions/watchonly/static/components/address-list/address-list.js b/lnbits/extensions/watchonly/static/components/address-list/address-list.js deleted file mode 100644 index 61545df0..00000000 --- a/lnbits/extensions/watchonly/static/components/address-list/address-list.js +++ /dev/null @@ -1,131 +0,0 @@ -async function addressList(path) { - const template = await loadTemplateAsync(path) - Vue.component('address-list', { - name: 'address-list', - template, - - props: [ - 'addresses', - 'accounts', - 'mempool-endpoint', - 'inkey', - 'sats-denominated' - ], - data: function () { - return { - show: false, - history: [], - selectedWallet: null, - note: '', - filterOptions: [ - 'Show Change Addresses', - 'Show Gap Addresses', - 'Only With Amount' - ], - filterValues: [], - - addressesTable: { - columns: [ - { - name: 'expand', - align: 'left', - label: '' - }, - { - name: 'address', - align: 'left', - label: 'Address', - field: 'address', - sortable: true - }, - { - name: 'amount', - align: 'left', - label: 'Amount', - field: 'amount', - sortable: true - }, - { - name: 'note', - align: 'left', - label: 'Note', - field: 'note', - sortable: true - }, - { - name: 'wallet', - align: 'left', - label: 'Account', - field: 'wallet', - sortable: true - } - ], - pagination: { - rowsPerPage: 0, - sortBy: 'amount', - descending: true - }, - filter: '' - } - } - }, - - methods: { - satBtc(val, showUnit = true) { - return satOrBtc(val, showUnit, this.satsDenominated) - }, - // todo: bad. base.js not present in custom components - copyText: function (text, message, position) { - var notify = this.$q.notify - Quasar.utils.copyToClipboard(text).then(function () { - notify({ - message: message || 'Copied to clipboard!', - position: position || 'bottom' - }) - }) - }, - getWalletName: function (walletId) { - const wallet = (this.accounts || []).find(wl => wl.id === walletId) - return wallet ? wallet.title : 'unknown' - }, - getFilteredAddresses: function () { - const selectedWalletId = this.selectedWallet?.id - const filter = this.filterValues || [] - const includeChangeAddrs = filter.includes('Show Change Addresses') - const includeGapAddrs = filter.includes('Show Gap Addresses') - const excludeNoAmount = filter.includes('Only With Amount') - - const walletsLimit = (this.accounts || []).reduce((r, w) => { - r[`_${w.id}`] = w.address_no - return r - }, {}) - - const fAddresses = this.addresses.filter( - a => - (includeChangeAddrs || !a.isChange) && - (includeGapAddrs || - a.isChange || - a.addressIndex <= walletsLimit[`_${a.wallet}`]) && - !(excludeNoAmount && a.amount === 0) && - (!selectedWalletId || a.wallet === selectedWalletId) - ) - return fAddresses - }, - - scanAddress: async function (addressData) { - this.$emit('scan:address', addressData) - }, - showAddressDetails: function (addressData) { - this.$emit('show-address-details', addressData) - }, - searchInTab: function (tab, value) { - this.$emit('search:tab', {tab, value}) - }, - updateNoteForAddress: async function (addressData, note) { - this.$emit('update:note', {addressId: addressData.id, note}) - } - }, - - created: async function () {} - }) -} diff --git a/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.html b/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.html deleted file mode 100644 index 0df5bebf..00000000 --- a/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.html +++ /dev/null @@ -1,62 +0,0 @@ -
-
-
Fee Rate:
-
- -
-
- -
-
-
-
-
- - Warning! The fee is too low. The transaction might take a long time to - confirm. - - - Warning! The fee is too high. You might be overpaying for this - transaction. - -
-
- -
-
Fee:
-
{{feeValue}} sats
-
- Refresh Fee Rates -
-
-
diff --git a/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.js b/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.js deleted file mode 100644 index 7a920a9a..00000000 --- a/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.js +++ /dev/null @@ -1,64 +0,0 @@ -async function feeRate(path) { - const template = await loadTemplateAsync(path) - Vue.component('fee-rate', { - name: 'fee-rate', - template, - - props: ['rate', 'fee-value', 'sats-denominated', 'mempool-endpoint'], - - computed: { - feeRate: { - get: function () { - return this['rate'] - }, - set: function (value) { - this.$emit('update:rate', +value) - } - } - }, - - data: function () { - return { - recommededFees: { - fastestFee: 1, - halfHourFee: 1, - hourFee: 1, - economyFee: 1, - minimumFee: 1 - } - } - }, - - methods: { - satBtc(val, showUnit = true) { - return satOrBtc(val, showUnit, this.satsDenominated) - }, - - refreshRecommendedFees: async function () { - const fn = async () => { - const { - bitcoin: {fees: feesAPI} - } = mempoolJS({ - hostname: this.mempoolEndpoint - }) - return feesAPI.getFeesRecommended() - } - this.recommededFees = await retryWithDelay(fn) - }, - getFeeRateLabel: function (feeRate) { - const fees = this.recommededFees - if (feeRate >= fees.fastestFee) - return `High Priority (${feeRate} sat/vB)` - if (feeRate >= fees.halfHourFee) - return `Medium Priority (${feeRate} sat/vB)` - if (feeRate >= fees.hourFee) return `Low Priority (${feeRate} sat/vB)` - return `No Priority (${feeRate} sat/vB)` - } - }, - - created: async function () { - await this.refreshRecommendedFees() - this.feeRate = this.recommededFees.halfHourFee - } - }) -} diff --git a/lnbits/extensions/watchonly/static/components/history/history.html b/lnbits/extensions/watchonly/static/components/history/history.html deleted file mode 100644 index ced03b26..00000000 --- a/lnbits/extensions/watchonly/static/components/history/history.html +++ /dev/null @@ -1,146 +0,0 @@ -
-
-
-
- - - -
-
- - - - - Export to CSV - - - - -
-
- - - -
diff --git a/lnbits/extensions/watchonly/static/components/history/history.js b/lnbits/extensions/watchonly/static/components/history/history.js deleted file mode 100644 index 81cf44cc..00000000 --- a/lnbits/extensions/watchonly/static/components/history/history.js +++ /dev/null @@ -1,98 +0,0 @@ -async function history(path) { - const template = await loadTemplateAsync(path) - Vue.component('history', { - name: 'history', - template, - - props: ['history', 'mempool-endpoint', 'sats-denominated', 'filter'], - data: function () { - return { - historyTable: { - columns: [ - { - name: 'expand', - align: 'left', - label: '' - }, - { - name: 'status', - align: 'left', - label: 'Status' - }, - { - name: 'amount', - align: 'left', - label: 'Amount', - field: 'amount', - sortable: true - }, - { - name: 'address', - align: 'left', - label: 'Address', - field: 'address', - sortable: true - }, - { - name: 'date', - align: 'left', - label: 'Date', - field: 'date', - sortable: true - }, - { - name: 'txId', - field: 'txId' - } - ], - exportColums: [ - { - label: 'Action', - field: 'action' - }, - { - label: 'Date&Time', - field: 'date' - }, - { - label: 'Amount', - field: 'amount' - }, - { - label: 'Fee', - field: 'fee' - }, - { - label: 'Transaction Id', - field: 'txId' - } - ], - pagination: { - rowsPerPage: 0 - } - } - } - }, - - methods: { - satBtc(val, showUnit = true) { - return satOrBtc(val, showUnit, this.satsDenominated) - }, - getFilteredAddressesHistory: function () { - return this.history.filter(a => (!a.isChange || a.sent) && !a.isSubItem) - }, - exportHistoryToCSV: function () { - const history = this.getFilteredAddressesHistory().map(a => ({ - ...a, - action: a.sent ? 'Sent' : 'Received' - })) - LNbits.utils.exportCSV( - this.historyTable.exportColums, - history, - 'address-history' - ) - } - }, - created: async function () {} - }) -} diff --git a/lnbits/extensions/watchonly/static/components/my-checkbox/my-checkbox.html b/lnbits/extensions/watchonly/static/components/my-checkbox/my-checkbox.html deleted file mode 100644 index 83af1248..00000000 --- a/lnbits/extensions/watchonly/static/components/my-checkbox/my-checkbox.html +++ /dev/null @@ -1,5 +0,0 @@ -
-
-
{{ title }}
- XXX -
diff --git a/lnbits/extensions/watchonly/static/components/my-checkbox/my-checkbox.js b/lnbits/extensions/watchonly/static/components/my-checkbox/my-checkbox.js deleted file mode 100644 index 3d22c3a0..00000000 --- a/lnbits/extensions/watchonly/static/components/my-checkbox/my-checkbox.js +++ /dev/null @@ -1,16 +0,0 @@ -async function initMyCheckbox(path) { - const t = await loadTemplateAsync(path) - Vue.component('my-checkbox', { - name: 'my-checkbox', - template: t, - data() { - return {checked: false, title: 'Check me'} - }, - methods: { - check() { - this.checked = !this.checked - console.log('### checked', this.checked) - } - } - }) -} diff --git a/lnbits/extensions/watchonly/static/components/payment/payment.html b/lnbits/extensions/watchonly/static/components/payment/payment.html deleted file mode 100644 index 6e1d94c7..00000000 --- a/lnbits/extensions/watchonly/static/components/payment/payment.html +++ /dev/null @@ -1,312 +0,0 @@ -
- - - - - - - - - -
-
- -
- -
-
- Fee Rate: - - {{feeRate}} sats/vbyte - Fee: - {{satBtc(feeValue)}} -
-
-
- -
-
- - -
-
-
-
- - - -
-
- -
- -
-
- Balance: - {{satBtc(balance)}} - Selected: - - {{satBtc(selectedAmount)}} - -
-
-
- -
-
- - -
-
-
-
- - - -
-
- -
- -
- - Below dust limit. Will be used as fee. - -
-
-
- Change: - - {{satBtc(0)}} - - - {{satBtc(changeAmount)}} - -
-
-
- -
-
- -
-
Change Account:
-
- -
-
- -
-
-
-
-
-
- -
-
- - - - - Serial Port - - Sign using a Serial Port device - - - - - Share PSBT - Share the PSBT as text or Animated QR Code - - - - -
- -
- - - The payed amount is higher than the selected amount! - -
-
-
- - - - -
- Close -
-
-
- - - -
-
- Transaction Details -
-
- -
-
-
-
Version
-
{{signedTx.version}}
-
-
-
Locktime
-
{{signedTx.locktime}}
-
-
-
Fee
-
- {{satBtc(signedTx.fee)}} -
-
- - Outputs - -
-
- {{satBtc(out.amount)}} -
- -
- {{out.address}} -
-
-
-
- -
-
- -
-
-
-
- -
-
- -
- Send - Close -
-
-
-
diff --git a/lnbits/extensions/watchonly/static/components/payment/payment.js b/lnbits/extensions/watchonly/static/components/payment/payment.js deleted file mode 100644 index e9689003..00000000 --- a/lnbits/extensions/watchonly/static/components/payment/payment.js +++ /dev/null @@ -1,379 +0,0 @@ -async function payment(path) { - const t = await loadTemplateAsync(path) - Vue.component('payment', { - name: 'payment', - template: t, - - props: [ - 'accounts', - 'addresses', - 'utxos', - 'mempool-endpoint', - 'sats-denominated', - 'serial-signer-ref', - 'adminkey', - 'network' - ], - watch: { - immediate: true, - accounts() { - this.updateChangeAddress() - }, - addresses() { - this.updateChangeAddress() - } - }, - - data: function () { - return { - DUST_LIMIT: 546, - tx: null, - psbtBase64: null, - psbtBase64Signed: null, - signedTx: null, - signedTxHex: null, - sentTxId: null, - signedTxId: null, - sendToList: [{address: '', amount: undefined}], - changeWallet: null, - changeAddress: {}, - showCustomFee: false, - showCoinSelect: false, - showChecking: false, - showChange: false, - showPsbt: false, - showFinalTx: false, - feeRate: 1 - } - }, - - computed: { - txSize: function () { - const tx = this.createTx() - return Math.round(txSize(tx)) - }, - txSizeNoChange: function () { - const tx = this.createTx(true) - return Math.round(txSize(tx)) - }, - feeValue: function () { - return this.feeRate * this.txSize - }, - selectedAmount: function () { - return this.utxos - .filter(utxo => utxo.selected) - .reduce((t, a) => t + (a.amount || 0), 0) - }, - changeAmount: function () { - return ( - this.selectedAmount - - this.totalPayedAmount - - this.feeRate * this.txSize - ) - }, - balance: function () { - return this.utxos.reduce((t, a) => t + (a.amount || 0), 0) - }, - totalPayedAmount: function () { - return this.sendToList.reduce((t, a) => t + (a.amount || 0), 0) - } - }, - - methods: { - satBtc(val, showUnit = true) { - return satOrBtc(val, showUnit, this.satsDenominated) - }, - clearState: function () { - this.psbtBase64 = null - this.psbtBase64Signed = null - this.signedTx = null - this.signedTxHex = null - this.signedTxId = null - this.sendToList = [{address: '', amount: undefined}] - this.showChecking = false - this.showPsbt = false - this.showFinalTx = false - }, - checkAndSend: async function () { - this.showChecking = true - try { - if (!this.serialSignerRef.isConnected()) { - this.$q.notify({ - type: 'warning', - message: 'Please connect to a Signing device first!', - timeout: 10000 - }) - return - } - const p2trUtxo = this.utxos.find( - u => u.selected && u.accountType === 'p2tr' - ) - if (p2trUtxo) { - this.$q.notify({ - type: 'warning', - message: 'Taproot Signing not supported yet!', - caption: 'Please manually deselect the Taproot UTXOs', - timeout: 10000 - }) - return - } - if (!this.serialSignerRef.isAuthenticated()) { - await this.serialSignerRef.hwwShowPasswordDialog() - const authenticated = await this.serialSignerRef.isAuthenticating() - if (!authenticated) return - } - - await this.createPsbt() - - if (this.psbtBase64) { - const txData = { - outputs: this.tx.outputs, - feeRate: this.tx.fee_rate, - feeValue: this.feeValue - } - await this.serialSignerRef.hwwSendPsbt(this.psbtBase64, txData) - await this.serialSignerRef.isSendingPsbt() - } - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Cannot check and sign PSBT!', - caption: `${error}`, - timeout: 10000 - }) - } finally { - this.showChecking = false - this.psbtBase64 = null - } - }, - showPsbtDialog: async function () { - try { - const valid = await this.$refs.paymentFormRef.validate() - if (!valid) return - - const data = await this.createPsbt() - if (data) { - this.showPsbt = true - } - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to create PSBT!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - createPsbt: async function () { - try { - this.tx = this.createTx() - for (const input of this.tx.inputs) { - input.tx_hex = await this.fetchTxHex(input.tx_id) - } - - const changeOutput = this.tx.outputs.find(o => o.branch_index === 1) - if (changeOutput) changeOutput.amount = this.changeAmount - - const {data} = await LNbits.api.request( - 'POST', - '/watchonly/api/v1/psbt', - this.adminkey, - this.tx - ) - - this.psbtBase64 = data - return data - } catch (err) { - LNbits.utils.notifyApiError(err) - } - }, - createTx: function (excludeChange = false) { - const tx = { - fee_rate: this.feeRate, - masterpubs: this.accounts.map(w => ({ - id: w.id, - public_key: w.masterpub, - fingerprint: w.fingerprint - })) - } - tx.inputs = this.utxos - .filter(utxo => utxo.selected) - .map(mapUtxoToPsbtInput) - .sort((a, b) => - a.tx_id < b.tx_id ? -1 : a.tx_id > b.tx_id ? 1 : a.vout - b.vout - ) - - tx.outputs = this.sendToList.map(out => ({ - address: out.address, - amount: out.amount - })) - - if (!excludeChange) { - const change = this.createChangeOutput() - const diffAmount = this.selectedAmount - this.totalPayedAmount - if (diffAmount >= this.DUST_LIMIT) { - tx.outputs.push(change) - } - } - tx.tx_size = Math.round(txSize(tx)) - tx.inputs = _.shuffle(tx.inputs) - tx.outputs = _.shuffle(tx.outputs) - - return tx - }, - createChangeOutput: function () { - const change = this.changeAddress - const walletAcount = - this.accounts.find(w => w.id === change.wallet) || {} - - return { - address: change.address, - address_index: change.addressIndex, - branch_index: change.isChange ? 1 : 0, - wallet: walletAcount.id - } - }, - selectChangeAddress: function (account) { - if (!account) this.changeAddress = '' - this.changeAddress = - this.addresses.find( - a => a.wallet === account.id && a.isChange && !a.hasActivity - ) || {} - }, - updateChangeAddress: function () { - if (this.changeWallet) { - const changeAccount = (this.accounts || []).find( - w => w.id === this.changeWallet.id - ) - // change account deleted - if (!changeAccount) { - this.changeWallet = this.accounts[0] - } - } else { - this.changeWallet = this.accounts[0] - } - this.selectChangeAddress(this.changeWallet) - }, - updateSignedPsbt: async function (psbtBase64) { - try { - this.showChecking = true - this.psbtBase64Signed = psbtBase64 - - const data = await this.extractTxFromPsbt(psbtBase64) - this.showFinalTx = true - if (data) { - this.signedTx = JSON.parse(data.tx_json) - this.signedTxHex = data.tx_hex - } else { - this.signedTx = null - this.signedTxHex = null - } - } finally { - this.showChecking = false - } - }, - - fetchUtxoHexForPsbt: async function (psbtBase64) { - if (this.tx?.inputs && this.tx?.inputs.length) return this.tx.inputs - - const {data: psbtUtxos} = await LNbits.api.request( - 'PUT', - '/watchonly/api/v1/psbt/utxos', - this.adminkey, - {psbtBase64} - ) - - const inputs = [] - for (const utxo of psbtUtxos) { - const txHex = await this.fetchTxHex(utxo.tx_id) - inputs.push({tx_hex: txHex}) - } - return inputs - }, - extractTxFromPsbt: async function (psbtBase64) { - try { - const inputs = await this.fetchUtxoHexForPsbt(psbtBase64) - - const {data} = await LNbits.api.request( - 'PUT', - '/watchonly/api/v1/psbt/extract', - this.adminkey, - { - psbtBase64, - inputs, - network: this.network - } - ) - return data - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Cannot finalize PSBT!', - caption: `${error}`, - timeout: 10000 - }) - LNbits.utils.notifyApiError(error) - } - }, - broadcastTransaction: async function () { - try { - const {data} = await LNbits.api.request( - 'POST', - '/watchonly/api/v1/tx', - this.adminkey, - {tx_hex: this.signedTxHex} - ) - this.sentTxId = data - - this.$q.notify({ - type: 'positive', - message: 'Transaction broadcasted!', - caption: `${data}`, - timeout: 10000 - }) - - this.clearState() - this.$emit('broadcast-done', this.sentTxId) - } catch (error) { - this.sentTxId = null - this.$q.notify({ - type: 'warning', - message: 'Failed to broadcast!', - caption: `${error}`, - timeout: 10000 - }) - } finally { - this.showFinalTx = false - } - }, - fetchTxHex: async function (txId) { - const { - bitcoin: {transactions: transactionsAPI} - } = mempoolJS({ - hostname: this.mempoolEndpoint - }) - - try { - const response = await transactionsAPI.getTxHex({txid: txId}) - return response - } catch (error) { - this.$q.notify({ - type: 'warning', - message: `Failed to fetch transaction details for tx id: '${txId}'`, - timeout: 10000 - }) - LNbits.utils.notifyApiError(error) - throw error - } - }, - handleOutputsChange: function () { - this.$refs.utxoList.refreshUtxoSelection(this.totalPayedAmount) - }, - getTotalPaymentAmount: function () { - return this.sendToList.reduce((t, a) => t + (a.amount || 0), 0) - } - }, - - created: async function () {} - }) -} diff --git a/lnbits/extensions/watchonly/static/components/seed-input/seed-input.html b/lnbits/extensions/watchonly/static/components/seed-input/seed-input.html deleted file mode 100644 index 60cdaaa8..00000000 --- a/lnbits/extensions/watchonly/static/components/seed-input/seed-input.html +++ /dev/null @@ -1,80 +0,0 @@ -
-
-
-
Seed Input Done
-
-
-
-
-
Word Count
-
- -
-
-
-
-
-
Enter word at position: {{actualPosition}}
-
-
-
-
- Previous -
-
- -
- -
- Next - Done -
- -
-
-
diff --git a/lnbits/extensions/watchonly/static/components/seed-input/seed-input.js b/lnbits/extensions/watchonly/static/components/seed-input/seed-input.js deleted file mode 100644 index 5e415abb..00000000 --- a/lnbits/extensions/watchonly/static/components/seed-input/seed-input.js +++ /dev/null @@ -1,102 +0,0 @@ -async function seedInput(path) { - const template = await loadTemplateAsync(path) - Vue.component('seed-input', { - name: 'seed-input', - template, - - computed: { - actualPosition: function () { - return this.words[this.currentPosition].position - } - }, - - data: function () { - return { - wordCountOptions: ['12', '15', '18', '21', '24'], - wordCount: 24, - words: [], - currentPosition: 0, - stringOptions: [], - options: [], - currentWord: '', - done: false - } - }, - - methods: { - filterFn(val, update, abort) { - update(() => { - const needle = val.toLocaleLowerCase() - this.options = this.stringOptions - .filter(v => v.toLocaleLowerCase().indexOf(needle) != -1) - .sort((a, b) => { - if (a.startsWith(needle)) { - if (b.startsWith(needle)) { - return a - b - } - return -1 - } else { - if (b.startsWith(needle)) { - return 1 - } - return a - b - } - }) - }) - }, - initWords() { - const words = [] - for (let i = 1; i <= this.wordCount; i++) { - words.push({ - position: i, - value: '' - }) - } - this.currentPosition = 0 - this.words = _.shuffle(words) - }, - setModel(val) { - this.currentWord = val - this.words[this.currentPosition].value = this.currentWord - }, - nextPosition() { - if (this.currentPosition < this.wordCount - 1) { - this.currentPosition++ - } - this.currentWord = this.words[this.currentPosition].value - }, - previousPosition() { - if (this.currentPosition > 0) { - this.currentPosition-- - } - this.currentWord = this.words[this.currentPosition].value - }, - seedInputDone() { - const badWordPositions = this.words - .filter(w => !w.value || !this.stringOptions.includes(w.value)) - .map(w => w.position) - if (badWordPositions.length) { - this.$q.notify({ - timeout: 10000, - type: 'warning', - message: - 'The seed has incorrect words. Please check at these positions: ', - caption: 'Position: ' + badWordPositions.join(', ') - }) - return - } - const mnemonic = this.words - .sort((a, b) => a.position - b.position) - .map(w => w.value) - .join(' ') - this.$emit('on-seed-input-done', mnemonic) - this.done = true - } - }, - - created: async function () { - this.stringOptions = bip39WordList - this.initWords() - } - }) -} diff --git a/lnbits/extensions/watchonly/static/components/send-to/send-to.html b/lnbits/extensions/watchonly/static/components/send-to/send-to.html deleted file mode 100644 index c16ebf95..00000000 --- a/lnbits/extensions/watchonly/static/components/send-to/send-to.html +++ /dev/null @@ -1,77 +0,0 @@ -
-
- - - -
-
- Add -
-
-
- Payed Amount: - - {{satBtc(getTotalPaymentAmount())}} - -
-
-
-
-
diff --git a/lnbits/extensions/watchonly/static/components/send-to/send-to.js b/lnbits/extensions/watchonly/static/components/send-to/send-to.js deleted file mode 100644 index 2b93cea7..00000000 --- a/lnbits/extensions/watchonly/static/components/send-to/send-to.js +++ /dev/null @@ -1,81 +0,0 @@ -async function sendTo(path) { - const template = await loadTemplateAsync(path) - Vue.component('send-to', { - name: 'send-to', - template, - - props: [ - 'data', - 'tx-size', - 'selected-amount', - 'fee-rate', - 'sats-denominated' - ], - - computed: { - dataLocal: { - get: function () { - return this.data - }, - set: function (value) { - console.log('### computed update data', value) - this.$emit('update:data', value) - } - } - }, - - data: function () { - return { - DUST_LIMIT: 546, - paymentTable: { - columns: [ - { - name: 'data', - align: 'left' - } - ], - pagination: { - rowsPerPage: 10 - }, - filter: '' - } - } - }, - - methods: { - satBtc(val, showUnit = true) { - return satOrBtc(val, showUnit, this.satsDenominated) - }, - addPaymentAddress: function () { - this.dataLocal.push({address: '', amount: undefined}) - this.handleOutputsChange() - }, - deletePaymentAddress: function (v) { - const index = this.dataLocal.indexOf(v) - if (index !== -1) { - this.dataLocal.splice(index, 1) - } - this.handleOutputsChange() - }, - - sendMaxToAddress: function (paymentAddress = {}) { - const feeValue = this.feeRate * this.txSize - const inputAmount = this.selectedAmount - const currentAmount = Math.max(0, paymentAddress.amount || 0) - const payedAmount = this.getTotalPaymentAmount() - currentAmount - paymentAddress.amount = Math.max( - 0, - inputAmount - payedAmount - feeValue - ) - }, - handleOutputsChange: function () { - this.$emit('update:outputs') - }, - getTotalPaymentAmount: function () { - return this.dataLocal.reduce((t, a) => t + (a.amount || 0), 0) - } - }, - - created: async function () {} - }) -} diff --git a/lnbits/extensions/watchonly/static/components/serial-port-config/serial-port-config.html b/lnbits/extensions/watchonly/static/components/serial-port-config/serial-port-config.html deleted file mode 100644 index 18e52058..00000000 --- a/lnbits/extensions/watchonly/static/components/serial-port-config/serial-port-config.html +++ /dev/null @@ -1,100 +0,0 @@ -
-
-
- -
-
- -
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
-
-
- -
-
- -
-
- -
-
- - -
-
- -
-
-
-
- -
-
-
diff --git a/lnbits/extensions/watchonly/static/components/serial-port-config/serial-port-config.js b/lnbits/extensions/watchonly/static/components/serial-port-config/serial-port-config.js deleted file mode 100644 index 87b54e38..00000000 --- a/lnbits/extensions/watchonly/static/components/serial-port-config/serial-port-config.js +++ /dev/null @@ -1,12 +0,0 @@ -async function serialPortConfig(path) { - const t = await loadTemplateAsync(path) - Vue.component('serial-port-config', { - name: 'serial-port-config', - props: ['config'], - template: t, - data() { - return {} - }, - methods: {} - }) -} diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html deleted file mode 100644 index a95a1906..00000000 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.html +++ /dev/null @@ -1,544 +0,0 @@ -
- - - - - Login - Enter password for Hardware Wallet. - - - - - - Logout - Clear password for HWW. - - - - - Config & Connect - Set the Serial Port communication parameters. - - - - - Paired Device ({{device.config.name || 'no-name'}}) - - {{device.id}} - - - Forget - - - - - - Disconnect - Disconnect from Serial Port. - - - - - - Restore - Restore wallet from existing word list. - - - - - Show Seed - Show seed on the Hardware Wallet display. - - - - - Wipe - Clean-up the wallet. New random seed. - - - - - Help - View available comands. - - - - - Console - Show the serial port communication messages - - - - - - - - - Enter Config - - -
- Connect - Cancel -
-
-
-
- - - - - Enter password for Hardware Wallet (8 numbers/letters) - - - - - - - -
- -
- Login - Cancel -
-
-
-
- - - - -
-
-
- Output {{hww.confirm.outputIndex}} - - change - -
-
-
-
- Address: -
-
- {{tx.outputs[hww.confirm.outputIndex].address}} -
-
-
-
- Amount: -
-
- {{satBtc(tx.outputs[hww.confirm.outputIndex].amount)}} -
-
-
-
- Fee: -
-
- {{satBtc(tx.feeValue)}} -
-
-
-
- Fee Rate: -
-
- {{tx.feeRate}} sats/vbyte -
-
-
-
-
- - Confirm then check the Hardware Device. - -
-
-
-
- - - -
-
- - -
-
- Cancel -
-
-
-
-
- - - - - - This action will remove all data from the Hardware Wallet. Please - create a back-up for the seed! - - Enter new password for Hardware Wallet (8 numbers/letters) - - - - - This action cannot be reversed! - - -
- Wipe - Cancel -
-
-
-
- - - - - -
- Close -
-
-
- - - -
-
- - Open the browser Developer Console for more Details! - -
-
- - -
- Close -
-
-
- - - - Check word at position {{hww.seedWordPosition}} on device -
-
- -
-
-
-
- -
-
- -
-
- Prev -
-
- Next -
-
- Close -
-
-
-
- - - - - - For test purposes only. Do not enter word list with real funds!!! - -
- -
- -
- - - -
- - -
- -
- Enter new password (8 numbers/letters) - - - - - -
- - - ALL existing data on the Hardware Device will be lost. - - -
- Restore - Cancel -
-
-
-
-
diff --git a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js b/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js deleted file mode 100644 index 2e80b1e0..00000000 --- a/lnbits/extensions/watchonly/static/components/serial-signer/serial-signer.js +++ /dev/null @@ -1,1008 +0,0 @@ -async function serialSigner(path) { - const t = await loadTemplateAsync(path) - Vue.component('serial-signer', { - name: 'serial-signer', - template: t, - - props: ['sats-denominated', 'network'], - data: function () { - return { - selectedPort: null, - writableStreamClosed: null, - writer: null, - readableStreamClosed: null, - reader: null, - receivedData: '', - config: {}, - decryptionKey: null, - sharedSecret: null, - - hww: { - password: null, - showPassword: false, - mnemonic: null, - showMnemonic: false, - quickMnemonicInput: false, - passphrase: null, - showPassphrase: false, - hasPassphrase: false, - authenticated: false, - showPasswordDialog: false, - showConfigDialog: false, - showWipeDialog: false, - showRestoreDialog: false, - showConfirmationDialog: false, - showSignedPsbt: false, - sendingPsbt: false, - signingPsbt: false, - loginResolve: null, - psbtSentResolve: null, - xpubResolve: null, - seedWordPosition: 1, - seedWord: null, - showSeedWord: false, - showSeedDialog: false, - // config: null, - - confirm: { - outputIndex: 0, - showFee: false - } - }, - tx: null, // todo: move to hww - - showConsole: false, - showPairedDevices: true - } - }, - - computed: { - pairedDevices: { - cache: false, - get: function () { - return ( - JSON.parse(window.localStorage.getItem('lnbits-paired-devices')) || - [] - ) - }, - set: function (devices) { - window.localStorage.setItem( - 'lnbits-paired-devices', - JSON.stringify(devices) - ) - } - } - }, - - methods: { - satBtc(val, showUnit = true) { - return satOrBtc(val, showUnit, this.satsDenominated) - }, - openSerialPortDialog: async function () { - this.config = {...HWW_DEFAULT_CONFIG} - await this.openSerialPort(this.config) - }, - openSerialPort: async function (config = {baudRate: 9600}) { - if (!this.checkSerialPortSupported()) return false - if (this.selectedPort) { - this.$q.notify({ - type: 'warning', - message: 'Already connected. Disconnect first!', - timeout: 10000 - }) - return true - } - - try { - this.selectedPort = await navigator.serial.requestPort() - this.selectedPort.addEventListener('connect', event => { - // do nothing - }) - - this.selectedPort.addEventListener('disconnect', () => { - this.selectedPort = null - this.hww.authenticated = false - this.$q.notify({ - type: 'warning', - message: 'Disconnected from Serial Port!', - timeout: 10000 - }) - }) - - // Wait for the serial port to open. - await this.selectedPort.open(config) - // do not await - this.startSerialPortReading() - // wait to init - sleep(1000) - - const textEncoder = new TextEncoderStream() - this.writableStreamClosed = textEncoder.readable.pipeTo( - this.selectedPort.writable - ) - - this.writer = textEncoder.writable.getWriter() - - await this.hwwPing() - - return true - } catch (error) { - this.selectedPort = null - this.$q.notify({ - type: 'warning', - message: 'Cannot open serial port!', - caption: `${error}`, - timeout: 10000 - }) - return false - } - }, - openSerialPortConfig: async function (deviceId) { - const device = this.getPairedDevice(deviceId) - if (device) { - this.config = device.config - } else { - this.config = {...HWW_DEFAULT_CONFIG} - } - this.hww.showConfigDialog = true - }, - closeSerialPort: async function () { - try { - if (this.writer) this.writer.close() - if (this.writableStreamClosed) await this.writableStreamClosed - if (this.reader) this.reader.cancel() - if (this.readableStreamClosed) - await this.readableStreamClosed.catch(() => { - /* Ignore the error */ - }) - if (this.selectedPort) await this.selectedPort.close() - this.$q.notify({ - type: 'positive', - message: 'Serial port disconnected!', - timeout: 5000 - }) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Cannot close serial port!', - caption: `${error}`, - timeout: 10000 - }) - } finally { - this.selectedPort = null - this.hww.authenticated = false - } - }, - - isConnected: function () { - return !!this.selectedPort - }, - isAuthenticated: function () { - return this.hww.authenticated - }, - - seedInputDone: function (mnemonic) { - this.hww.mnemonic = mnemonic - }, - isAuthenticating: function () { - if (this.isAuthenticated()) return false - return new Promise(resolve => { - this.loginResolve = resolve - }) - }, - - isSendingPsbt: async function () { - if (!this.hww.sendingPsbt) return false - return new Promise(resolve => { - this.psbtSentResolve = resolve - }) - }, - - isFetchingXpub: async function () { - return new Promise(resolve => { - this.xpubResolve = resolve - }) - }, - - checkSerialPortSupported: function () { - if (!navigator.serial) { - this.$q.notify({ - type: 'warning', - message: 'Serial port communication not supported!', - caption: - 'Make sure your browser supports Serial Port and that you are using HTTPS.', - timeout: 10000 - }) - return false - } - return true - }, - startSerialPortReading: async function () { - const port = this.selectedPort - - while (port && port.readable) { - const textDecoder = new TextDecoderStream() - this.readableStreamClosed = port.readable.pipeTo(textDecoder.writable) - this.reader = textDecoder.readable.getReader() - const readStringUntil = readFromSerialPort(this.reader) - - try { - while (true) { - const {value, done} = await readStringUntil('\n') - if (value) { - const {command, commandData} = await this.extractCommand(value) - this.handleSerialPortResponse(command, commandData) - this.updateSerialPortConsole(command) - } - if (done) return - } - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Serial port communication error!', - caption: `${error}`, - timeout: 10000 - }) - } - } - }, - handleSerialPortResponse: async function (command, commandData) { - this.logPublicCommandsResponse(command, commandData) - - switch (command) { - case COMMAND_PING: - this.handlePingResponse(commandData) - break - case COMMAND_CHECK_PAIRING: - this.handleCheckPairingResponse(commandData) - break - case COMMAND_SIGN_PSBT: - this.handleSignResponse(commandData) - break - case COMMAND_PASSWORD: - this.handleLoginResponse(commandData) - break - case COMMAND_PASSWORD_CLEAR: - this.handleLogoutResponse(commandData) - break - case COMMAND_SEND_PSBT: - this.handleSendPsbtResponse(commandData) - break - case COMMAND_WIPE: - this.handleWipeResponse(commandData) - break - case COMMAND_XPUB: - this.handleXpubResponse(commandData) - break - case COMMAND_SEED: - this.handleShowSeedResponse(commandData) - break - case COMMAND_PAIR: - this.handlePairResponse(commandData) - break - case COMMAND_LOG: - console.log( - ` %c${commandData}`, - 'background: #222; color: #bada55' - ) - break - default: - console.log(` %c${command}`, 'background: #222; color: red') - } - }, - logPublicCommandsResponse: function (command, commandData) { - switch (command) { - case COMMAND_SIGN_PSBT: - case COMMAND_PASSWORD: - case COMMAND_PASSWORD_CLEAR: - case COMMAND_SEND_PSBT: - case COMMAND_WIPE: - case COMMAND_XPUB: - case COMMAND_PAIR: - console.log( - ` %c${command} ${commandData}`, - 'background: #222; color: yellow' - ) - } - }, - updateSerialPortConsole: function (value) { - this.receivedData += value + '\n' - const textArea = document.getElementById('serial-port-console') - if (textArea) textArea.scrollTop = textArea.scrollHeight - }, - hwwPing: async function () { - try { - // Send an empty ping. The serial port buffer might have some jubk data. Flush it. - await this.sendCommandClearText(COMMAND_PING) - await this.sendCommandClearText(COMMAND_PING, [window.location.host]) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to ping Hardware Wallet!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - handlePingResponse: function (res = '') { - const [status, deviceId] = res.split(' ') - this.deviceId = deviceId - - if (!this.deviceId) { - this.$q.notify({ - type: 'warning', - message: 'Missing device ID for Hardware Wallet', - timeout: 10000 - }) - return - } - - const device = this.getPairedDevice(deviceId) - - if (device) { - this.sharedSecret = nobleSecp256k1.utils.hexToBytes( - device.sharedSecretHex - ) - this.hwwCheckPairing() - } else { - this.hwwPair() - } - }, - hwwShowPasswordDialog: async function () { - try { - this.hww.showPasswordDialog = true - await this.sendCommandSecure(COMMAND_PASSWORD) - } catch (error) { - console.log(error) - this.$q.notify({ - type: 'warning', - message: 'Failed to connect to Hardware Wallet!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - hwwShowWipeDialog: async function () { - try { - this.hww.showWipeDialog = true - await this.sendCommandSecure(COMMAND_WIPE) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to connect to Hardware Wallet!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - hwwShowRestoreDialog: async function () { - try { - this.hww.showRestoreDialog = true - await this.sendCommandSecure(COMMAND_RESTORE) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to connect to Hardware Wallet!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - closeSeedDialog: function () { - this.hww.seedWord = null - this.hww.showSeedWord = false - }, - hwwConfirmNext: async function () { - this.hww.confirm.outputIndex += 1 - if (this.hww.confirm.outputIndex >= this.tx.outputs.length) { - this.hww.confirm.showFee = true - } - await this.sendCommandSecure(COMMAND_CONFIRM_NEXT) - }, - cancelOperation: async function () { - try { - await this.sendCommandSecure(COMMAND_CANCEL) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to send cancel!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - hwwConfigAndConnect: async function () { - this.hww.showConfigDialog = false - if (this.config.deviceId) { - this.updatePairedDeviceConfig(this.config.deviceId, this.config) - } - await this.openSerialPort(this.config) - return true - }, - hwwLogin: async function () { - try { - await this.sendCommandSecure(COMMAND_PASSWORD, [ - this.hww.password, - this.hww.passphrase - ]) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to send password to Hardware Wallet!', - caption: `${error}`, - timeout: 10000 - }) - } finally { - this.hww.showPasswordDialog = false - this.hww.password = null - this.hww.passphrase = null - this.hww.showPassword = false - this.hww.showPassphrase = false - } - }, - handleLoginResponse: function (res = '') { - this.hww.authenticated = res.trim() === '1' - if (this.loginResolve) { - this.loginResolve(this.hww.authenticated) - } - - if (this.hww.authenticated) { - this.$q.notify({ - type: 'positive', - message: 'Login successfull!', - timeout: 10000 - }) - } else { - this.$q.notify({ - type: 'warning', - message: 'Wrong password, try again!', - timeout: 10000 - }) - } - }, - hwwLogout: async function () { - try { - await this.sendCommandSecure(COMMAND_PASSWORD_CLEAR) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to logout from Hardware Wallet!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - hwwShowAddress: async function (path, address) { - try { - await this.sendCommandSecure(COMMAND_ADDRESS, [ - this.network, - path, - address - ]) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to logout from Hardware Wallet!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - handleLogoutResponse: function (res = '') { - const authenticated = !(res.trim() === '1') - if (this.hww.authenticated && !authenticated) { - this.$q.notify({ - type: 'positive', - message: 'Logged Out', - timeout: 10000 - }) - } - this.hww.authenticated = authenticated - }, - hwwSendPsbt: async function (psbtBase64, tx) { - try { - this.tx = tx - this.hww.sendingPsbt = true - await this.sendCommandSecure(COMMAND_SEND_PSBT, [ - this.network, - psbtBase64 - ]) - this.$q.notify({ - type: 'positive', - message: 'Data sent to serial port device!', - timeout: 5000 - }) - } catch (error) { - this.hww.sendingPsbt = false - this.$q.notify({ - type: 'warning', - message: 'Failed to send data to serial port!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - handleSendPsbtResponse: function (res = '') { - try { - const psbtOK = res.trim() === '1' - if (!psbtOK) { - this.$q.notify({ - type: 'warning', - message: 'Failed to send PSBT!', - caption: `${res}`, - timeout: 10000 - }) - return - } - this.hww.confirm.outputIndex = 0 - this.hww.showConfirmationDialog = true - this.hww.confirm = { - outputIndex: 0, - showFee: false - } - this.hww.sendingPsbt = false - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to send PSBT!', - caption: `${error}`, - timeout: 10000 - }) - } finally { - this.psbtSentResolve() - } - }, - hwwSignPsbt: async function () { - try { - this.hww.showConfirmationDialog = false - this.hww.signingPsbt = true - await this.sendCommandSecure(COMMAND_SIGN_PSBT) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to sign PSBT!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - handleSignResponse: function (res = '') { - this.hww.signingPsbt = false - const [count, psbt] = res.trim().split(' ') - if (!psbt || !count || count.trim() === '0') { - this.$q.notify({ - type: 'warning', - message: 'No input signed!', - caption: 'Are you using the right seed?', - timeout: 10000 - }) - return - } - this.updateSignedPsbt(psbt) - this.$q.notify({ - type: 'positive', - message: 'Transaction Signed', - message: `Inputs signed: ${count}`, - timeout: 10000 - }) - }, - hwwCheckPairing: async function () { - const iv = window.crypto.getRandomValues(new Uint8Array(16)) - const encrypted = await this.encryptMessage( - this.sharedSecret, // todo: revisit - iv, - PAIRING_CONTROL_TEXT.length + ' ' + PAIRING_CONTROL_TEXT - ) - - const encryptedHex = nobleSecp256k1.utils.bytesToHex(encrypted) - const encryptedIvHex = nobleSecp256k1.utils.bytesToHex(iv) - try { - await this.sendCommandClearText(COMMAND_CHECK_PAIRING, [ - encryptedHex + encryptedIvHex - ]) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to check secure connection!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - handleCheckPairingResponse: async function (res = '') { - const [statusCode, message] = res.split(' ') - switch (statusCode) { - case '0': - const controlText = await this.decryptData(message) - if (controlText == PAIRING_CONTROL_TEXT) { - this.$q.notify({ - type: 'positive', - message: 'Re-paired with success!', - timeout: 10000 - }) - } else { - this.$q.notify({ - type: 'warning', - message: 'Re-pairing failed!', - caption: 'Remove (forget) device and try again!', - timeout: 10000 - }) - } - break - case '1': - this.closeSerialPort() - this.$q.notify({ - type: 'warning', - message: - 'Re-pairing failed. Remove (forget) device and try again!', - caption: `Error: ${message}`, - timeout: 10000 - }) - break - default: - // noting to do here yet - break - } - }, - hwwPair: async function () { - try { - this.decryptionKey = nobleSecp256k1.utils.randomPrivateKey() - const publicKey = nobleSecp256k1.Point.fromPrivateKey( - this.decryptionKey - ) - const publicKeyHex = publicKey.toHex().slice(2) - - const args = [publicKeyHex] - if (Number.isInteger(+this.config.buttonOnePin)) { - args.push(this.config.buttonOnePin) - } - if (Number.isInteger(+this.config.buttonTwoPin)) { - args.push(this.config.buttonTwoPin) - } - await this.sendCommandClearText(COMMAND_PAIR, args) - this.$q.notify({ - type: 'positive', - message: 'Pairing started!', - timeout: 5000 - }) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to pair with device!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - handlePairResponse: async function (res = '') { - const [statusCode, data] = res.trim().split(' ') - let pubKeyHex, errorMessage, captionMessage - switch (statusCode) { - case '0': - pubKeyHex = data - if (!data) errorMessage = 'Failed to exchange DH secret!' - break - case '1': - errorMessage = - 'Device pairing only possible in the first 10 seconds after start-up!' - captionMessage = 'Restart and try again' - break - - default: - errorMessage = 'Unexpected error code' - break - } - - if (errorMessage) { - this.$q.notify({ - type: 'warning', - message: errorMessage, - caption: captionMessage || '', - timeout: 10000 - }) - this.closeSerialPort() - return - } - const hwwPublicKey = nobleSecp256k1.Point.fromHex('04' + pubKeyHex) - - this.sharedSecret = nobleSecp256k1 - .getSharedSecret(this.decryptionKey, hwwPublicKey) - .slice(1, 33) - - const sharedSecretHex = nobleSecp256k1.utils.bytesToHex( - this.sharedSecret - ) - const sharedSecredHash = await nobleSecp256k1.utils.sha256( - asciiToUint8Array(sharedSecretHex) - ) - const fingerprint = nobleSecp256k1.utils - .bytesToHex(sharedSecredHash) - .substring(0, 5) - .toUpperCase() - - LNbits.utils - .confirmDialog('Confirm code from display: ' + fingerprint) - .onOk(() => { - this.addPairedDevice( - this.deviceId, - nobleSecp256k1.utils.bytesToHex(this.sharedSecret), - this.config - ) - - this.$q.notify({ - type: 'positive', - message: 'Paired with device!', - timeout: 5000 - }) - }) - .onCancel(() => { - this.closeSerialPort() - }) - }, - hwwHelp: async function () { - try { - await this.sendCommandSecure(COMMAND_HELP) - this.$q.notify({ - type: 'positive', - message: 'Check display or console for details!', - timeout: 5000 - }) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to ask for help!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - hwwWipe: async function () { - try { - this.hww.showWipeDialog = false - await this.sendCommandSecure(COMMAND_WIPE, [this.hww.password]) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to wipe!', - caption: `${error}`, - timeout: 10000 - }) - } finally { - this.hww.password = null - this.hww.confirmedPassword = null - this.hww.showPassword = false - } - }, - handleWipeResponse: function (res = '') { - const wiped = res.trim() === '1' - if (wiped) { - this.$q.notify({ - type: 'positive', - message: 'Wallet wiped!', - timeout: 10000 - }) - } else { - this.$q.notify({ - type: 'warning', - message: 'Failed to wipe wallet!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - hwwXpub: async function (path) { - try { - await this.sendCommandSecure(COMMAND_XPUB, [this.network, path]) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to fetch XPub!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - handleXpubResponse: function (res = '') { - const args = res.trim().split(' ') - if (args.length < 3 || args[0].trim() !== '1') { - this.$q.notify({ - type: 'warning', - message: 'Failed to fetch XPub!', - caption: `${res}`, - timeout: 10000 - }) - this.xpubResolve({}) - return - } - const xpub = args[1].trim() - const fingerprint = args[2].trim() - this.xpubResolve({xpub, fingerprint}) - }, - - hwwShowSeed: async function () { - try { - this.hww.showSeedDialog = true - this.hww.seedWordPosition = 1 - - await this.sendCommandSecure(COMMAND_SEED, [ - this.hww.seedWordPosition - ]) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to show seed!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - showNextSeedWord: async function () { - this.hww.seedWordPosition++ - await this.sendCommandSecure(COMMAND_SEED, [this.hww.seedWordPosition]) - }, - showPrevSeedWord: async function () { - this.hww.seedWordPosition = Math.max(1, this.hww.seedWordPosition - 1) - await this.sendCommandSecure(COMMAND_SEED, [this.hww.seedWordPosition]) - }, - handleShowSeedResponse: function (res = '') { - const [pos, word] = res.trim().split(' ') - this.hww.seedWord = `${pos}. ${word}` - this.hww.seedWordPosition = pos - }, - hwwRestore: async function () { - try { - await this.sendCommandSecure(COMMAND_RESTORE, [ - this.hww.password, - this.hww.mnemonic - ]) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to restore from seed!', - caption: `${error}`, - timeout: 10000 - }) - } finally { - this.hww.showRestoreDialog = false - this.hww.mnemonic = null - this.hww.showMnemonic = false - this.hww.password = null - this.hww.confirmedPassword = null - this.hww.showPassword = false - } - }, - - updateSignedPsbt: async function (value) { - this.$emit('signed:psbt', value) - }, - - sendCommandSecure: async function (command, attrs = []) { - const message = [command].concat(attrs).join(' ') - const iv = window.crypto.getRandomValues(new Uint8Array(16)) - if (!this.sharedSecret || !this.sharedSecret.length) { - throw new Error( - `Secure connection not estabileshed. Tried to run command: ${command}` - ) - } - const encrypted = await this.encryptMessage( - this.sharedSecret, - iv, - message.length + ' ' + message - ) - - const encryptedHex = nobleSecp256k1.utils.bytesToHex(encrypted) - const encryptedIvHex = nobleSecp256k1.utils.bytesToHex(iv) - await this.writer.write(encryptedHex + encryptedIvHex + '\n') - }, - sendCommandClearText: async function (command, attrs = []) { - const message = [command].concat(attrs).join(' ') - await this.writer.write(message + '\n') - }, - extractCommand: async function (value) { - const command = value.split(' ')[0] - const commandData = value.substring(command.length).trim() - - if ( - command === COMMAND_PAIR || - command === COMMAND_LOG || - command === COMMAND_PASSWORD_CLEAR || - command === COMMAND_PING || - command === COMMAND_CHECK_PAIRING - ) - return {command, commandData} - - const decryptedValue = await this.decryptData(value) - const decryptedCommand = decryptedValue.split(' ')[0] - const decryptedCommandData = decryptedValue - .substring(decryptedCommand.length) - .trim() - return { - command: decryptedCommand, - commandData: decryptedCommandData - } - }, - decryptData: async function (value) { - if (!this.sharedSecret) { - console.log('/error Secure session not established!') - return '/error Secure session not established!' - } - try { - const ivSize = 32 - const messageHex = value.substring(0, value.length - ivSize) - const ivHex = value.substring(value.length - ivSize) - const messageBytes = nobleSecp256k1.utils.hexToBytes(messageHex) - const iv = nobleSecp256k1.utils.hexToBytes(ivHex) - const decrypted1 = await this.decryptMessage( - this.sharedSecret, - iv, - messageBytes - ) - const data = new TextDecoder().decode(decrypted1) - const [len] = data.split(' ') - const command = data - .substring(len.length + 1, +len + len.length + 1) - .trim() - return command - } catch (error) { - console.log('/error Failed to decrypt message from device!') - return '/error Failed to decrypt message from device!' - } - }, - encryptMessage: async function (key, iv, message) { - while (message.length % 16 !== 0) message += ' ' - const encodedMessage = asciiToUint8Array(message) - - const aesCbc = new aesjs.ModeOfOperation.cbc(key, iv) - const encryptedBytes = aesCbc.encrypt(encodedMessage) - - return encryptedBytes - }, - decryptMessage: async function (key, iv, encryptedBytes) { - const aesCbc = new aesjs.ModeOfOperation.cbc(key, iv) - const decryptedBytes = aesCbc.decrypt(encryptedBytes) - return decryptedBytes - }, - - getPairedDevice: function (deviceId) { - return this.pairedDevices.find(d => d.id === deviceId) - }, - removePairedDevice: function (deviceId) { - const devices = this.pairedDevices - const deviceIndex = devices.findIndex(d => d.id === deviceId) - if (deviceIndex !== -1) { - devices.splice(deviceIndex, 1) - } - this.pairedDevices = devices - this.showPairedDevices = false - setTimeout(() => { - // force UI refresh - this.showPairedDevices = true - }) - }, - addPairedDevice: function (deviceId, sharedSecretHex, config) { - const devices = this.pairedDevices - config.deviceId = deviceId - devices.unshift({ - id: deviceId, - sharedSecretHex: sharedSecretHex, - pairingDate: new Date().toISOString(), - config - }) - this.pairedDevices = devices - this.showPairedDevices = false - setTimeout(() => { - // force UI refresh - this.showPairedDevices = true - }) - }, - updatePairedDeviceConfig(deviceId, config) { - const device = this.getPairedDevice(deviceId) - if (device) { - this.removePairedDevice(deviceId) - this.addPairedDevice(deviceId, device.sharedSecretHex, config) - } - } - }, - created: async function () {} - }) -} diff --git a/lnbits/extensions/watchonly/static/components/utxo-list/utxo-list.html b/lnbits/extensions/watchonly/static/components/utxo-list/utxo-list.html deleted file mode 100644 index cb1b930b..00000000 --- a/lnbits/extensions/watchonly/static/components/utxo-list/utxo-list.html +++ /dev/null @@ -1,144 +0,0 @@ - - -
-
- -
-
- -
-
-
-
- - - -
-
- - - - -
diff --git a/lnbits/extensions/watchonly/static/components/utxo-list/utxo-list.js b/lnbits/extensions/watchonly/static/components/utxo-list/utxo-list.js deleted file mode 100644 index 6741ed94..00000000 --- a/lnbits/extensions/watchonly/static/components/utxo-list/utxo-list.js +++ /dev/null @@ -1,148 +0,0 @@ -async function utxoList(path) { - const template = await loadTemplateAsync(path) - Vue.component('utxo-list', { - name: 'utxo-list', - template, - - props: [ - 'utxos', - 'accounts', - 'selectable', - 'payed-amount', - 'sats-denominated', - 'mempool-endpoint', - 'filter' - ], - - data: function () { - return { - utxosTable: { - columns: [ - { - name: 'expand', - align: 'left', - label: '' - }, - { - name: 'selected', - align: 'left', - label: '', - selectable: true - }, - { - name: 'status', - align: 'center', - label: 'Status', - sortable: true - }, - { - name: 'address', - align: 'left', - label: 'Address', - field: 'address', - sortable: true - }, - { - name: 'amount', - align: 'left', - label: 'Amount', - field: 'amount', - sortable: true - }, - { - name: 'date', - align: 'left', - label: 'Date', - field: 'date', - sortable: true - }, - { - name: 'wallet', - align: 'left', - label: 'Account', - field: 'wallet', - sortable: true - } - ], - pagination: { - rowsPerPage: 10 - } - }, - utxoSelectionModes: [ - 'Manual', - 'Random', - 'Select All', - 'Smaller Inputs First', - 'Larger Inputs First' - ], - utxoSelectionMode: 'Random', - utxoSelectAmount: 0 - } - }, - - computed: { - columns: function () { - return this.utxosTable.columns.filter(c => - c.selectable ? this.selectable : true - ) - } - }, - - methods: { - satBtc(val, showUnit = true) { - return satOrBtc(val, showUnit, this.satsDenominated) - }, - getWalletName: function (walletId) { - const wallet = (this.accounts || []).find(wl => wl.id === walletId) - return wallet ? wallet.title : 'unknown' - }, - getTotalSelectedUtxoAmount: function () { - const total = (this.utxos || []) - .filter(u => u.selected) - .reduce((t, a) => t + (a.amount || 0), 0) - return total - }, - refreshUtxoSelection: function (totalPayedAmount) { - this.utxoSelectAmount = totalPayedAmount - this.applyUtxoSelectionMode() - }, - updateUtxoSelection: function () { - this.utxoSelectAmount = this.payedAmount - this.applyUtxoSelectionMode() - }, - applyUtxoSelectionMode: function () { - const mode = this.utxoSelectionMode - const isSelectAll = mode === 'Select All' - if (isSelectAll) { - this.utxos.forEach(u => (u.selected = true)) - return - } - - const isManual = mode === 'Manual' - if (isManual || !this.utxoSelectAmount) return - - this.utxos.forEach(u => (u.selected = false)) - - const isSmallerFirst = mode === 'Smaller Inputs First' - const isLargerFirst = mode === 'Larger Inputs First' - let selectedUtxos = this.utxos.slice() - if (isSmallerFirst || isLargerFirst) { - const sortFn = isSmallerFirst - ? (a, b) => a.amount - b.amount - : (a, b) => b.amount - a.amount - selectedUtxos.sort(sortFn) - } else { - // default to random order - selectedUtxos = _.shuffle(selectedUtxos) - } - selectedUtxos.reduce((total, utxo) => { - utxo.selected = total < this.utxoSelectAmount - total += utxo.amount - return total - }, 0) - } - }, - - created: async function () {} - }) -} diff --git a/lnbits/extensions/watchonly/static/components/wallet-config/wallet-config.html b/lnbits/extensions/watchonly/static/components/wallet-config/wallet-config.html deleted file mode 100644 index 748d650d..00000000 --- a/lnbits/extensions/watchonly/static/components/wallet-config/wallet-config.html +++ /dev/null @@ -1,80 +0,0 @@ -
- -
-
- - -
-
-
-
{{satBtc(total)}}
-
-
-
- -
-
-
- - - - - - - - - - - - - - - -
- Update - Cancel -
-
-
-
-
diff --git a/lnbits/extensions/watchonly/static/components/wallet-config/wallet-config.js b/lnbits/extensions/watchonly/static/components/wallet-config/wallet-config.js deleted file mode 100644 index 447dc65c..00000000 --- a/lnbits/extensions/watchonly/static/components/wallet-config/wallet-config.js +++ /dev/null @@ -1,67 +0,0 @@ -async function walletConfig(path) { - const t = await loadTemplateAsync(path) - Vue.component('wallet-config', { - name: 'wallet-config', - template: t, - - props: ['total', 'config-data', 'adminkey'], - data: function () { - return { - networOptions: ['Mainnet', 'Testnet'], - internalConfig: {}, - show: false - } - }, - - computed: { - config: { - get() { - return this.internalConfig - }, - set(value) { - value.isLoaded = true - this.internalConfig = JSON.parse(JSON.stringify(value)) - this.$emit( - 'update:config-data', - JSON.parse(JSON.stringify(this.internalConfig)) - ) - } - } - }, - - methods: { - satBtc(val, showUnit = true) { - return satOrBtc(val, showUnit, this.config.sats_denominated) - }, - updateConfig: async function () { - try { - const {data} = await LNbits.api.request( - 'PUT', - '/watchonly/api/v1/config', - this.adminkey, - this.config - ) - this.show = false - this.config = data - } catch (error) { - LNbits.utils.notifyApiError(error) - } - }, - getConfig: async function () { - try { - const {data} = await LNbits.api.request( - 'GET', - '/watchonly/api/v1/config', - this.adminkey - ) - this.config = data - } catch (error) { - LNbits.utils.notifyApiError(error) - } - } - }, - created: async function () { - await this.getConfig() - } - }) -} diff --git a/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.html b/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.html deleted file mode 100644 index b656bdca..00000000 --- a/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.html +++ /dev/null @@ -1,285 +0,0 @@ -
- - -
-
- - - - - New Account - Enter account Xpub or Descriptor - - - - - From Hardware Device - - Get Xpub from a Hardware Device - - - - -
-
-
- - - -
-
- - - - -
-
- - - - - - - - - - -
- - - - Cancel -
-
-
-
- - - - - - - -
diff --git a/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js b/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js deleted file mode 100644 index 27d40ce9..00000000 --- a/lnbits/extensions/watchonly/static/components/wallet-list/wallet-list.js +++ /dev/null @@ -1,315 +0,0 @@ -async function walletList(path) { - const template = await loadTemplateAsync(path) - Vue.component('wallet-list', { - name: 'wallet-list', - template, - - props: [ - 'adminkey', - 'inkey', - 'sats-denominated', - 'addresses', - 'network', - 'serial-signer-ref' - ], - data: function () { - return { - walletAccounts: [], - address: {}, - showQrCodeDialog: false, - qrCodeValue: null, - formDialog: { - show: false, - - addressType: { - label: 'Segwit (P2WPKH)', - id: 'wpkh', - pathMainnet: "m/84'/0'/0'", - pathTestnet: "m/84'/1'/0'" - }, - useSerialPort: false, - data: { - title: '', - masterpub: '' - } - }, - accountPath: '', - filter: '', - showCreating: false, - addressTypeOptions: [ - { - label: 'Legacy (P2PKH)', - id: 'pkh', - pathMainnet: "m/44'/0'/0'", - pathTestnet: "m/44'/1'/0'" - }, - { - label: 'Segwit (P2WPKH)', - id: 'wpkh', - pathMainnet: "m/84'/0'/0'", - pathTestnet: "m/84'/1'/0'" - }, - { - label: 'Wrapped Segwit (P2SH-P2WPKH)', - id: 'sh', - pathMainnet: "m/49'/0'/0'", - pathTestnet: "m/49'/1'/0'" - }, - { - label: 'Taproot (P2TR)', - id: 'tr', - pathMainnet: "m/86'/0'/0'", - pathTestnet: "m/86'/1'/0'" - } - ], - - walletsTable: { - columns: [ - { - name: 'new', - align: 'left', - label: '' - }, - { - name: 'title', - align: 'left', - label: 'Title', - field: 'title' - }, - { - name: 'amount', - align: 'left', - label: 'Amount' - }, - { - name: 'type', - align: 'left', - label: 'Type', - field: 'type' - }, - {name: 'id', align: 'left', label: 'ID', field: 'id'} - ], - pagination: { - rowsPerPage: 10 - }, - filter: '' - } - } - }, - watch: { - immediate: true, - async network(newNet, oldNet) { - if (newNet !== oldNet) { - await this.refreshWalletAccounts() - this.handleAddressTypeChanged(this.addressTypeOptions[1]) - } - } - }, - - methods: { - satBtc(val, showUnit = true) { - return satOrBtc(val, showUnit, this.satsDenominated) - }, - - addWalletAccount: async function () { - this.showCreating = true - const data = _.omit(this.formDialog.data, 'wallet') - data.network = this.network - await this.createWalletAccount(data) - this.showCreating = false - }, - createWalletAccount: async function (data) { - try { - const meta = {accountPath: this.accountPath} - if (this.formDialog.useSerialPort) { - const {xpub, fingerprint} = await this.fetchXpubFromHww() - if (!xpub) return - meta.xpub = xpub - const path = this.accountPath.substring(2) - const outputType = this.formDialog.addressType.id - if (outputType === 'sh') { - data.masterpub = `${outputType}(wpkh([${fingerprint}/${path}]${xpub}/{0,1}/*))` - } else { - data.masterpub = `${outputType}([${fingerprint}/${path}]${xpub}/{0,1}/*)` - } - } - data.meta = JSON.stringify(meta) - const response = await LNbits.api.request( - 'POST', - '/watchonly/api/v1/wallet', - this.adminkey, - data - ) - this.walletAccounts.push(mapWalletAccount(response.data)) - this.formDialog.show = false - - await this.refreshWalletAccounts() - } catch (error) { - LNbits.utils.notifyApiError(error) - } - }, - fetchXpubFromHww: async function () { - const error = findAccountPathIssues(this.accountPath) - if (error) { - this.$q.notify({ - type: 'warning', - message: 'Invalid derivation path.', - caption: error, - timeout: 10000 - }) - return - } - await this.serialSignerRef.hwwXpub(this.accountPath) - return await this.serialSignerRef.isFetchingXpub() - }, - deleteWalletAccount: function (walletAccountId) { - LNbits.utils - .confirmDialog( - 'Are you sure you want to delete this watch only wallet?' - ) - .onOk(async () => { - try { - await LNbits.api.request( - 'DELETE', - '/watchonly/api/v1/wallet/' + walletAccountId, - this.adminkey - ) - this.walletAccounts = _.reject( - this.walletAccounts, - function (obj) { - return obj.id === walletAccountId - } - ) - await this.refreshWalletAccounts() - } catch (error) { - this.$q.notify({ - type: 'warning', - message: - 'Error while deleting wallet account. Please try again.', - timeout: 10000 - }) - } - }) - }, - - getWatchOnlyWallets: async function () { - try { - const {data} = await LNbits.api.request( - 'GET', - `/watchonly/api/v1/wallet?network=${this.network}`, - this.inkey - ) - return data - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Failed to fetch wallets.', - timeout: 10000 - }) - LNbits.utils.notifyApiError(error) - } - return [] - }, - refreshWalletAccounts: async function () { - this.walletAccounts = [] - const wallets = await this.getWatchOnlyWallets() - this.walletAccounts = wallets.map(w => mapWalletAccount(w)) - this.$emit('accounts-update', this.walletAccounts) - }, - getAmmountForWallet: function (walletId) { - const amount = this.addresses - .filter(a => a.wallet === walletId) - .reduce((t, a) => t + a.amount || 0, 0) - return this.satBtc(amount) - }, - closeFormDialog: function () { - this.formDialog.data = { - is_unique: false - } - }, - getAccountDescription: function (accountType) { - return getAccountDescription(accountType) - }, - openGetFreshAddressDialog: async function (walletId) { - const {data} = await LNbits.api.request( - 'GET', - `/watchonly/api/v1/address/${walletId}`, - this.inkey - ) - const addressData = mapAddressesData(data) - - addressData.note = `Shared on ${currentDateTime()}` - const lastActiveAddress = - this.addresses - .filter( - a => - a.wallet === addressData.wallet && !a.isChange && a.hasActivity - ) - .pop() || {} - addressData.gapLimitExceeded = - !addressData.isChange && - addressData.addressIndex > - lastActiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT - - const wallet = this.walletAccounts.find(w => w.id === walletId) || {} - wallet.address_no = addressData.addressIndex - this.$emit('new-receive-address', {addressData, wallet}) - }, - showAddAccountDialog: function () { - this.formDialog.show = true - this.formDialog.useSerialPort = false - }, - getXpubFromDevice: async function () { - try { - if (!this.serialSignerRef.isConnected()) { - this.$q.notify({ - type: 'warning', - message: 'Please connect to a hardware Device first!', - timeout: 10000 - }) - return - } - if (!this.serialSignerRef.isAuthenticated()) { - await this.serialSignerRef.hwwShowPasswordDialog() - const authenticated = await this.serialSignerRef.isAuthenticating() - if (!authenticated) return - } - this.formDialog.show = true - this.formDialog.useSerialPort = true - } catch (error) { - this.$q.notify({ - type: 'warning', - message: 'Cannot fetch Xpub!', - caption: `${error}`, - timeout: 10000 - }) - } - }, - handleAddressTypeChanged: function (value = {}) { - const addressType = - this.addressTypeOptions.find(t => t.id === value.id) || {} - this.accountPath = addressType[`path${this.network}`] - }, - // todo: bad. base.js not present in custom components - copyText: function (text, message, position) { - var notify = this.$q.notify - Quasar.utils.copyToClipboard(text).then(function () { - notify({ - message: message || 'Copied to clipboard!', - position: position || 'bottom' - }) - }) - }, - openQrCodeDialog: function (qrCodeValue) { - this.qrCodeValue = qrCodeValue - this.showQrCodeDialog = true - } - }, - created: async function () { - if (this.inkey) { - await this.refreshWalletAccounts() - this.handleAddressTypeChanged(this.addressTypeOptions[1]) - } - } - }) -} diff --git a/lnbits/extensions/watchonly/static/js/bip39-word-list.js b/lnbits/extensions/watchonly/static/js/bip39-word-list.js deleted file mode 100644 index c0a5eac3..00000000 --- a/lnbits/extensions/watchonly/static/js/bip39-word-list.js +++ /dev/null @@ -1,2050 +0,0 @@ -const bip39WordList = Object.freeze([ - 'abandon', - 'ability', - 'able', - 'about', - 'above', - 'absent', - 'absorb', - 'abstract', - 'absurd', - 'abuse', - 'access', - 'accident', - 'account', - 'accuse', - 'achieve', - 'acid', - 'acoustic', - 'acquire', - 'across', - 'act', - 'action', - 'actor', - 'actress', - 'actual', - 'adapt', - 'add', - 'addict', - 'address', - 'adjust', - 'admit', - 'adult', - 'advance', - 'advice', - 'aerobic', - 'affair', - 'afford', - 'afraid', - 'again', - 'age', - 'agent', - 'agree', - 'ahead', - 'aim', - 'air', - 'airport', - 'aisle', - 'alarm', - 'album', - 'alcohol', - 'alert', - 'alien', - 'all', - 'alley', - 'allow', - 'almost', - 'alone', - 'alpha', - 'already', - 'also', - 'alter', - 'always', - 'amateur', - 'amazing', - 'among', - 'amount', - 'amused', - 'analyst', - 'anchor', - 'ancient', - 'anger', - 'angle', - 'angry', - 'animal', - 'ankle', - 'announce', - 'annual', - 'another', - 'answer', - 'antenna', - 'antique', - 'anxiety', - 'any', - 'apart', - 'apology', - 'appear', - 'apple', - 'approve', - 'april', - 'arch', - 'arctic', - 'area', - 'arena', - 'argue', - 'arm', - 'armed', - 'armor', - 'army', - 'around', - 'arrange', - 'arrest', - 'arrive', - 'arrow', - 'art', - 'artefact', - 'artist', - 'artwork', - 'ask', - 'aspect', - 'assault', - 'asset', - 'assist', - 'assume', - 'asthma', - 'athlete', - 'atom', - 'attack', - 'attend', - 'attitude', - 'attract', - 'auction', - 'audit', - 'august', - 'aunt', - 'author', - 'auto', - 'autumn', - 'average', - 'avocado', - 'avoid', - 'awake', - 'aware', - 'away', - 'awesome', - 'awful', - 'awkward', - 'axis', - 'baby', - 'bachelor', - 'bacon', - 'badge', - 'bag', - 'balance', - 'balcony', - 'ball', - 'bamboo', - 'banana', - 'banner', - 'bar', - 'barely', - 'bargain', - 'barrel', - 'base', - 'basic', - 'basket', - 'battle', - 'beach', - 'bean', - 'beauty', - 'because', - 'become', - 'beef', - 'before', - 'begin', - 'behave', - 'behind', - 'believe', - 'below', - 'belt', - 'bench', - 'benefit', - 'best', - 'betray', - 'better', - 'between', - 'beyond', - 'bicycle', - 'bid', - 'bike', - 'bind', - 'biology', - 'bird', - 'birth', - 'bitter', - 'black', - 'blade', - 'blame', - 'blanket', - 'blast', - 'bleak', - 'bless', - 'blind', - 'blood', - 'blossom', - 'blouse', - 'blue', - 'blur', - 'blush', - 'board', - 'boat', - 'body', - 'boil', - 'bomb', - 'bone', - 'bonus', - 'book', - 'boost', - 'border', - 'boring', - 'borrow', - 'boss', - 'bottom', - 'bounce', - 'box', - 'boy', - 'bracket', - 'brain', - 'brand', - 'brass', - 'brave', - 'bread', - 'breeze', - 'brick', - 'bridge', - 'brief', - 'bright', - 'bring', - 'brisk', - 'broccoli', - 'broken', - 'bronze', - 'broom', - 'brother', - 'brown', - 'brush', - 'bubble', - 'buddy', - 'budget', - 'buffalo', - 'build', - 'bulb', - 'bulk', - 'bullet', - 'bundle', - 'bunker', - 'burden', - 'burger', - 'burst', - 'bus', - 'business', - 'busy', - 'butter', - 'buyer', - 'buzz', - 'cabbage', - 'cabin', - 'cable', - 'cactus', - 'cage', - 'cake', - 'call', - 'calm', - 'camera', - 'camp', - 'can', - 'canal', - 'cancel', - 'candy', - 'cannon', - 'canoe', - 'canvas', - 'canyon', - 'capable', - 'capital', - 'captain', - 'car', - 'carbon', - 'card', - 'cargo', - 'carpet', - 'carry', - 'cart', - 'case', - 'cash', - 'casino', - 'castle', - 'casual', - 'cat', - 'catalog', - 'catch', - 'category', - 'cattle', - 'caught', - 'cause', - 'caution', - 'cave', - 'ceiling', - 'celery', - 'cement', - 'census', - 'century', - 'cereal', - 'certain', - 'chair', - 'chalk', - 'champion', - 'change', - 'chaos', - 'chapter', - 'charge', - 'chase', - 'chat', - 'cheap', - 'check', - 'cheese', - 'chef', - 'cherry', - 'chest', - 'chicken', - 'chief', - 'child', - 'chimney', - 'choice', - 'choose', - 'chronic', - 'chuckle', - 'chunk', - 'churn', - 'cigar', - 'cinnamon', - 'circle', - 'citizen', - 'city', - 'civil', - 'claim', - 'clap', - 'clarify', - 'claw', - 'clay', - 'clean', - 'clerk', - 'clever', - 'click', - 'client', - 'cliff', - 'climb', - 'clinic', - 'clip', - 'clock', - 'clog', - 'close', - 'cloth', - 'cloud', - 'clown', - 'club', - 'clump', - 'cluster', - 'clutch', - 'coach', - 'coast', - 'coconut', - 'code', - 'coffee', - 'coil', - 'coin', - 'collect', - 'color', - 'column', - 'combine', - 'come', - 'comfort', - 'comic', - 'common', - 'company', - 'concert', - 'conduct', - 'confirm', - 'congress', - 'connect', - 'consider', - 'control', - 'convince', - 'cook', - 'cool', - 'copper', - 'copy', - 'coral', - 'core', - 'corn', - 'correct', - 'cost', - 'cotton', - 'couch', - 'country', - 'couple', - 'course', - 'cousin', - 'cover', - 'coyote', - 'crack', - 'cradle', - 'craft', - 'cram', - 'crane', - 'crash', - 'crater', - 'crawl', - 'crazy', - 'cream', - 'credit', - 'creek', - 'crew', - 'cricket', - 'crime', - 'crisp', - 'critic', - 'crop', - 'cross', - 'crouch', - 'crowd', - 'crucial', - 'cruel', - 'cruise', - 'crumble', - 'crunch', - 'crush', - 'cry', - 'crystal', - 'cube', - 'culture', - 'cup', - 'cupboard', - 'curious', - 'current', - 'curtain', - 'curve', - 'cushion', - 'custom', - 'cute', - 'cycle', - 'dad', - 'damage', - 'damp', - 'dance', - 'danger', - 'daring', - 'dash', - 'daughter', - 'dawn', - 'day', - 'deal', - 'debate', - 'debris', - 'decade', - 'december', - 'decide', - 'decline', - 'decorate', - 'decrease', - 'deer', - 'defense', - 'define', - 'defy', - 'degree', - 'delay', - 'deliver', - 'demand', - 'demise', - 'denial', - 'dentist', - 'deny', - 'depart', - 'depend', - 'deposit', - 'depth', - 'deputy', - 'derive', - 'describe', - 'desert', - 'design', - 'desk', - 'despair', - 'destroy', - 'detail', - 'detect', - 'develop', - 'device', - 'devote', - 'diagram', - 'dial', - 'diamond', - 'diary', - 'dice', - 'diesel', - 'diet', - 'differ', - 'digital', - 'dignity', - 'dilemma', - 'dinner', - 'dinosaur', - 'direct', - 'dirt', - 'disagree', - 'discover', - 'disease', - 'dish', - 'dismiss', - 'disorder', - 'display', - 'distance', - 'divert', - 'divide', - 'divorce', - 'dizzy', - 'doctor', - 'document', - 'dog', - 'doll', - 'dolphin', - 'domain', - 'donate', - 'donkey', - 'donor', - 'door', - 'dose', - 'double', - 'dove', - 'draft', - 'dragon', - 'drama', - 'drastic', - 'draw', - 'dream', - 'dress', - 'drift', - 'drill', - 'drink', - 'drip', - 'drive', - 'drop', - 'drum', - 'dry', - 'duck', - 'dumb', - 'dune', - 'during', - 'dust', - 'dutch', - 'duty', - 'dwarf', - 'dynamic', - 'eager', - 'eagle', - 'early', - 'earn', - 'earth', - 'easily', - 'east', - 'easy', - 'echo', - 'ecology', - 'economy', - 'edge', - 'edit', - 'educate', - 'effort', - 'egg', - 'eight', - 'either', - 'elbow', - 'elder', - 'electric', - 'elegant', - 'element', - 'elephant', - 'elevator', - 'elite', - 'else', - 'embark', - 'embody', - 'embrace', - 'emerge', - 'emotion', - 'employ', - 'empower', - 'empty', - 'enable', - 'enact', - 'end', - 'endless', - 'endorse', - 'enemy', - 'energy', - 'enforce', - 'engage', - 'engine', - 'enhance', - 'enjoy', - 'enlist', - 'enough', - 'enrich', - 'enroll', - 'ensure', - 'enter', - 'entire', - 'entry', - 'envelope', - 'episode', - 'equal', - 'equip', - 'era', - 'erase', - 'erode', - 'erosion', - 'error', - 'erupt', - 'escape', - 'essay', - 'essence', - 'estate', - 'eternal', - 'ethics', - 'evidence', - 'evil', - 'evoke', - 'evolve', - 'exact', - 'example', - 'excess', - 'exchange', - 'excite', - 'exclude', - 'excuse', - 'execute', - 'exercise', - 'exhaust', - 'exhibit', - 'exile', - 'exist', - 'exit', - 'exotic', - 'expand', - 'expect', - 'expire', - 'explain', - 'expose', - 'express', - 'extend', - 'extra', - 'eye', - 'eyebrow', - 'fabric', - 'face', - 'faculty', - 'fade', - 'faint', - 'faith', - 'fall', - 'false', - 'fame', - 'family', - 'famous', - 'fan', - 'fancy', - 'fantasy', - 'farm', - 'fashion', - 'fat', - 'fatal', - 'father', - 'fatigue', - 'fault', - 'favorite', - 'feature', - 'february', - 'federal', - 'fee', - 'feed', - 'feel', - 'female', - 'fence', - 'festival', - 'fetch', - 'fever', - 'few', - 'fiber', - 'fiction', - 'field', - 'figure', - 'file', - 'film', - 'filter', - 'final', - 'find', - 'fine', - 'finger', - 'finish', - 'fire', - 'firm', - 'first', - 'fiscal', - 'fish', - 'fit', - 'fitness', - 'fix', - 'flag', - 'flame', - 'flash', - 'flat', - 'flavor', - 'flee', - 'flight', - 'flip', - 'float', - 'flock', - 'floor', - 'flower', - 'fluid', - 'flush', - 'fly', - 'foam', - 'focus', - 'fog', - 'foil', - 'fold', - 'follow', - 'food', - 'foot', - 'force', - 'forest', - 'forget', - 'fork', - 'fortune', - 'forum', - 'forward', - 'fossil', - 'foster', - 'found', - 'fox', - 'fragile', - 'frame', - 'frequent', - 'fresh', - 'friend', - 'fringe', - 'frog', - 'front', - 'frost', - 'frown', - 'frozen', - 'fruit', - 'fuel', - 'fun', - 'funny', - 'furnace', - 'fury', - 'future', - 'gadget', - 'gain', - 'galaxy', - 'gallery', - 'game', - 'gap', - 'garage', - 'garbage', - 'garden', - 'garlic', - 'garment', - 'gas', - 'gasp', - 'gate', - 'gather', - 'gauge', - 'gaze', - 'general', - 'genius', - 'genre', - 'gentle', - 'genuine', - 'gesture', - 'ghost', - 'giant', - 'gift', - 'giggle', - 'ginger', - 'giraffe', - 'girl', - 'give', - 'glad', - 'glance', - 'glare', - 'glass', - 'glide', - 'glimpse', - 'globe', - 'gloom', - 'glory', - 'glove', - 'glow', - 'glue', - 'goat', - 'goddess', - 'gold', - 'good', - 'goose', - 'gorilla', - 'gospel', - 'gossip', - 'govern', - 'gown', - 'grab', - 'grace', - 'grain', - 'grant', - 'grape', - 'grass', - 'gravity', - 'great', - 'green', - 'grid', - 'grief', - 'grit', - 'grocery', - 'group', - 'grow', - 'grunt', - 'guard', - 'guess', - 'guide', - 'guilt', - 'guitar', - 'gun', - 'gym', - 'habit', - 'hair', - 'half', - 'hammer', - 'hamster', - 'hand', - 'happy', - 'harbor', - 'hard', - 'harsh', - 'harvest', - 'hat', - 'have', - 'hawk', - 'hazard', - 'head', - 'health', - 'heart', - 'heavy', - 'hedgehog', - 'height', - 'hello', - 'helmet', - 'help', - 'hen', - 'hero', - 'hidden', - 'high', - 'hill', - 'hint', - 'hip', - 'hire', - 'history', - 'hobby', - 'hockey', - 'hold', - 'hole', - 'holiday', - 'hollow', - 'home', - 'honey', - 'hood', - 'hope', - 'horn', - 'horror', - 'horse', - 'hospital', - 'host', - 'hotel', - 'hour', - 'hover', - 'hub', - 'huge', - 'human', - 'humble', - 'humor', - 'hundred', - 'hungry', - 'hunt', - 'hurdle', - 'hurry', - 'hurt', - 'husband', - 'hybrid', - 'ice', - 'icon', - 'idea', - 'identify', - 'idle', - 'ignore', - 'ill', - 'illegal', - 'illness', - 'image', - 'imitate', - 'immense', - 'immune', - 'impact', - 'impose', - 'improve', - 'impulse', - 'inch', - 'include', - 'income', - 'increase', - 'index', - 'indicate', - 'indoor', - 'industry', - 'infant', - 'inflict', - 'inform', - 'inhale', - 'inherit', - 'initial', - 'inject', - 'injury', - 'inmate', - 'inner', - 'innocent', - 'input', - 'inquiry', - 'insane', - 'insect', - 'inside', - 'inspire', - 'install', - 'intact', - 'interest', - 'into', - 'invest', - 'invite', - 'involve', - 'iron', - 'island', - 'isolate', - 'issue', - 'item', - 'ivory', - 'jacket', - 'jaguar', - 'jar', - 'jazz', - 'jealous', - 'jeans', - 'jelly', - 'jewel', - 'job', - 'join', - 'joke', - 'journey', - 'joy', - 'judge', - 'juice', - 'jump', - 'jungle', - 'junior', - 'junk', - 'just', - 'kangaroo', - 'keen', - 'keep', - 'ketchup', - 'key', - 'kick', - 'kid', - 'kidney', - 'kind', - 'kingdom', - 'kiss', - 'kit', - 'kitchen', - 'kite', - 'kitten', - 'kiwi', - 'knee', - 'knife', - 'knock', - 'know', - 'lab', - 'label', - 'labor', - 'ladder', - 'lady', - 'lake', - 'lamp', - 'language', - 'laptop', - 'large', - 'later', - 'latin', - 'laugh', - 'laundry', - 'lava', - 'law', - 'lawn', - 'lawsuit', - 'layer', - 'lazy', - 'leader', - 'leaf', - 'learn', - 'leave', - 'lecture', - 'left', - 'leg', - 'legal', - 'legend', - 'leisure', - 'lemon', - 'lend', - 'length', - 'lens', - 'leopard', - 'lesson', - 'letter', - 'level', - 'liar', - 'liberty', - 'library', - 'license', - 'life', - 'lift', - 'light', - 'like', - 'limb', - 'limit', - 'link', - 'lion', - 'liquid', - 'list', - 'little', - 'live', - 'lizard', - 'load', - 'loan', - 'lobster', - 'local', - 'lock', - 'logic', - 'lonely', - 'long', - 'loop', - 'lottery', - 'loud', - 'lounge', - 'love', - 'loyal', - 'lucky', - 'luggage', - 'lumber', - 'lunar', - 'lunch', - 'luxury', - 'lyrics', - 'machine', - 'mad', - 'magic', - 'magnet', - 'maid', - 'mail', - 'main', - 'major', - 'make', - 'mammal', - 'man', - 'manage', - 'mandate', - 'mango', - 'mansion', - 'manual', - 'maple', - 'marble', - 'march', - 'margin', - 'marine', - 'market', - 'marriage', - 'mask', - 'mass', - 'master', - 'match', - 'material', - 'math', - 'matrix', - 'matter', - 'maximum', - 'maze', - 'meadow', - 'mean', - 'measure', - 'meat', - 'mechanic', - 'medal', - 'media', - 'melody', - 'melt', - 'member', - 'memory', - 'mention', - 'menu', - 'mercy', - 'merge', - 'merit', - 'merry', - 'mesh', - 'message', - 'metal', - 'method', - 'middle', - 'midnight', - 'milk', - 'million', - 'mimic', - 'mind', - 'minimum', - 'minor', - 'minute', - 'miracle', - 'mirror', - 'misery', - 'miss', - 'mistake', - 'mix', - 'mixed', - 'mixture', - 'mobile', - 'model', - 'modify', - 'mom', - 'moment', - 'monitor', - 'monkey', - 'monster', - 'month', - 'moon', - 'moral', - 'more', - 'morning', - 'mosquito', - 'mother', - 'motion', - 'motor', - 'mountain', - 'mouse', - 'move', - 'movie', - 'much', - 'muffin', - 'mule', - 'multiply', - 'muscle', - 'museum', - 'mushroom', - 'music', - 'must', - 'mutual', - 'myself', - 'mystery', - 'myth', - 'naive', - 'name', - 'napkin', - 'narrow', - 'nasty', - 'nation', - 'nature', - 'near', - 'neck', - 'need', - 'negative', - 'neglect', - 'neither', - 'nephew', - 'nerve', - 'nest', - 'net', - 'network', - 'neutral', - 'never', - 'news', - 'next', - 'nice', - 'night', - 'noble', - 'noise', - 'nominee', - 'noodle', - 'normal', - 'north', - 'nose', - 'notable', - 'note', - 'nothing', - 'notice', - 'novel', - 'now', - 'nuclear', - 'number', - 'nurse', - 'nut', - 'oak', - 'obey', - 'object', - 'oblige', - 'obscure', - 'observe', - 'obtain', - 'obvious', - 'occur', - 'ocean', - 'october', - 'odor', - 'off', - 'offer', - 'office', - 'often', - 'oil', - 'okay', - 'old', - 'olive', - 'olympic', - 'omit', - 'once', - 'one', - 'onion', - 'online', - 'only', - 'open', - 'opera', - 'opinion', - 'oppose', - 'option', - 'orange', - 'orbit', - 'orchard', - 'order', - 'ordinary', - 'organ', - 'orient', - 'original', - 'orphan', - 'ostrich', - 'other', - 'outdoor', - 'outer', - 'output', - 'outside', - 'oval', - 'oven', - 'over', - 'own', - 'owner', - 'oxygen', - 'oyster', - 'ozone', - 'pact', - 'paddle', - 'page', - 'pair', - 'palace', - 'palm', - 'panda', - 'panel', - 'panic', - 'panther', - 'paper', - 'parade', - 'parent', - 'park', - 'parrot', - 'party', - 'pass', - 'patch', - 'path', - 'patient', - 'patrol', - 'pattern', - 'pause', - 'pave', - 'payment', - 'peace', - 'peanut', - 'pear', - 'peasant', - 'pelican', - 'pen', - 'penalty', - 'pencil', - 'people', - 'pepper', - 'perfect', - 'permit', - 'person', - 'pet', - 'phone', - 'photo', - 'phrase', - 'physical', - 'piano', - 'picnic', - 'picture', - 'piece', - 'pig', - 'pigeon', - 'pill', - 'pilot', - 'pink', - 'pioneer', - 'pipe', - 'pistol', - 'pitch', - 'pizza', - 'place', - 'planet', - 'plastic', - 'plate', - 'play', - 'please', - 'pledge', - 'pluck', - 'plug', - 'plunge', - 'poem', - 'poet', - 'point', - 'polar', - 'pole', - 'police', - 'pond', - 'pony', - 'pool', - 'popular', - 'portion', - 'position', - 'possible', - 'post', - 'potato', - 'pottery', - 'poverty', - 'powder', - 'power', - 'practice', - 'praise', - 'predict', - 'prefer', - 'prepare', - 'present', - 'pretty', - 'prevent', - 'price', - 'pride', - 'primary', - 'print', - 'priority', - 'prison', - 'private', - 'prize', - 'problem', - 'process', - 'produce', - 'profit', - 'program', - 'project', - 'promote', - 'proof', - 'property', - 'prosper', - 'protect', - 'proud', - 'provide', - 'public', - 'pudding', - 'pull', - 'pulp', - 'pulse', - 'pumpkin', - 'punch', - 'pupil', - 'puppy', - 'purchase', - 'purity', - 'purpose', - 'purse', - 'push', - 'put', - 'puzzle', - 'pyramid', - 'quality', - 'quantum', - 'quarter', - 'question', - 'quick', - 'quit', - 'quiz', - 'quote', - 'rabbit', - 'raccoon', - 'race', - 'rack', - 'radar', - 'radio', - 'rail', - 'rain', - 'raise', - 'rally', - 'ramp', - 'ranch', - 'random', - 'range', - 'rapid', - 'rare', - 'rate', - 'rather', - 'raven', - 'raw', - 'razor', - 'ready', - 'real', - 'reason', - 'rebel', - 'rebuild', - 'recall', - 'receive', - 'recipe', - 'record', - 'recycle', - 'reduce', - 'reflect', - 'reform', - 'refuse', - 'region', - 'regret', - 'regular', - 'reject', - 'relax', - 'release', - 'relief', - 'rely', - 'remain', - 'remember', - 'remind', - 'remove', - 'render', - 'renew', - 'rent', - 'reopen', - 'repair', - 'repeat', - 'replace', - 'report', - 'require', - 'rescue', - 'resemble', - 'resist', - 'resource', - 'response', - 'result', - 'retire', - 'retreat', - 'return', - 'reunion', - 'reveal', - 'review', - 'reward', - 'rhythm', - 'rib', - 'ribbon', - 'rice', - 'rich', - 'ride', - 'ridge', - 'rifle', - 'right', - 'rigid', - 'ring', - 'riot', - 'ripple', - 'risk', - 'ritual', - 'rival', - 'river', - 'road', - 'roast', - 'robot', - 'robust', - 'rocket', - 'romance', - 'roof', - 'rookie', - 'room', - 'rose', - 'rotate', - 'rough', - 'round', - 'route', - 'royal', - 'rubber', - 'rude', - 'rug', - 'rule', - 'run', - 'runway', - 'rural', - 'sad', - 'saddle', - 'sadness', - 'safe', - 'sail', - 'salad', - 'salmon', - 'salon', - 'salt', - 'salute', - 'same', - 'sample', - 'sand', - 'satisfy', - 'satoshi', - 'sauce', - 'sausage', - 'save', - 'say', - 'scale', - 'scan', - 'scare', - 'scatter', - 'scene', - 'scheme', - 'school', - 'science', - 'scissors', - 'scorpion', - 'scout', - 'scrap', - 'screen', - 'script', - 'scrub', - 'sea', - 'search', - 'season', - 'seat', - 'second', - 'secret', - 'section', - 'security', - 'seed', - 'seek', - 'segment', - 'select', - 'sell', - 'seminar', - 'senior', - 'sense', - 'sentence', - 'series', - 'service', - 'session', - 'settle', - 'setup', - 'seven', - 'shadow', - 'shaft', - 'shallow', - 'share', - 'shed', - 'shell', - 'sheriff', - 'shield', - 'shift', - 'shine', - 'ship', - 'shiver', - 'shock', - 'shoe', - 'shoot', - 'shop', - 'short', - 'shoulder', - 'shove', - 'shrimp', - 'shrug', - 'shuffle', - 'shy', - 'sibling', - 'sick', - 'side', - 'siege', - 'sight', - 'sign', - 'silent', - 'silk', - 'silly', - 'silver', - 'similar', - 'simple', - 'since', - 'sing', - 'siren', - 'sister', - 'situate', - 'six', - 'size', - 'skate', - 'sketch', - 'ski', - 'skill', - 'skin', - 'skirt', - 'skull', - 'slab', - 'slam', - 'sleep', - 'slender', - 'slice', - 'slide', - 'slight', - 'slim', - 'slogan', - 'slot', - 'slow', - 'slush', - 'small', - 'smart', - 'smile', - 'smoke', - 'smooth', - 'snack', - 'snake', - 'snap', - 'sniff', - 'snow', - 'soap', - 'soccer', - 'social', - 'sock', - 'soda', - 'soft', - 'solar', - 'soldier', - 'solid', - 'solution', - 'solve', - 'someone', - 'song', - 'soon', - 'sorry', - 'sort', - 'soul', - 'sound', - 'soup', - 'source', - 'south', - 'space', - 'spare', - 'spatial', - 'spawn', - 'speak', - 'special', - 'speed', - 'spell', - 'spend', - 'sphere', - 'spice', - 'spider', - 'spike', - 'spin', - 'spirit', - 'split', - 'spoil', - 'sponsor', - 'spoon', - 'sport', - 'spot', - 'spray', - 'spread', - 'spring', - 'spy', - 'square', - 'squeeze', - 'squirrel', - 'stable', - 'stadium', - 'staff', - 'stage', - 'stairs', - 'stamp', - 'stand', - 'start', - 'state', - 'stay', - 'steak', - 'steel', - 'stem', - 'step', - 'stereo', - 'stick', - 'still', - 'sting', - 'stock', - 'stomach', - 'stone', - 'stool', - 'story', - 'stove', - 'strategy', - 'street', - 'strike', - 'strong', - 'struggle', - 'student', - 'stuff', - 'stumble', - 'style', - 'subject', - 'submit', - 'subway', - 'success', - 'such', - 'sudden', - 'suffer', - 'sugar', - 'suggest', - 'suit', - 'summer', - 'sun', - 'sunny', - 'sunset', - 'super', - 'supply', - 'supreme', - 'sure', - 'surface', - 'surge', - 'surprise', - 'surround', - 'survey', - 'suspect', - 'sustain', - 'swallow', - 'swamp', - 'swap', - 'swarm', - 'swear', - 'sweet', - 'swift', - 'swim', - 'swing', - 'switch', - 'sword', - 'symbol', - 'symptom', - 'syrup', - 'system', - 'table', - 'tackle', - 'tag', - 'tail', - 'talent', - 'talk', - 'tank', - 'tape', - 'target', - 'task', - 'taste', - 'tattoo', - 'taxi', - 'teach', - 'team', - 'tell', - 'ten', - 'tenant', - 'tennis', - 'tent', - 'term', - 'test', - 'text', - 'thank', - 'that', - 'theme', - 'then', - 'theory', - 'there', - 'they', - 'thing', - 'this', - 'thought', - 'three', - 'thrive', - 'throw', - 'thumb', - 'thunder', - 'ticket', - 'tide', - 'tiger', - 'tilt', - 'timber', - 'time', - 'tiny', - 'tip', - 'tired', - 'tissue', - 'title', - 'toast', - 'tobacco', - 'today', - 'toddler', - 'toe', - 'together', - 'toilet', - 'token', - 'tomato', - 'tomorrow', - 'tone', - 'tongue', - 'tonight', - 'tool', - 'tooth', - 'top', - 'topic', - 'topple', - 'torch', - 'tornado', - 'tortoise', - 'toss', - 'total', - 'tourist', - 'toward', - 'tower', - 'town', - 'toy', - 'track', - 'trade', - 'traffic', - 'tragic', - 'train', - 'transfer', - 'trap', - 'trash', - 'travel', - 'tray', - 'treat', - 'tree', - 'trend', - 'trial', - 'tribe', - 'trick', - 'trigger', - 'trim', - 'trip', - 'trophy', - 'trouble', - 'truck', - 'true', - 'truly', - 'trumpet', - 'trust', - 'truth', - 'try', - 'tube', - 'tuition', - 'tumble', - 'tuna', - 'tunnel', - 'turkey', - 'turn', - 'turtle', - 'twelve', - 'twenty', - 'twice', - 'twin', - 'twist', - 'two', - 'type', - 'typical', - 'ugly', - 'umbrella', - 'unable', - 'unaware', - 'uncle', - 'uncover', - 'under', - 'undo', - 'unfair', - 'unfold', - 'unhappy', - 'uniform', - 'unique', - 'unit', - 'universe', - 'unknown', - 'unlock', - 'until', - 'unusual', - 'unveil', - 'update', - 'upgrade', - 'uphold', - 'upon', - 'upper', - 'upset', - 'urban', - 'urge', - 'usage', - 'use', - 'used', - 'useful', - 'useless', - 'usual', - 'utility', - 'vacant', - 'vacuum', - 'vague', - 'valid', - 'valley', - 'valve', - 'van', - 'vanish', - 'vapor', - 'various', - 'vast', - 'vault', - 'vehicle', - 'velvet', - 'vendor', - 'venture', - 'venue', - 'verb', - 'verify', - 'version', - 'very', - 'vessel', - 'veteran', - 'viable', - 'vibrant', - 'vicious', - 'victory', - 'video', - 'view', - 'village', - 'vintage', - 'violin', - 'virtual', - 'virus', - 'visa', - 'visit', - 'visual', - 'vital', - 'vivid', - 'vocal', - 'voice', - 'void', - 'volcano', - 'volume', - 'vote', - 'voyage', - 'wage', - 'wagon', - 'wait', - 'walk', - 'wall', - 'walnut', - 'want', - 'warfare', - 'warm', - 'warrior', - 'wash', - 'wasp', - 'waste', - 'water', - 'wave', - 'way', - 'wealth', - 'weapon', - 'wear', - 'weasel', - 'weather', - 'web', - 'wedding', - 'weekend', - 'weird', - 'welcome', - 'west', - 'wet', - 'whale', - 'what', - 'wheat', - 'wheel', - 'when', - 'where', - 'whip', - 'whisper', - 'wide', - 'width', - 'wife', - 'wild', - 'will', - 'win', - 'window', - 'wine', - 'wing', - 'wink', - 'winner', - 'winter', - 'wire', - 'wisdom', - 'wise', - 'wish', - 'witness', - 'wolf', - 'woman', - 'wonder', - 'wood', - 'wool', - 'word', - 'work', - 'world', - 'worry', - 'worth', - 'wrap', - 'wreck', - 'wrestle', - 'wrist', - 'write', - 'wrong', - 'yard', - 'year', - 'yellow', - 'you', - 'young', - 'youth', - 'zebra', - 'zero', - 'zone', - 'zoo' -]) diff --git a/lnbits/extensions/watchonly/static/js/crypto/aes.js b/lnbits/extensions/watchonly/static/js/crypto/aes.js deleted file mode 100644 index 92a17ca2..00000000 --- a/lnbits/extensions/watchonly/static/js/crypto/aes.js +++ /dev/null @@ -1,802 +0,0 @@ -/*! MIT License. Copyright 2015-2018 Richard Moore . See LICENSE.txt. */ -(function(root) { - "use strict"; - - function checkInt(value) { - return (parseInt(value) === value); - } - - function checkInts(arrayish) { - if (!checkInt(arrayish.length)) { return false; } - - for (var i = 0; i < arrayish.length; i++) { - if (!checkInt(arrayish[i]) || arrayish[i] < 0 || arrayish[i] > 255) { - return false; - } - } - - return true; - } - - function coerceArray(arg, copy) { - - // ArrayBuffer view - if (arg.buffer && arg.name === 'Uint8Array') { - - if (copy) { - if (arg.slice) { - arg = arg.slice(); - } else { - arg = Array.prototype.slice.call(arg); - } - } - - return arg; - } - - // It's an array; check it is a valid representation of a byte - if (Array.isArray(arg)) { - if (!checkInts(arg)) { - throw new Error('Array contains invalid value: ' + arg); - } - - return new Uint8Array(arg); - } - - // Something else, but behaves like an array (maybe a Buffer? Arguments?) - if (checkInt(arg.length) && checkInts(arg)) { - return new Uint8Array(arg); - } - throw new Error('unsupported array-like object'); - } - - function createArray(length) { - return new Uint8Array(length); - } - - function copyArray(sourceArray, targetArray, targetStart, sourceStart, sourceEnd) { - if (sourceStart != null || sourceEnd != null) { - if (sourceArray.slice) { - sourceArray = sourceArray.slice(sourceStart, sourceEnd); - } else { - sourceArray = Array.prototype.slice.call(sourceArray, sourceStart, sourceEnd); - } - } - targetArray.set(sourceArray, targetStart); - } - - - - var convertUtf8 = (function() { - function toBytes(text) { - var result = [], i = 0; - text = encodeURI(text); - while (i < text.length) { - var c = text.charCodeAt(i++); - - // if it is a % sign, encode the following 2 bytes as a hex value - if (c === 37) { - result.push(parseInt(text.substr(i, 2), 16)) - i += 2; - - // otherwise, just the actual byte - } else { - result.push(c) - } - } - - return coerceArray(result); - } - - function fromBytes(bytes) { - var result = [], i = 0; - - while (i < bytes.length) { - var c = bytes[i]; - - if (c < 128) { - result.push(String.fromCharCode(c)); - i++; - } else if (c > 191 && c < 224) { - result.push(String.fromCharCode(((c & 0x1f) << 6) | (bytes[i + 1] & 0x3f))); - i += 2; - } else { - result.push(String.fromCharCode(((c & 0x0f) << 12) | ((bytes[i + 1] & 0x3f) << 6) | (bytes[i + 2] & 0x3f))); - i += 3; - } - } - - return result.join(''); - } - - return { - toBytes: toBytes, - fromBytes: fromBytes, - } - })(); - - var convertHex = (function() { - function toBytes(text) { - var result = []; - for (var i = 0; i < text.length; i += 2) { - result.push(parseInt(text.substr(i, 2), 16)); - } - - return result; - } - - // http://ixti.net/development/javascript/2011/11/11/base64-encodedecode-of-utf8-in-browser-with-js.html - var Hex = '0123456789abcdef'; - - function fromBytes(bytes) { - var result = []; - for (var i = 0; i < bytes.length; i++) { - var v = bytes[i]; - result.push(Hex[(v & 0xf0) >> 4] + Hex[v & 0x0f]); - } - return result.join(''); - } - - return { - toBytes: toBytes, - fromBytes: fromBytes, - } - })(); - - - // Number of rounds by keysize - var numberOfRounds = {16: 10, 24: 12, 32: 14} - - // Round constant words - var rcon = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91]; - - // S-box and Inverse S-box (S is for Substitution) - var S = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16]; - var Si =[0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d]; - - // Transformations for encryption - var T1 = [0xc66363a5, 0xf87c7c84, 0xee777799, 0xf67b7b8d, 0xfff2f20d, 0xd66b6bbd, 0xde6f6fb1, 0x91c5c554, 0x60303050, 0x02010103, 0xce6767a9, 0x562b2b7d, 0xe7fefe19, 0xb5d7d762, 0x4dababe6, 0xec76769a, 0x8fcaca45, 0x1f82829d, 0x89c9c940, 0xfa7d7d87, 0xeffafa15, 0xb25959eb, 0x8e4747c9, 0xfbf0f00b, 0x41adadec, 0xb3d4d467, 0x5fa2a2fd, 0x45afafea, 0x239c9cbf, 0x53a4a4f7, 0xe4727296, 0x9bc0c05b, 0x75b7b7c2, 0xe1fdfd1c, 0x3d9393ae, 0x4c26266a, 0x6c36365a, 0x7e3f3f41, 0xf5f7f702, 0x83cccc4f, 0x6834345c, 0x51a5a5f4, 0xd1e5e534, 0xf9f1f108, 0xe2717193, 0xabd8d873, 0x62313153, 0x2a15153f, 0x0804040c, 0x95c7c752, 0x46232365, 0x9dc3c35e, 0x30181828, 0x379696a1, 0x0a05050f, 0x2f9a9ab5, 0x0e070709, 0x24121236, 0x1b80809b, 0xdfe2e23d, 0xcdebeb26, 0x4e272769, 0x7fb2b2cd, 0xea75759f, 0x1209091b, 0x1d83839e, 0x582c2c74, 0x341a1a2e, 0x361b1b2d, 0xdc6e6eb2, 0xb45a5aee, 0x5ba0a0fb, 0xa45252f6, 0x763b3b4d, 0xb7d6d661, 0x7db3b3ce, 0x5229297b, 0xdde3e33e, 0x5e2f2f71, 0x13848497, 0xa65353f5, 0xb9d1d168, 0x00000000, 0xc1eded2c, 0x40202060, 0xe3fcfc1f, 0x79b1b1c8, 0xb65b5bed, 0xd46a6abe, 0x8dcbcb46, 0x67bebed9, 0x7239394b, 0x944a4ade, 0x984c4cd4, 0xb05858e8, 0x85cfcf4a, 0xbbd0d06b, 0xc5efef2a, 0x4faaaae5, 0xedfbfb16, 0x864343c5, 0x9a4d4dd7, 0x66333355, 0x11858594, 0x8a4545cf, 0xe9f9f910, 0x04020206, 0xfe7f7f81, 0xa05050f0, 0x783c3c44, 0x259f9fba, 0x4ba8a8e3, 0xa25151f3, 0x5da3a3fe, 0x804040c0, 0x058f8f8a, 0x3f9292ad, 0x219d9dbc, 0x70383848, 0xf1f5f504, 0x63bcbcdf, 0x77b6b6c1, 0xafdada75, 0x42212163, 0x20101030, 0xe5ffff1a, 0xfdf3f30e, 0xbfd2d26d, 0x81cdcd4c, 0x180c0c14, 0x26131335, 0xc3ecec2f, 0xbe5f5fe1, 0x359797a2, 0x884444cc, 0x2e171739, 0x93c4c457, 0x55a7a7f2, 0xfc7e7e82, 0x7a3d3d47, 0xc86464ac, 0xba5d5de7, 0x3219192b, 0xe6737395, 0xc06060a0, 0x19818198, 0x9e4f4fd1, 0xa3dcdc7f, 0x44222266, 0x542a2a7e, 0x3b9090ab, 0x0b888883, 0x8c4646ca, 0xc7eeee29, 0x6bb8b8d3, 0x2814143c, 0xa7dede79, 0xbc5e5ee2, 0x160b0b1d, 0xaddbdb76, 0xdbe0e03b, 0x64323256, 0x743a3a4e, 0x140a0a1e, 0x924949db, 0x0c06060a, 0x4824246c, 0xb85c5ce4, 0x9fc2c25d, 0xbdd3d36e, 0x43acacef, 0xc46262a6, 0x399191a8, 0x319595a4, 0xd3e4e437, 0xf279798b, 0xd5e7e732, 0x8bc8c843, 0x6e373759, 0xda6d6db7, 0x018d8d8c, 0xb1d5d564, 0x9c4e4ed2, 0x49a9a9e0, 0xd86c6cb4, 0xac5656fa, 0xf3f4f407, 0xcfeaea25, 0xca6565af, 0xf47a7a8e, 0x47aeaee9, 0x10080818, 0x6fbabad5, 0xf0787888, 0x4a25256f, 0x5c2e2e72, 0x381c1c24, 0x57a6a6f1, 0x73b4b4c7, 0x97c6c651, 0xcbe8e823, 0xa1dddd7c, 0xe874749c, 0x3e1f1f21, 0x964b4bdd, 0x61bdbddc, 0x0d8b8b86, 0x0f8a8a85, 0xe0707090, 0x7c3e3e42, 0x71b5b5c4, 0xcc6666aa, 0x904848d8, 0x06030305, 0xf7f6f601, 0x1c0e0e12, 0xc26161a3, 0x6a35355f, 0xae5757f9, 0x69b9b9d0, 0x17868691, 0x99c1c158, 0x3a1d1d27, 0x279e9eb9, 0xd9e1e138, 0xebf8f813, 0x2b9898b3, 0x22111133, 0xd26969bb, 0xa9d9d970, 0x078e8e89, 0x339494a7, 0x2d9b9bb6, 0x3c1e1e22, 0x15878792, 0xc9e9e920, 0x87cece49, 0xaa5555ff, 0x50282878, 0xa5dfdf7a, 0x038c8c8f, 0x59a1a1f8, 0x09898980, 0x1a0d0d17, 0x65bfbfda, 0xd7e6e631, 0x844242c6, 0xd06868b8, 0x824141c3, 0x299999b0, 0x5a2d2d77, 0x1e0f0f11, 0x7bb0b0cb, 0xa85454fc, 0x6dbbbbd6, 0x2c16163a]; - var T2 = [0xa5c66363, 0x84f87c7c, 0x99ee7777, 0x8df67b7b, 0x0dfff2f2, 0xbdd66b6b, 0xb1de6f6f, 0x5491c5c5, 0x50603030, 0x03020101, 0xa9ce6767, 0x7d562b2b, 0x19e7fefe, 0x62b5d7d7, 0xe64dabab, 0x9aec7676, 0x458fcaca, 0x9d1f8282, 0x4089c9c9, 0x87fa7d7d, 0x15effafa, 0xebb25959, 0xc98e4747, 0x0bfbf0f0, 0xec41adad, 0x67b3d4d4, 0xfd5fa2a2, 0xea45afaf, 0xbf239c9c, 0xf753a4a4, 0x96e47272, 0x5b9bc0c0, 0xc275b7b7, 0x1ce1fdfd, 0xae3d9393, 0x6a4c2626, 0x5a6c3636, 0x417e3f3f, 0x02f5f7f7, 0x4f83cccc, 0x5c683434, 0xf451a5a5, 0x34d1e5e5, 0x08f9f1f1, 0x93e27171, 0x73abd8d8, 0x53623131, 0x3f2a1515, 0x0c080404, 0x5295c7c7, 0x65462323, 0x5e9dc3c3, 0x28301818, 0xa1379696, 0x0f0a0505, 0xb52f9a9a, 0x090e0707, 0x36241212, 0x9b1b8080, 0x3ddfe2e2, 0x26cdebeb, 0x694e2727, 0xcd7fb2b2, 0x9fea7575, 0x1b120909, 0x9e1d8383, 0x74582c2c, 0x2e341a1a, 0x2d361b1b, 0xb2dc6e6e, 0xeeb45a5a, 0xfb5ba0a0, 0xf6a45252, 0x4d763b3b, 0x61b7d6d6, 0xce7db3b3, 0x7b522929, 0x3edde3e3, 0x715e2f2f, 0x97138484, 0xf5a65353, 0x68b9d1d1, 0x00000000, 0x2cc1eded, 0x60402020, 0x1fe3fcfc, 0xc879b1b1, 0xedb65b5b, 0xbed46a6a, 0x468dcbcb, 0xd967bebe, 0x4b723939, 0xde944a4a, 0xd4984c4c, 0xe8b05858, 0x4a85cfcf, 0x6bbbd0d0, 0x2ac5efef, 0xe54faaaa, 0x16edfbfb, 0xc5864343, 0xd79a4d4d, 0x55663333, 0x94118585, 0xcf8a4545, 0x10e9f9f9, 0x06040202, 0x81fe7f7f, 0xf0a05050, 0x44783c3c, 0xba259f9f, 0xe34ba8a8, 0xf3a25151, 0xfe5da3a3, 0xc0804040, 0x8a058f8f, 0xad3f9292, 0xbc219d9d, 0x48703838, 0x04f1f5f5, 0xdf63bcbc, 0xc177b6b6, 0x75afdada, 0x63422121, 0x30201010, 0x1ae5ffff, 0x0efdf3f3, 0x6dbfd2d2, 0x4c81cdcd, 0x14180c0c, 0x35261313, 0x2fc3ecec, 0xe1be5f5f, 0xa2359797, 0xcc884444, 0x392e1717, 0x5793c4c4, 0xf255a7a7, 0x82fc7e7e, 0x477a3d3d, 0xacc86464, 0xe7ba5d5d, 0x2b321919, 0x95e67373, 0xa0c06060, 0x98198181, 0xd19e4f4f, 0x7fa3dcdc, 0x66442222, 0x7e542a2a, 0xab3b9090, 0x830b8888, 0xca8c4646, 0x29c7eeee, 0xd36bb8b8, 0x3c281414, 0x79a7dede, 0xe2bc5e5e, 0x1d160b0b, 0x76addbdb, 0x3bdbe0e0, 0x56643232, 0x4e743a3a, 0x1e140a0a, 0xdb924949, 0x0a0c0606, 0x6c482424, 0xe4b85c5c, 0x5d9fc2c2, 0x6ebdd3d3, 0xef43acac, 0xa6c46262, 0xa8399191, 0xa4319595, 0x37d3e4e4, 0x8bf27979, 0x32d5e7e7, 0x438bc8c8, 0x596e3737, 0xb7da6d6d, 0x8c018d8d, 0x64b1d5d5, 0xd29c4e4e, 0xe049a9a9, 0xb4d86c6c, 0xfaac5656, 0x07f3f4f4, 0x25cfeaea, 0xafca6565, 0x8ef47a7a, 0xe947aeae, 0x18100808, 0xd56fbaba, 0x88f07878, 0x6f4a2525, 0x725c2e2e, 0x24381c1c, 0xf157a6a6, 0xc773b4b4, 0x5197c6c6, 0x23cbe8e8, 0x7ca1dddd, 0x9ce87474, 0x213e1f1f, 0xdd964b4b, 0xdc61bdbd, 0x860d8b8b, 0x850f8a8a, 0x90e07070, 0x427c3e3e, 0xc471b5b5, 0xaacc6666, 0xd8904848, 0x05060303, 0x01f7f6f6, 0x121c0e0e, 0xa3c26161, 0x5f6a3535, 0xf9ae5757, 0xd069b9b9, 0x91178686, 0x5899c1c1, 0x273a1d1d, 0xb9279e9e, 0x38d9e1e1, 0x13ebf8f8, 0xb32b9898, 0x33221111, 0xbbd26969, 0x70a9d9d9, 0x89078e8e, 0xa7339494, 0xb62d9b9b, 0x223c1e1e, 0x92158787, 0x20c9e9e9, 0x4987cece, 0xffaa5555, 0x78502828, 0x7aa5dfdf, 0x8f038c8c, 0xf859a1a1, 0x80098989, 0x171a0d0d, 0xda65bfbf, 0x31d7e6e6, 0xc6844242, 0xb8d06868, 0xc3824141, 0xb0299999, 0x775a2d2d, 0x111e0f0f, 0xcb7bb0b0, 0xfca85454, 0xd66dbbbb, 0x3a2c1616]; - var T3 = [0x63a5c663, 0x7c84f87c, 0x7799ee77, 0x7b8df67b, 0xf20dfff2, 0x6bbdd66b, 0x6fb1de6f, 0xc55491c5, 0x30506030, 0x01030201, 0x67a9ce67, 0x2b7d562b, 0xfe19e7fe, 0xd762b5d7, 0xabe64dab, 0x769aec76, 0xca458fca, 0x829d1f82, 0xc94089c9, 0x7d87fa7d, 0xfa15effa, 0x59ebb259, 0x47c98e47, 0xf00bfbf0, 0xadec41ad, 0xd467b3d4, 0xa2fd5fa2, 0xafea45af, 0x9cbf239c, 0xa4f753a4, 0x7296e472, 0xc05b9bc0, 0xb7c275b7, 0xfd1ce1fd, 0x93ae3d93, 0x266a4c26, 0x365a6c36, 0x3f417e3f, 0xf702f5f7, 0xcc4f83cc, 0x345c6834, 0xa5f451a5, 0xe534d1e5, 0xf108f9f1, 0x7193e271, 0xd873abd8, 0x31536231, 0x153f2a15, 0x040c0804, 0xc75295c7, 0x23654623, 0xc35e9dc3, 0x18283018, 0x96a13796, 0x050f0a05, 0x9ab52f9a, 0x07090e07, 0x12362412, 0x809b1b80, 0xe23ddfe2, 0xeb26cdeb, 0x27694e27, 0xb2cd7fb2, 0x759fea75, 0x091b1209, 0x839e1d83, 0x2c74582c, 0x1a2e341a, 0x1b2d361b, 0x6eb2dc6e, 0x5aeeb45a, 0xa0fb5ba0, 0x52f6a452, 0x3b4d763b, 0xd661b7d6, 0xb3ce7db3, 0x297b5229, 0xe33edde3, 0x2f715e2f, 0x84971384, 0x53f5a653, 0xd168b9d1, 0x00000000, 0xed2cc1ed, 0x20604020, 0xfc1fe3fc, 0xb1c879b1, 0x5bedb65b, 0x6abed46a, 0xcb468dcb, 0xbed967be, 0x394b7239, 0x4ade944a, 0x4cd4984c, 0x58e8b058, 0xcf4a85cf, 0xd06bbbd0, 0xef2ac5ef, 0xaae54faa, 0xfb16edfb, 0x43c58643, 0x4dd79a4d, 0x33556633, 0x85941185, 0x45cf8a45, 0xf910e9f9, 0x02060402, 0x7f81fe7f, 0x50f0a050, 0x3c44783c, 0x9fba259f, 0xa8e34ba8, 0x51f3a251, 0xa3fe5da3, 0x40c08040, 0x8f8a058f, 0x92ad3f92, 0x9dbc219d, 0x38487038, 0xf504f1f5, 0xbcdf63bc, 0xb6c177b6, 0xda75afda, 0x21634221, 0x10302010, 0xff1ae5ff, 0xf30efdf3, 0xd26dbfd2, 0xcd4c81cd, 0x0c14180c, 0x13352613, 0xec2fc3ec, 0x5fe1be5f, 0x97a23597, 0x44cc8844, 0x17392e17, 0xc45793c4, 0xa7f255a7, 0x7e82fc7e, 0x3d477a3d, 0x64acc864, 0x5de7ba5d, 0x192b3219, 0x7395e673, 0x60a0c060, 0x81981981, 0x4fd19e4f, 0xdc7fa3dc, 0x22664422, 0x2a7e542a, 0x90ab3b90, 0x88830b88, 0x46ca8c46, 0xee29c7ee, 0xb8d36bb8, 0x143c2814, 0xde79a7de, 0x5ee2bc5e, 0x0b1d160b, 0xdb76addb, 0xe03bdbe0, 0x32566432, 0x3a4e743a, 0x0a1e140a, 0x49db9249, 0x060a0c06, 0x246c4824, 0x5ce4b85c, 0xc25d9fc2, 0xd36ebdd3, 0xacef43ac, 0x62a6c462, 0x91a83991, 0x95a43195, 0xe437d3e4, 0x798bf279, 0xe732d5e7, 0xc8438bc8, 0x37596e37, 0x6db7da6d, 0x8d8c018d, 0xd564b1d5, 0x4ed29c4e, 0xa9e049a9, 0x6cb4d86c, 0x56faac56, 0xf407f3f4, 0xea25cfea, 0x65afca65, 0x7a8ef47a, 0xaee947ae, 0x08181008, 0xbad56fba, 0x7888f078, 0x256f4a25, 0x2e725c2e, 0x1c24381c, 0xa6f157a6, 0xb4c773b4, 0xc65197c6, 0xe823cbe8, 0xdd7ca1dd, 0x749ce874, 0x1f213e1f, 0x4bdd964b, 0xbddc61bd, 0x8b860d8b, 0x8a850f8a, 0x7090e070, 0x3e427c3e, 0xb5c471b5, 0x66aacc66, 0x48d89048, 0x03050603, 0xf601f7f6, 0x0e121c0e, 0x61a3c261, 0x355f6a35, 0x57f9ae57, 0xb9d069b9, 0x86911786, 0xc15899c1, 0x1d273a1d, 0x9eb9279e, 0xe138d9e1, 0xf813ebf8, 0x98b32b98, 0x11332211, 0x69bbd269, 0xd970a9d9, 0x8e89078e, 0x94a73394, 0x9bb62d9b, 0x1e223c1e, 0x87921587, 0xe920c9e9, 0xce4987ce, 0x55ffaa55, 0x28785028, 0xdf7aa5df, 0x8c8f038c, 0xa1f859a1, 0x89800989, 0x0d171a0d, 0xbfda65bf, 0xe631d7e6, 0x42c68442, 0x68b8d068, 0x41c38241, 0x99b02999, 0x2d775a2d, 0x0f111e0f, 0xb0cb7bb0, 0x54fca854, 0xbbd66dbb, 0x163a2c16]; - var T4 = [0x6363a5c6, 0x7c7c84f8, 0x777799ee, 0x7b7b8df6, 0xf2f20dff, 0x6b6bbdd6, 0x6f6fb1de, 0xc5c55491, 0x30305060, 0x01010302, 0x6767a9ce, 0x2b2b7d56, 0xfefe19e7, 0xd7d762b5, 0xababe64d, 0x76769aec, 0xcaca458f, 0x82829d1f, 0xc9c94089, 0x7d7d87fa, 0xfafa15ef, 0x5959ebb2, 0x4747c98e, 0xf0f00bfb, 0xadadec41, 0xd4d467b3, 0xa2a2fd5f, 0xafafea45, 0x9c9cbf23, 0xa4a4f753, 0x727296e4, 0xc0c05b9b, 0xb7b7c275, 0xfdfd1ce1, 0x9393ae3d, 0x26266a4c, 0x36365a6c, 0x3f3f417e, 0xf7f702f5, 0xcccc4f83, 0x34345c68, 0xa5a5f451, 0xe5e534d1, 0xf1f108f9, 0x717193e2, 0xd8d873ab, 0x31315362, 0x15153f2a, 0x04040c08, 0xc7c75295, 0x23236546, 0xc3c35e9d, 0x18182830, 0x9696a137, 0x05050f0a, 0x9a9ab52f, 0x0707090e, 0x12123624, 0x80809b1b, 0xe2e23ddf, 0xebeb26cd, 0x2727694e, 0xb2b2cd7f, 0x75759fea, 0x09091b12, 0x83839e1d, 0x2c2c7458, 0x1a1a2e34, 0x1b1b2d36, 0x6e6eb2dc, 0x5a5aeeb4, 0xa0a0fb5b, 0x5252f6a4, 0x3b3b4d76, 0xd6d661b7, 0xb3b3ce7d, 0x29297b52, 0xe3e33edd, 0x2f2f715e, 0x84849713, 0x5353f5a6, 0xd1d168b9, 0x00000000, 0xeded2cc1, 0x20206040, 0xfcfc1fe3, 0xb1b1c879, 0x5b5bedb6, 0x6a6abed4, 0xcbcb468d, 0xbebed967, 0x39394b72, 0x4a4ade94, 0x4c4cd498, 0x5858e8b0, 0xcfcf4a85, 0xd0d06bbb, 0xefef2ac5, 0xaaaae54f, 0xfbfb16ed, 0x4343c586, 0x4d4dd79a, 0x33335566, 0x85859411, 0x4545cf8a, 0xf9f910e9, 0x02020604, 0x7f7f81fe, 0x5050f0a0, 0x3c3c4478, 0x9f9fba25, 0xa8a8e34b, 0x5151f3a2, 0xa3a3fe5d, 0x4040c080, 0x8f8f8a05, 0x9292ad3f, 0x9d9dbc21, 0x38384870, 0xf5f504f1, 0xbcbcdf63, 0xb6b6c177, 0xdada75af, 0x21216342, 0x10103020, 0xffff1ae5, 0xf3f30efd, 0xd2d26dbf, 0xcdcd4c81, 0x0c0c1418, 0x13133526, 0xecec2fc3, 0x5f5fe1be, 0x9797a235, 0x4444cc88, 0x1717392e, 0xc4c45793, 0xa7a7f255, 0x7e7e82fc, 0x3d3d477a, 0x6464acc8, 0x5d5de7ba, 0x19192b32, 0x737395e6, 0x6060a0c0, 0x81819819, 0x4f4fd19e, 0xdcdc7fa3, 0x22226644, 0x2a2a7e54, 0x9090ab3b, 0x8888830b, 0x4646ca8c, 0xeeee29c7, 0xb8b8d36b, 0x14143c28, 0xdede79a7, 0x5e5ee2bc, 0x0b0b1d16, 0xdbdb76ad, 0xe0e03bdb, 0x32325664, 0x3a3a4e74, 0x0a0a1e14, 0x4949db92, 0x06060a0c, 0x24246c48, 0x5c5ce4b8, 0xc2c25d9f, 0xd3d36ebd, 0xacacef43, 0x6262a6c4, 0x9191a839, 0x9595a431, 0xe4e437d3, 0x79798bf2, 0xe7e732d5, 0xc8c8438b, 0x3737596e, 0x6d6db7da, 0x8d8d8c01, 0xd5d564b1, 0x4e4ed29c, 0xa9a9e049, 0x6c6cb4d8, 0x5656faac, 0xf4f407f3, 0xeaea25cf, 0x6565afca, 0x7a7a8ef4, 0xaeaee947, 0x08081810, 0xbabad56f, 0x787888f0, 0x25256f4a, 0x2e2e725c, 0x1c1c2438, 0xa6a6f157, 0xb4b4c773, 0xc6c65197, 0xe8e823cb, 0xdddd7ca1, 0x74749ce8, 0x1f1f213e, 0x4b4bdd96, 0xbdbddc61, 0x8b8b860d, 0x8a8a850f, 0x707090e0, 0x3e3e427c, 0xb5b5c471, 0x6666aacc, 0x4848d890, 0x03030506, 0xf6f601f7, 0x0e0e121c, 0x6161a3c2, 0x35355f6a, 0x5757f9ae, 0xb9b9d069, 0x86869117, 0xc1c15899, 0x1d1d273a, 0x9e9eb927, 0xe1e138d9, 0xf8f813eb, 0x9898b32b, 0x11113322, 0x6969bbd2, 0xd9d970a9, 0x8e8e8907, 0x9494a733, 0x9b9bb62d, 0x1e1e223c, 0x87879215, 0xe9e920c9, 0xcece4987, 0x5555ffaa, 0x28287850, 0xdfdf7aa5, 0x8c8c8f03, 0xa1a1f859, 0x89898009, 0x0d0d171a, 0xbfbfda65, 0xe6e631d7, 0x4242c684, 0x6868b8d0, 0x4141c382, 0x9999b029, 0x2d2d775a, 0x0f0f111e, 0xb0b0cb7b, 0x5454fca8, 0xbbbbd66d, 0x16163a2c]; - - // Transformations for decryption - var T5 = [0x51f4a750, 0x7e416553, 0x1a17a4c3, 0x3a275e96, 0x3bab6bcb, 0x1f9d45f1, 0xacfa58ab, 0x4be30393, 0x2030fa55, 0xad766df6, 0x88cc7691, 0xf5024c25, 0x4fe5d7fc, 0xc52acbd7, 0x26354480, 0xb562a38f, 0xdeb15a49, 0x25ba1b67, 0x45ea0e98, 0x5dfec0e1, 0xc32f7502, 0x814cf012, 0x8d4697a3, 0x6bd3f9c6, 0x038f5fe7, 0x15929c95, 0xbf6d7aeb, 0x955259da, 0xd4be832d, 0x587421d3, 0x49e06929, 0x8ec9c844, 0x75c2896a, 0xf48e7978, 0x99583e6b, 0x27b971dd, 0xbee14fb6, 0xf088ad17, 0xc920ac66, 0x7dce3ab4, 0x63df4a18, 0xe51a3182, 0x97513360, 0x62537f45, 0xb16477e0, 0xbb6bae84, 0xfe81a01c, 0xf9082b94, 0x70486858, 0x8f45fd19, 0x94de6c87, 0x527bf8b7, 0xab73d323, 0x724b02e2, 0xe31f8f57, 0x6655ab2a, 0xb2eb2807, 0x2fb5c203, 0x86c57b9a, 0xd33708a5, 0x302887f2, 0x23bfa5b2, 0x02036aba, 0xed16825c, 0x8acf1c2b, 0xa779b492, 0xf307f2f0, 0x4e69e2a1, 0x65daf4cd, 0x0605bed5, 0xd134621f, 0xc4a6fe8a, 0x342e539d, 0xa2f355a0, 0x058ae132, 0xa4f6eb75, 0x0b83ec39, 0x4060efaa, 0x5e719f06, 0xbd6e1051, 0x3e218af9, 0x96dd063d, 0xdd3e05ae, 0x4de6bd46, 0x91548db5, 0x71c45d05, 0x0406d46f, 0x605015ff, 0x1998fb24, 0xd6bde997, 0x894043cc, 0x67d99e77, 0xb0e842bd, 0x07898b88, 0xe7195b38, 0x79c8eedb, 0xa17c0a47, 0x7c420fe9, 0xf8841ec9, 0x00000000, 0x09808683, 0x322bed48, 0x1e1170ac, 0x6c5a724e, 0xfd0efffb, 0x0f853856, 0x3daed51e, 0x362d3927, 0x0a0fd964, 0x685ca621, 0x9b5b54d1, 0x24362e3a, 0x0c0a67b1, 0x9357e70f, 0xb4ee96d2, 0x1b9b919e, 0x80c0c54f, 0x61dc20a2, 0x5a774b69, 0x1c121a16, 0xe293ba0a, 0xc0a02ae5, 0x3c22e043, 0x121b171d, 0x0e090d0b, 0xf28bc7ad, 0x2db6a8b9, 0x141ea9c8, 0x57f11985, 0xaf75074c, 0xee99ddbb, 0xa37f60fd, 0xf701269f, 0x5c72f5bc, 0x44663bc5, 0x5bfb7e34, 0x8b432976, 0xcb23c6dc, 0xb6edfc68, 0xb8e4f163, 0xd731dcca, 0x42638510, 0x13972240, 0x84c61120, 0x854a247d, 0xd2bb3df8, 0xaef93211, 0xc729a16d, 0x1d9e2f4b, 0xdcb230f3, 0x0d8652ec, 0x77c1e3d0, 0x2bb3166c, 0xa970b999, 0x119448fa, 0x47e96422, 0xa8fc8cc4, 0xa0f03f1a, 0x567d2cd8, 0x223390ef, 0x87494ec7, 0xd938d1c1, 0x8ccaa2fe, 0x98d40b36, 0xa6f581cf, 0xa57ade28, 0xdab78e26, 0x3fadbfa4, 0x2c3a9de4, 0x5078920d, 0x6a5fcc9b, 0x547e4662, 0xf68d13c2, 0x90d8b8e8, 0x2e39f75e, 0x82c3aff5, 0x9f5d80be, 0x69d0937c, 0x6fd52da9, 0xcf2512b3, 0xc8ac993b, 0x10187da7, 0xe89c636e, 0xdb3bbb7b, 0xcd267809, 0x6e5918f4, 0xec9ab701, 0x834f9aa8, 0xe6956e65, 0xaaffe67e, 0x21bccf08, 0xef15e8e6, 0xbae79bd9, 0x4a6f36ce, 0xea9f09d4, 0x29b07cd6, 0x31a4b2af, 0x2a3f2331, 0xc6a59430, 0x35a266c0, 0x744ebc37, 0xfc82caa6, 0xe090d0b0, 0x33a7d815, 0xf104984a, 0x41ecdaf7, 0x7fcd500e, 0x1791f62f, 0x764dd68d, 0x43efb04d, 0xccaa4d54, 0xe49604df, 0x9ed1b5e3, 0x4c6a881b, 0xc12c1fb8, 0x4665517f, 0x9d5eea04, 0x018c355d, 0xfa877473, 0xfb0b412e, 0xb3671d5a, 0x92dbd252, 0xe9105633, 0x6dd64713, 0x9ad7618c, 0x37a10c7a, 0x59f8148e, 0xeb133c89, 0xcea927ee, 0xb761c935, 0xe11ce5ed, 0x7a47b13c, 0x9cd2df59, 0x55f2733f, 0x1814ce79, 0x73c737bf, 0x53f7cdea, 0x5ffdaa5b, 0xdf3d6f14, 0x7844db86, 0xcaaff381, 0xb968c43e, 0x3824342c, 0xc2a3405f, 0x161dc372, 0xbce2250c, 0x283c498b, 0xff0d9541, 0x39a80171, 0x080cb3de, 0xd8b4e49c, 0x6456c190, 0x7bcb8461, 0xd532b670, 0x486c5c74, 0xd0b85742]; - var T6 = [0x5051f4a7, 0x537e4165, 0xc31a17a4, 0x963a275e, 0xcb3bab6b, 0xf11f9d45, 0xabacfa58, 0x934be303, 0x552030fa, 0xf6ad766d, 0x9188cc76, 0x25f5024c, 0xfc4fe5d7, 0xd7c52acb, 0x80263544, 0x8fb562a3, 0x49deb15a, 0x6725ba1b, 0x9845ea0e, 0xe15dfec0, 0x02c32f75, 0x12814cf0, 0xa38d4697, 0xc66bd3f9, 0xe7038f5f, 0x9515929c, 0xebbf6d7a, 0xda955259, 0x2dd4be83, 0xd3587421, 0x2949e069, 0x448ec9c8, 0x6a75c289, 0x78f48e79, 0x6b99583e, 0xdd27b971, 0xb6bee14f, 0x17f088ad, 0x66c920ac, 0xb47dce3a, 0x1863df4a, 0x82e51a31, 0x60975133, 0x4562537f, 0xe0b16477, 0x84bb6bae, 0x1cfe81a0, 0x94f9082b, 0x58704868, 0x198f45fd, 0x8794de6c, 0xb7527bf8, 0x23ab73d3, 0xe2724b02, 0x57e31f8f, 0x2a6655ab, 0x07b2eb28, 0x032fb5c2, 0x9a86c57b, 0xa5d33708, 0xf2302887, 0xb223bfa5, 0xba02036a, 0x5ced1682, 0x2b8acf1c, 0x92a779b4, 0xf0f307f2, 0xa14e69e2, 0xcd65daf4, 0xd50605be, 0x1fd13462, 0x8ac4a6fe, 0x9d342e53, 0xa0a2f355, 0x32058ae1, 0x75a4f6eb, 0x390b83ec, 0xaa4060ef, 0x065e719f, 0x51bd6e10, 0xf93e218a, 0x3d96dd06, 0xaedd3e05, 0x464de6bd, 0xb591548d, 0x0571c45d, 0x6f0406d4, 0xff605015, 0x241998fb, 0x97d6bde9, 0xcc894043, 0x7767d99e, 0xbdb0e842, 0x8807898b, 0x38e7195b, 0xdb79c8ee, 0x47a17c0a, 0xe97c420f, 0xc9f8841e, 0x00000000, 0x83098086, 0x48322bed, 0xac1e1170, 0x4e6c5a72, 0xfbfd0eff, 0x560f8538, 0x1e3daed5, 0x27362d39, 0x640a0fd9, 0x21685ca6, 0xd19b5b54, 0x3a24362e, 0xb10c0a67, 0x0f9357e7, 0xd2b4ee96, 0x9e1b9b91, 0x4f80c0c5, 0xa261dc20, 0x695a774b, 0x161c121a, 0x0ae293ba, 0xe5c0a02a, 0x433c22e0, 0x1d121b17, 0x0b0e090d, 0xadf28bc7, 0xb92db6a8, 0xc8141ea9, 0x8557f119, 0x4caf7507, 0xbbee99dd, 0xfda37f60, 0x9ff70126, 0xbc5c72f5, 0xc544663b, 0x345bfb7e, 0x768b4329, 0xdccb23c6, 0x68b6edfc, 0x63b8e4f1, 0xcad731dc, 0x10426385, 0x40139722, 0x2084c611, 0x7d854a24, 0xf8d2bb3d, 0x11aef932, 0x6dc729a1, 0x4b1d9e2f, 0xf3dcb230, 0xec0d8652, 0xd077c1e3, 0x6c2bb316, 0x99a970b9, 0xfa119448, 0x2247e964, 0xc4a8fc8c, 0x1aa0f03f, 0xd8567d2c, 0xef223390, 0xc787494e, 0xc1d938d1, 0xfe8ccaa2, 0x3698d40b, 0xcfa6f581, 0x28a57ade, 0x26dab78e, 0xa43fadbf, 0xe42c3a9d, 0x0d507892, 0x9b6a5fcc, 0x62547e46, 0xc2f68d13, 0xe890d8b8, 0x5e2e39f7, 0xf582c3af, 0xbe9f5d80, 0x7c69d093, 0xa96fd52d, 0xb3cf2512, 0x3bc8ac99, 0xa710187d, 0x6ee89c63, 0x7bdb3bbb, 0x09cd2678, 0xf46e5918, 0x01ec9ab7, 0xa8834f9a, 0x65e6956e, 0x7eaaffe6, 0x0821bccf, 0xe6ef15e8, 0xd9bae79b, 0xce4a6f36, 0xd4ea9f09, 0xd629b07c, 0xaf31a4b2, 0x312a3f23, 0x30c6a594, 0xc035a266, 0x37744ebc, 0xa6fc82ca, 0xb0e090d0, 0x1533a7d8, 0x4af10498, 0xf741ecda, 0x0e7fcd50, 0x2f1791f6, 0x8d764dd6, 0x4d43efb0, 0x54ccaa4d, 0xdfe49604, 0xe39ed1b5, 0x1b4c6a88, 0xb8c12c1f, 0x7f466551, 0x049d5eea, 0x5d018c35, 0x73fa8774, 0x2efb0b41, 0x5ab3671d, 0x5292dbd2, 0x33e91056, 0x136dd647, 0x8c9ad761, 0x7a37a10c, 0x8e59f814, 0x89eb133c, 0xeecea927, 0x35b761c9, 0xede11ce5, 0x3c7a47b1, 0x599cd2df, 0x3f55f273, 0x791814ce, 0xbf73c737, 0xea53f7cd, 0x5b5ffdaa, 0x14df3d6f, 0x867844db, 0x81caaff3, 0x3eb968c4, 0x2c382434, 0x5fc2a340, 0x72161dc3, 0x0cbce225, 0x8b283c49, 0x41ff0d95, 0x7139a801, 0xde080cb3, 0x9cd8b4e4, 0x906456c1, 0x617bcb84, 0x70d532b6, 0x74486c5c, 0x42d0b857]; - var T7 = [0xa75051f4, 0x65537e41, 0xa4c31a17, 0x5e963a27, 0x6bcb3bab, 0x45f11f9d, 0x58abacfa, 0x03934be3, 0xfa552030, 0x6df6ad76, 0x769188cc, 0x4c25f502, 0xd7fc4fe5, 0xcbd7c52a, 0x44802635, 0xa38fb562, 0x5a49deb1, 0x1b6725ba, 0x0e9845ea, 0xc0e15dfe, 0x7502c32f, 0xf012814c, 0x97a38d46, 0xf9c66bd3, 0x5fe7038f, 0x9c951592, 0x7aebbf6d, 0x59da9552, 0x832dd4be, 0x21d35874, 0x692949e0, 0xc8448ec9, 0x896a75c2, 0x7978f48e, 0x3e6b9958, 0x71dd27b9, 0x4fb6bee1, 0xad17f088, 0xac66c920, 0x3ab47dce, 0x4a1863df, 0x3182e51a, 0x33609751, 0x7f456253, 0x77e0b164, 0xae84bb6b, 0xa01cfe81, 0x2b94f908, 0x68587048, 0xfd198f45, 0x6c8794de, 0xf8b7527b, 0xd323ab73, 0x02e2724b, 0x8f57e31f, 0xab2a6655, 0x2807b2eb, 0xc2032fb5, 0x7b9a86c5, 0x08a5d337, 0x87f23028, 0xa5b223bf, 0x6aba0203, 0x825ced16, 0x1c2b8acf, 0xb492a779, 0xf2f0f307, 0xe2a14e69, 0xf4cd65da, 0xbed50605, 0x621fd134, 0xfe8ac4a6, 0x539d342e, 0x55a0a2f3, 0xe132058a, 0xeb75a4f6, 0xec390b83, 0xefaa4060, 0x9f065e71, 0x1051bd6e, 0x8af93e21, 0x063d96dd, 0x05aedd3e, 0xbd464de6, 0x8db59154, 0x5d0571c4, 0xd46f0406, 0x15ff6050, 0xfb241998, 0xe997d6bd, 0x43cc8940, 0x9e7767d9, 0x42bdb0e8, 0x8b880789, 0x5b38e719, 0xeedb79c8, 0x0a47a17c, 0x0fe97c42, 0x1ec9f884, 0x00000000, 0x86830980, 0xed48322b, 0x70ac1e11, 0x724e6c5a, 0xfffbfd0e, 0x38560f85, 0xd51e3dae, 0x3927362d, 0xd9640a0f, 0xa621685c, 0x54d19b5b, 0x2e3a2436, 0x67b10c0a, 0xe70f9357, 0x96d2b4ee, 0x919e1b9b, 0xc54f80c0, 0x20a261dc, 0x4b695a77, 0x1a161c12, 0xba0ae293, 0x2ae5c0a0, 0xe0433c22, 0x171d121b, 0x0d0b0e09, 0xc7adf28b, 0xa8b92db6, 0xa9c8141e, 0x198557f1, 0x074caf75, 0xddbbee99, 0x60fda37f, 0x269ff701, 0xf5bc5c72, 0x3bc54466, 0x7e345bfb, 0x29768b43, 0xc6dccb23, 0xfc68b6ed, 0xf163b8e4, 0xdccad731, 0x85104263, 0x22401397, 0x112084c6, 0x247d854a, 0x3df8d2bb, 0x3211aef9, 0xa16dc729, 0x2f4b1d9e, 0x30f3dcb2, 0x52ec0d86, 0xe3d077c1, 0x166c2bb3, 0xb999a970, 0x48fa1194, 0x642247e9, 0x8cc4a8fc, 0x3f1aa0f0, 0x2cd8567d, 0x90ef2233, 0x4ec78749, 0xd1c1d938, 0xa2fe8cca, 0x0b3698d4, 0x81cfa6f5, 0xde28a57a, 0x8e26dab7, 0xbfa43fad, 0x9de42c3a, 0x920d5078, 0xcc9b6a5f, 0x4662547e, 0x13c2f68d, 0xb8e890d8, 0xf75e2e39, 0xaff582c3, 0x80be9f5d, 0x937c69d0, 0x2da96fd5, 0x12b3cf25, 0x993bc8ac, 0x7da71018, 0x636ee89c, 0xbb7bdb3b, 0x7809cd26, 0x18f46e59, 0xb701ec9a, 0x9aa8834f, 0x6e65e695, 0xe67eaaff, 0xcf0821bc, 0xe8e6ef15, 0x9bd9bae7, 0x36ce4a6f, 0x09d4ea9f, 0x7cd629b0, 0xb2af31a4, 0x23312a3f, 0x9430c6a5, 0x66c035a2, 0xbc37744e, 0xcaa6fc82, 0xd0b0e090, 0xd81533a7, 0x984af104, 0xdaf741ec, 0x500e7fcd, 0xf62f1791, 0xd68d764d, 0xb04d43ef, 0x4d54ccaa, 0x04dfe496, 0xb5e39ed1, 0x881b4c6a, 0x1fb8c12c, 0x517f4665, 0xea049d5e, 0x355d018c, 0x7473fa87, 0x412efb0b, 0x1d5ab367, 0xd25292db, 0x5633e910, 0x47136dd6, 0x618c9ad7, 0x0c7a37a1, 0x148e59f8, 0x3c89eb13, 0x27eecea9, 0xc935b761, 0xe5ede11c, 0xb13c7a47, 0xdf599cd2, 0x733f55f2, 0xce791814, 0x37bf73c7, 0xcdea53f7, 0xaa5b5ffd, 0x6f14df3d, 0xdb867844, 0xf381caaf, 0xc43eb968, 0x342c3824, 0x405fc2a3, 0xc372161d, 0x250cbce2, 0x498b283c, 0x9541ff0d, 0x017139a8, 0xb3de080c, 0xe49cd8b4, 0xc1906456, 0x84617bcb, 0xb670d532, 0x5c74486c, 0x5742d0b8]; - var T8 = [0xf4a75051, 0x4165537e, 0x17a4c31a, 0x275e963a, 0xab6bcb3b, 0x9d45f11f, 0xfa58abac, 0xe303934b, 0x30fa5520, 0x766df6ad, 0xcc769188, 0x024c25f5, 0xe5d7fc4f, 0x2acbd7c5, 0x35448026, 0x62a38fb5, 0xb15a49de, 0xba1b6725, 0xea0e9845, 0xfec0e15d, 0x2f7502c3, 0x4cf01281, 0x4697a38d, 0xd3f9c66b, 0x8f5fe703, 0x929c9515, 0x6d7aebbf, 0x5259da95, 0xbe832dd4, 0x7421d358, 0xe0692949, 0xc9c8448e, 0xc2896a75, 0x8e7978f4, 0x583e6b99, 0xb971dd27, 0xe14fb6be, 0x88ad17f0, 0x20ac66c9, 0xce3ab47d, 0xdf4a1863, 0x1a3182e5, 0x51336097, 0x537f4562, 0x6477e0b1, 0x6bae84bb, 0x81a01cfe, 0x082b94f9, 0x48685870, 0x45fd198f, 0xde6c8794, 0x7bf8b752, 0x73d323ab, 0x4b02e272, 0x1f8f57e3, 0x55ab2a66, 0xeb2807b2, 0xb5c2032f, 0xc57b9a86, 0x3708a5d3, 0x2887f230, 0xbfa5b223, 0x036aba02, 0x16825ced, 0xcf1c2b8a, 0x79b492a7, 0x07f2f0f3, 0x69e2a14e, 0xdaf4cd65, 0x05bed506, 0x34621fd1, 0xa6fe8ac4, 0x2e539d34, 0xf355a0a2, 0x8ae13205, 0xf6eb75a4, 0x83ec390b, 0x60efaa40, 0x719f065e, 0x6e1051bd, 0x218af93e, 0xdd063d96, 0x3e05aedd, 0xe6bd464d, 0x548db591, 0xc45d0571, 0x06d46f04, 0x5015ff60, 0x98fb2419, 0xbde997d6, 0x4043cc89, 0xd99e7767, 0xe842bdb0, 0x898b8807, 0x195b38e7, 0xc8eedb79, 0x7c0a47a1, 0x420fe97c, 0x841ec9f8, 0x00000000, 0x80868309, 0x2bed4832, 0x1170ac1e, 0x5a724e6c, 0x0efffbfd, 0x8538560f, 0xaed51e3d, 0x2d392736, 0x0fd9640a, 0x5ca62168, 0x5b54d19b, 0x362e3a24, 0x0a67b10c, 0x57e70f93, 0xee96d2b4, 0x9b919e1b, 0xc0c54f80, 0xdc20a261, 0x774b695a, 0x121a161c, 0x93ba0ae2, 0xa02ae5c0, 0x22e0433c, 0x1b171d12, 0x090d0b0e, 0x8bc7adf2, 0xb6a8b92d, 0x1ea9c814, 0xf1198557, 0x75074caf, 0x99ddbbee, 0x7f60fda3, 0x01269ff7, 0x72f5bc5c, 0x663bc544, 0xfb7e345b, 0x4329768b, 0x23c6dccb, 0xedfc68b6, 0xe4f163b8, 0x31dccad7, 0x63851042, 0x97224013, 0xc6112084, 0x4a247d85, 0xbb3df8d2, 0xf93211ae, 0x29a16dc7, 0x9e2f4b1d, 0xb230f3dc, 0x8652ec0d, 0xc1e3d077, 0xb3166c2b, 0x70b999a9, 0x9448fa11, 0xe9642247, 0xfc8cc4a8, 0xf03f1aa0, 0x7d2cd856, 0x3390ef22, 0x494ec787, 0x38d1c1d9, 0xcaa2fe8c, 0xd40b3698, 0xf581cfa6, 0x7ade28a5, 0xb78e26da, 0xadbfa43f, 0x3a9de42c, 0x78920d50, 0x5fcc9b6a, 0x7e466254, 0x8d13c2f6, 0xd8b8e890, 0x39f75e2e, 0xc3aff582, 0x5d80be9f, 0xd0937c69, 0xd52da96f, 0x2512b3cf, 0xac993bc8, 0x187da710, 0x9c636ee8, 0x3bbb7bdb, 0x267809cd, 0x5918f46e, 0x9ab701ec, 0x4f9aa883, 0x956e65e6, 0xffe67eaa, 0xbccf0821, 0x15e8e6ef, 0xe79bd9ba, 0x6f36ce4a, 0x9f09d4ea, 0xb07cd629, 0xa4b2af31, 0x3f23312a, 0xa59430c6, 0xa266c035, 0x4ebc3774, 0x82caa6fc, 0x90d0b0e0, 0xa7d81533, 0x04984af1, 0xecdaf741, 0xcd500e7f, 0x91f62f17, 0x4dd68d76, 0xefb04d43, 0xaa4d54cc, 0x9604dfe4, 0xd1b5e39e, 0x6a881b4c, 0x2c1fb8c1, 0x65517f46, 0x5eea049d, 0x8c355d01, 0x877473fa, 0x0b412efb, 0x671d5ab3, 0xdbd25292, 0x105633e9, 0xd647136d, 0xd7618c9a, 0xa10c7a37, 0xf8148e59, 0x133c89eb, 0xa927eece, 0x61c935b7, 0x1ce5ede1, 0x47b13c7a, 0xd2df599c, 0xf2733f55, 0x14ce7918, 0xc737bf73, 0xf7cdea53, 0xfdaa5b5f, 0x3d6f14df, 0x44db8678, 0xaff381ca, 0x68c43eb9, 0x24342c38, 0xa3405fc2, 0x1dc37216, 0xe2250cbc, 0x3c498b28, 0x0d9541ff, 0xa8017139, 0x0cb3de08, 0xb4e49cd8, 0x56c19064, 0xcb84617b, 0x32b670d5, 0x6c5c7448, 0xb85742d0]; - - // Transformations for decryption key expansion - var U1 = [0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3]; - var U2 = [0x00000000, 0x0b0e090d, 0x161c121a, 0x1d121b17, 0x2c382434, 0x27362d39, 0x3a24362e, 0x312a3f23, 0x58704868, 0x537e4165, 0x4e6c5a72, 0x4562537f, 0x74486c5c, 0x7f466551, 0x62547e46, 0x695a774b, 0xb0e090d0, 0xbbee99dd, 0xa6fc82ca, 0xadf28bc7, 0x9cd8b4e4, 0x97d6bde9, 0x8ac4a6fe, 0x81caaff3, 0xe890d8b8, 0xe39ed1b5, 0xfe8ccaa2, 0xf582c3af, 0xc4a8fc8c, 0xcfa6f581, 0xd2b4ee96, 0xd9bae79b, 0x7bdb3bbb, 0x70d532b6, 0x6dc729a1, 0x66c920ac, 0x57e31f8f, 0x5ced1682, 0x41ff0d95, 0x4af10498, 0x23ab73d3, 0x28a57ade, 0x35b761c9, 0x3eb968c4, 0x0f9357e7, 0x049d5eea, 0x198f45fd, 0x12814cf0, 0xcb3bab6b, 0xc035a266, 0xdd27b971, 0xd629b07c, 0xe7038f5f, 0xec0d8652, 0xf11f9d45, 0xfa119448, 0x934be303, 0x9845ea0e, 0x8557f119, 0x8e59f814, 0xbf73c737, 0xb47dce3a, 0xa96fd52d, 0xa261dc20, 0xf6ad766d, 0xfda37f60, 0xe0b16477, 0xebbf6d7a, 0xda955259, 0xd19b5b54, 0xcc894043, 0xc787494e, 0xaedd3e05, 0xa5d33708, 0xb8c12c1f, 0xb3cf2512, 0x82e51a31, 0x89eb133c, 0x94f9082b, 0x9ff70126, 0x464de6bd, 0x4d43efb0, 0x5051f4a7, 0x5b5ffdaa, 0x6a75c289, 0x617bcb84, 0x7c69d093, 0x7767d99e, 0x1e3daed5, 0x1533a7d8, 0x0821bccf, 0x032fb5c2, 0x32058ae1, 0x390b83ec, 0x241998fb, 0x2f1791f6, 0x8d764dd6, 0x867844db, 0x9b6a5fcc, 0x906456c1, 0xa14e69e2, 0xaa4060ef, 0xb7527bf8, 0xbc5c72f5, 0xd50605be, 0xde080cb3, 0xc31a17a4, 0xc8141ea9, 0xf93e218a, 0xf2302887, 0xef223390, 0xe42c3a9d, 0x3d96dd06, 0x3698d40b, 0x2b8acf1c, 0x2084c611, 0x11aef932, 0x1aa0f03f, 0x07b2eb28, 0x0cbce225, 0x65e6956e, 0x6ee89c63, 0x73fa8774, 0x78f48e79, 0x49deb15a, 0x42d0b857, 0x5fc2a340, 0x54ccaa4d, 0xf741ecda, 0xfc4fe5d7, 0xe15dfec0, 0xea53f7cd, 0xdb79c8ee, 0xd077c1e3, 0xcd65daf4, 0xc66bd3f9, 0xaf31a4b2, 0xa43fadbf, 0xb92db6a8, 0xb223bfa5, 0x83098086, 0x8807898b, 0x9515929c, 0x9e1b9b91, 0x47a17c0a, 0x4caf7507, 0x51bd6e10, 0x5ab3671d, 0x6b99583e, 0x60975133, 0x7d854a24, 0x768b4329, 0x1fd13462, 0x14df3d6f, 0x09cd2678, 0x02c32f75, 0x33e91056, 0x38e7195b, 0x25f5024c, 0x2efb0b41, 0x8c9ad761, 0x8794de6c, 0x9a86c57b, 0x9188cc76, 0xa0a2f355, 0xabacfa58, 0xb6bee14f, 0xbdb0e842, 0xd4ea9f09, 0xdfe49604, 0xc2f68d13, 0xc9f8841e, 0xf8d2bb3d, 0xf3dcb230, 0xeecea927, 0xe5c0a02a, 0x3c7a47b1, 0x37744ebc, 0x2a6655ab, 0x21685ca6, 0x10426385, 0x1b4c6a88, 0x065e719f, 0x0d507892, 0x640a0fd9, 0x6f0406d4, 0x72161dc3, 0x791814ce, 0x48322bed, 0x433c22e0, 0x5e2e39f7, 0x552030fa, 0x01ec9ab7, 0x0ae293ba, 0x17f088ad, 0x1cfe81a0, 0x2dd4be83, 0x26dab78e, 0x3bc8ac99, 0x30c6a594, 0x599cd2df, 0x5292dbd2, 0x4f80c0c5, 0x448ec9c8, 0x75a4f6eb, 0x7eaaffe6, 0x63b8e4f1, 0x68b6edfc, 0xb10c0a67, 0xba02036a, 0xa710187d, 0xac1e1170, 0x9d342e53, 0x963a275e, 0x8b283c49, 0x80263544, 0xe97c420f, 0xe2724b02, 0xff605015, 0xf46e5918, 0xc544663b, 0xce4a6f36, 0xd3587421, 0xd8567d2c, 0x7a37a10c, 0x7139a801, 0x6c2bb316, 0x6725ba1b, 0x560f8538, 0x5d018c35, 0x40139722, 0x4b1d9e2f, 0x2247e964, 0x2949e069, 0x345bfb7e, 0x3f55f273, 0x0e7fcd50, 0x0571c45d, 0x1863df4a, 0x136dd647, 0xcad731dc, 0xc1d938d1, 0xdccb23c6, 0xd7c52acb, 0xe6ef15e8, 0xede11ce5, 0xf0f307f2, 0xfbfd0eff, 0x92a779b4, 0x99a970b9, 0x84bb6bae, 0x8fb562a3, 0xbe9f5d80, 0xb591548d, 0xa8834f9a, 0xa38d4697]; - var U3 = [0x00000000, 0x0d0b0e09, 0x1a161c12, 0x171d121b, 0x342c3824, 0x3927362d, 0x2e3a2436, 0x23312a3f, 0x68587048, 0x65537e41, 0x724e6c5a, 0x7f456253, 0x5c74486c, 0x517f4665, 0x4662547e, 0x4b695a77, 0xd0b0e090, 0xddbbee99, 0xcaa6fc82, 0xc7adf28b, 0xe49cd8b4, 0xe997d6bd, 0xfe8ac4a6, 0xf381caaf, 0xb8e890d8, 0xb5e39ed1, 0xa2fe8cca, 0xaff582c3, 0x8cc4a8fc, 0x81cfa6f5, 0x96d2b4ee, 0x9bd9bae7, 0xbb7bdb3b, 0xb670d532, 0xa16dc729, 0xac66c920, 0x8f57e31f, 0x825ced16, 0x9541ff0d, 0x984af104, 0xd323ab73, 0xde28a57a, 0xc935b761, 0xc43eb968, 0xe70f9357, 0xea049d5e, 0xfd198f45, 0xf012814c, 0x6bcb3bab, 0x66c035a2, 0x71dd27b9, 0x7cd629b0, 0x5fe7038f, 0x52ec0d86, 0x45f11f9d, 0x48fa1194, 0x03934be3, 0x0e9845ea, 0x198557f1, 0x148e59f8, 0x37bf73c7, 0x3ab47dce, 0x2da96fd5, 0x20a261dc, 0x6df6ad76, 0x60fda37f, 0x77e0b164, 0x7aebbf6d, 0x59da9552, 0x54d19b5b, 0x43cc8940, 0x4ec78749, 0x05aedd3e, 0x08a5d337, 0x1fb8c12c, 0x12b3cf25, 0x3182e51a, 0x3c89eb13, 0x2b94f908, 0x269ff701, 0xbd464de6, 0xb04d43ef, 0xa75051f4, 0xaa5b5ffd, 0x896a75c2, 0x84617bcb, 0x937c69d0, 0x9e7767d9, 0xd51e3dae, 0xd81533a7, 0xcf0821bc, 0xc2032fb5, 0xe132058a, 0xec390b83, 0xfb241998, 0xf62f1791, 0xd68d764d, 0xdb867844, 0xcc9b6a5f, 0xc1906456, 0xe2a14e69, 0xefaa4060, 0xf8b7527b, 0xf5bc5c72, 0xbed50605, 0xb3de080c, 0xa4c31a17, 0xa9c8141e, 0x8af93e21, 0x87f23028, 0x90ef2233, 0x9de42c3a, 0x063d96dd, 0x0b3698d4, 0x1c2b8acf, 0x112084c6, 0x3211aef9, 0x3f1aa0f0, 0x2807b2eb, 0x250cbce2, 0x6e65e695, 0x636ee89c, 0x7473fa87, 0x7978f48e, 0x5a49deb1, 0x5742d0b8, 0x405fc2a3, 0x4d54ccaa, 0xdaf741ec, 0xd7fc4fe5, 0xc0e15dfe, 0xcdea53f7, 0xeedb79c8, 0xe3d077c1, 0xf4cd65da, 0xf9c66bd3, 0xb2af31a4, 0xbfa43fad, 0xa8b92db6, 0xa5b223bf, 0x86830980, 0x8b880789, 0x9c951592, 0x919e1b9b, 0x0a47a17c, 0x074caf75, 0x1051bd6e, 0x1d5ab367, 0x3e6b9958, 0x33609751, 0x247d854a, 0x29768b43, 0x621fd134, 0x6f14df3d, 0x7809cd26, 0x7502c32f, 0x5633e910, 0x5b38e719, 0x4c25f502, 0x412efb0b, 0x618c9ad7, 0x6c8794de, 0x7b9a86c5, 0x769188cc, 0x55a0a2f3, 0x58abacfa, 0x4fb6bee1, 0x42bdb0e8, 0x09d4ea9f, 0x04dfe496, 0x13c2f68d, 0x1ec9f884, 0x3df8d2bb, 0x30f3dcb2, 0x27eecea9, 0x2ae5c0a0, 0xb13c7a47, 0xbc37744e, 0xab2a6655, 0xa621685c, 0x85104263, 0x881b4c6a, 0x9f065e71, 0x920d5078, 0xd9640a0f, 0xd46f0406, 0xc372161d, 0xce791814, 0xed48322b, 0xe0433c22, 0xf75e2e39, 0xfa552030, 0xb701ec9a, 0xba0ae293, 0xad17f088, 0xa01cfe81, 0x832dd4be, 0x8e26dab7, 0x993bc8ac, 0x9430c6a5, 0xdf599cd2, 0xd25292db, 0xc54f80c0, 0xc8448ec9, 0xeb75a4f6, 0xe67eaaff, 0xf163b8e4, 0xfc68b6ed, 0x67b10c0a, 0x6aba0203, 0x7da71018, 0x70ac1e11, 0x539d342e, 0x5e963a27, 0x498b283c, 0x44802635, 0x0fe97c42, 0x02e2724b, 0x15ff6050, 0x18f46e59, 0x3bc54466, 0x36ce4a6f, 0x21d35874, 0x2cd8567d, 0x0c7a37a1, 0x017139a8, 0x166c2bb3, 0x1b6725ba, 0x38560f85, 0x355d018c, 0x22401397, 0x2f4b1d9e, 0x642247e9, 0x692949e0, 0x7e345bfb, 0x733f55f2, 0x500e7fcd, 0x5d0571c4, 0x4a1863df, 0x47136dd6, 0xdccad731, 0xd1c1d938, 0xc6dccb23, 0xcbd7c52a, 0xe8e6ef15, 0xe5ede11c, 0xf2f0f307, 0xfffbfd0e, 0xb492a779, 0xb999a970, 0xae84bb6b, 0xa38fb562, 0x80be9f5d, 0x8db59154, 0x9aa8834f, 0x97a38d46]; - var U4 = [0x00000000, 0x090d0b0e, 0x121a161c, 0x1b171d12, 0x24342c38, 0x2d392736, 0x362e3a24, 0x3f23312a, 0x48685870, 0x4165537e, 0x5a724e6c, 0x537f4562, 0x6c5c7448, 0x65517f46, 0x7e466254, 0x774b695a, 0x90d0b0e0, 0x99ddbbee, 0x82caa6fc, 0x8bc7adf2, 0xb4e49cd8, 0xbde997d6, 0xa6fe8ac4, 0xaff381ca, 0xd8b8e890, 0xd1b5e39e, 0xcaa2fe8c, 0xc3aff582, 0xfc8cc4a8, 0xf581cfa6, 0xee96d2b4, 0xe79bd9ba, 0x3bbb7bdb, 0x32b670d5, 0x29a16dc7, 0x20ac66c9, 0x1f8f57e3, 0x16825ced, 0x0d9541ff, 0x04984af1, 0x73d323ab, 0x7ade28a5, 0x61c935b7, 0x68c43eb9, 0x57e70f93, 0x5eea049d, 0x45fd198f, 0x4cf01281, 0xab6bcb3b, 0xa266c035, 0xb971dd27, 0xb07cd629, 0x8f5fe703, 0x8652ec0d, 0x9d45f11f, 0x9448fa11, 0xe303934b, 0xea0e9845, 0xf1198557, 0xf8148e59, 0xc737bf73, 0xce3ab47d, 0xd52da96f, 0xdc20a261, 0x766df6ad, 0x7f60fda3, 0x6477e0b1, 0x6d7aebbf, 0x5259da95, 0x5b54d19b, 0x4043cc89, 0x494ec787, 0x3e05aedd, 0x3708a5d3, 0x2c1fb8c1, 0x2512b3cf, 0x1a3182e5, 0x133c89eb, 0x082b94f9, 0x01269ff7, 0xe6bd464d, 0xefb04d43, 0xf4a75051, 0xfdaa5b5f, 0xc2896a75, 0xcb84617b, 0xd0937c69, 0xd99e7767, 0xaed51e3d, 0xa7d81533, 0xbccf0821, 0xb5c2032f, 0x8ae13205, 0x83ec390b, 0x98fb2419, 0x91f62f17, 0x4dd68d76, 0x44db8678, 0x5fcc9b6a, 0x56c19064, 0x69e2a14e, 0x60efaa40, 0x7bf8b752, 0x72f5bc5c, 0x05bed506, 0x0cb3de08, 0x17a4c31a, 0x1ea9c814, 0x218af93e, 0x2887f230, 0x3390ef22, 0x3a9de42c, 0xdd063d96, 0xd40b3698, 0xcf1c2b8a, 0xc6112084, 0xf93211ae, 0xf03f1aa0, 0xeb2807b2, 0xe2250cbc, 0x956e65e6, 0x9c636ee8, 0x877473fa, 0x8e7978f4, 0xb15a49de, 0xb85742d0, 0xa3405fc2, 0xaa4d54cc, 0xecdaf741, 0xe5d7fc4f, 0xfec0e15d, 0xf7cdea53, 0xc8eedb79, 0xc1e3d077, 0xdaf4cd65, 0xd3f9c66b, 0xa4b2af31, 0xadbfa43f, 0xb6a8b92d, 0xbfa5b223, 0x80868309, 0x898b8807, 0x929c9515, 0x9b919e1b, 0x7c0a47a1, 0x75074caf, 0x6e1051bd, 0x671d5ab3, 0x583e6b99, 0x51336097, 0x4a247d85, 0x4329768b, 0x34621fd1, 0x3d6f14df, 0x267809cd, 0x2f7502c3, 0x105633e9, 0x195b38e7, 0x024c25f5, 0x0b412efb, 0xd7618c9a, 0xde6c8794, 0xc57b9a86, 0xcc769188, 0xf355a0a2, 0xfa58abac, 0xe14fb6be, 0xe842bdb0, 0x9f09d4ea, 0x9604dfe4, 0x8d13c2f6, 0x841ec9f8, 0xbb3df8d2, 0xb230f3dc, 0xa927eece, 0xa02ae5c0, 0x47b13c7a, 0x4ebc3774, 0x55ab2a66, 0x5ca62168, 0x63851042, 0x6a881b4c, 0x719f065e, 0x78920d50, 0x0fd9640a, 0x06d46f04, 0x1dc37216, 0x14ce7918, 0x2bed4832, 0x22e0433c, 0x39f75e2e, 0x30fa5520, 0x9ab701ec, 0x93ba0ae2, 0x88ad17f0, 0x81a01cfe, 0xbe832dd4, 0xb78e26da, 0xac993bc8, 0xa59430c6, 0xd2df599c, 0xdbd25292, 0xc0c54f80, 0xc9c8448e, 0xf6eb75a4, 0xffe67eaa, 0xe4f163b8, 0xedfc68b6, 0x0a67b10c, 0x036aba02, 0x187da710, 0x1170ac1e, 0x2e539d34, 0x275e963a, 0x3c498b28, 0x35448026, 0x420fe97c, 0x4b02e272, 0x5015ff60, 0x5918f46e, 0x663bc544, 0x6f36ce4a, 0x7421d358, 0x7d2cd856, 0xa10c7a37, 0xa8017139, 0xb3166c2b, 0xba1b6725, 0x8538560f, 0x8c355d01, 0x97224013, 0x9e2f4b1d, 0xe9642247, 0xe0692949, 0xfb7e345b, 0xf2733f55, 0xcd500e7f, 0xc45d0571, 0xdf4a1863, 0xd647136d, 0x31dccad7, 0x38d1c1d9, 0x23c6dccb, 0x2acbd7c5, 0x15e8e6ef, 0x1ce5ede1, 0x07f2f0f3, 0x0efffbfd, 0x79b492a7, 0x70b999a9, 0x6bae84bb, 0x62a38fb5, 0x5d80be9f, 0x548db591, 0x4f9aa883, 0x4697a38d]; - - function convertToInt32(bytes) { - var result = []; - for (var i = 0; i < bytes.length; i += 4) { - result.push( - (bytes[i ] << 24) | - (bytes[i + 1] << 16) | - (bytes[i + 2] << 8) | - bytes[i + 3] - ); - } - return result; - } - - var AES = function(key) { - if (!(this instanceof AES)) { - throw Error('AES must be instanitated with `new`'); - } - - Object.defineProperty(this, 'key', { - value: coerceArray(key, true) - }); - - this._prepare(); - } - - - AES.prototype._prepare = function() { - - var rounds = numberOfRounds[this.key.length]; - if (rounds == null) { - throw new Error('invalid key size (must be 16, 24 or 32 bytes)'); - } - - // encryption round keys - this._Ke = []; - - // decryption round keys - this._Kd = []; - - for (var i = 0; i <= rounds; i++) { - this._Ke.push([0, 0, 0, 0]); - this._Kd.push([0, 0, 0, 0]); - } - - var roundKeyCount = (rounds + 1) * 4; - var KC = this.key.length / 4; - - // convert the key into ints - var tk = convertToInt32(this.key); - - // copy values into round key arrays - var index; - for (var i = 0; i < KC; i++) { - index = i >> 2; - this._Ke[index][i % 4] = tk[i]; - this._Kd[rounds - index][i % 4] = tk[i]; - } - - // key expansion (fips-197 section 5.2) - var rconpointer = 0; - var t = KC, tt; - while (t < roundKeyCount) { - tt = tk[KC - 1]; - tk[0] ^= ((S[(tt >> 16) & 0xFF] << 24) ^ - (S[(tt >> 8) & 0xFF] << 16) ^ - (S[ tt & 0xFF] << 8) ^ - S[(tt >> 24) & 0xFF] ^ - (rcon[rconpointer] << 24)); - rconpointer += 1; - - // key expansion (for non-256 bit) - if (KC != 8) { - for (var i = 1; i < KC; i++) { - tk[i] ^= tk[i - 1]; - } - - // key expansion for 256-bit keys is "slightly different" (fips-197) - } else { - for (var i = 1; i < (KC / 2); i++) { - tk[i] ^= tk[i - 1]; - } - tt = tk[(KC / 2) - 1]; - - tk[KC / 2] ^= (S[ tt & 0xFF] ^ - (S[(tt >> 8) & 0xFF] << 8) ^ - (S[(tt >> 16) & 0xFF] << 16) ^ - (S[(tt >> 24) & 0xFF] << 24)); - - for (var i = (KC / 2) + 1; i < KC; i++) { - tk[i] ^= tk[i - 1]; - } - } - - // copy values into round key arrays - var i = 0, r, c; - while (i < KC && t < roundKeyCount) { - r = t >> 2; - c = t % 4; - this._Ke[r][c] = tk[i]; - this._Kd[rounds - r][c] = tk[i++]; - t++; - } - } - - // inverse-cipher-ify the decryption round key (fips-197 section 5.3) - for (var r = 1; r < rounds; r++) { - for (var c = 0; c < 4; c++) { - tt = this._Kd[r][c]; - this._Kd[r][c] = (U1[(tt >> 24) & 0xFF] ^ - U2[(tt >> 16) & 0xFF] ^ - U3[(tt >> 8) & 0xFF] ^ - U4[ tt & 0xFF]); - } - } - } - - AES.prototype.encrypt = function(plaintext) { - if (plaintext.length != 16) { - throw new Error('invalid plaintext size (must be 16 bytes)'); - } - - var rounds = this._Ke.length - 1; - var a = [0, 0, 0, 0]; - - // convert plaintext to (ints ^ key) - var t = convertToInt32(plaintext); - for (var i = 0; i < 4; i++) { - t[i] ^= this._Ke[0][i]; - } - - // apply round transforms - for (var r = 1; r < rounds; r++) { - for (var i = 0; i < 4; i++) { - a[i] = (T1[(t[ i ] >> 24) & 0xff] ^ - T2[(t[(i + 1) % 4] >> 16) & 0xff] ^ - T3[(t[(i + 2) % 4] >> 8) & 0xff] ^ - T4[ t[(i + 3) % 4] & 0xff] ^ - this._Ke[r][i]); - } - t = a.slice(); - } - - // the last round is special - var result = createArray(16), tt; - for (var i = 0; i < 4; i++) { - tt = this._Ke[rounds][i]; - result[4 * i ] = (S[(t[ i ] >> 24) & 0xff] ^ (tt >> 24)) & 0xff; - result[4 * i + 1] = (S[(t[(i + 1) % 4] >> 16) & 0xff] ^ (tt >> 16)) & 0xff; - result[4 * i + 2] = (S[(t[(i + 2) % 4] >> 8) & 0xff] ^ (tt >> 8)) & 0xff; - result[4 * i + 3] = (S[ t[(i + 3) % 4] & 0xff] ^ tt ) & 0xff; - } - - return result; - } - - AES.prototype.decrypt = function(ciphertext) { - if (ciphertext.length != 16) { - throw new Error('invalid ciphertext size (must be 16 bytes)'); - } - - var rounds = this._Kd.length - 1; - var a = [0, 0, 0, 0]; - - // convert plaintext to (ints ^ key) - var t = convertToInt32(ciphertext); - for (var i = 0; i < 4; i++) { - t[i] ^= this._Kd[0][i]; - } - - // apply round transforms - for (var r = 1; r < rounds; r++) { - for (var i = 0; i < 4; i++) { - a[i] = (T5[(t[ i ] >> 24) & 0xff] ^ - T6[(t[(i + 3) % 4] >> 16) & 0xff] ^ - T7[(t[(i + 2) % 4] >> 8) & 0xff] ^ - T8[ t[(i + 1) % 4] & 0xff] ^ - this._Kd[r][i]); - } - t = a.slice(); - } - - // the last round is special - var result = createArray(16), tt; - for (var i = 0; i < 4; i++) { - tt = this._Kd[rounds][i]; - result[4 * i ] = (Si[(t[ i ] >> 24) & 0xff] ^ (tt >> 24)) & 0xff; - result[4 * i + 1] = (Si[(t[(i + 3) % 4] >> 16) & 0xff] ^ (tt >> 16)) & 0xff; - result[4 * i + 2] = (Si[(t[(i + 2) % 4] >> 8) & 0xff] ^ (tt >> 8)) & 0xff; - result[4 * i + 3] = (Si[ t[(i + 1) % 4] & 0xff] ^ tt ) & 0xff; - } - - return result; - } - - - /** - * Mode Of Operation - Electonic Codebook (ECB) - */ - var ModeOfOperationECB = function(key) { - if (!(this instanceof ModeOfOperationECB)) { - throw Error('AES must be instanitated with `new`'); - } - - this.description = "Electronic Code Block"; - this.name = "ecb"; - - this._aes = new AES(key); - } - - ModeOfOperationECB.prototype.encrypt = function(plaintext) { - plaintext = coerceArray(plaintext); - - if ((plaintext.length % 16) !== 0) { - throw new Error('invalid plaintext size (must be multiple of 16 bytes)'); - } - - var ciphertext = createArray(plaintext.length); - var block = createArray(16); - - for (var i = 0; i < plaintext.length; i += 16) { - copyArray(plaintext, block, 0, i, i + 16); - block = this._aes.encrypt(block); - copyArray(block, ciphertext, i); - } - - return ciphertext; - } - - ModeOfOperationECB.prototype.decrypt = function(ciphertext) { - ciphertext = coerceArray(ciphertext); - - if ((ciphertext.length % 16) !== 0) { - throw new Error('invalid ciphertext size (must be multiple of 16 bytes)'); - } - - var plaintext = createArray(ciphertext.length); - var block = createArray(16); - - for (var i = 0; i < ciphertext.length; i += 16) { - copyArray(ciphertext, block, 0, i, i + 16); - block = this._aes.decrypt(block); - copyArray(block, plaintext, i); - } - - return plaintext; - } - - - /** - * Mode Of Operation - Cipher Block Chaining (CBC) - */ - var ModeOfOperationCBC = function(key, iv) { - if (!(this instanceof ModeOfOperationCBC)) { - throw Error('AES must be instanitated with `new`'); - } - - this.description = "Cipher Block Chaining"; - this.name = "cbc"; - - if (!iv) { - iv = createArray(16); - - } else if (iv.length != 16) { - throw new Error('invalid initialation vector size (must be 16 bytes)'); - } - - this._lastCipherblock = coerceArray(iv, true); - - this._aes = new AES(key); - } - - ModeOfOperationCBC.prototype.encrypt = function(plaintext) { - plaintext = coerceArray(plaintext); - - if ((plaintext.length % 16) !== 0) { - throw new Error('invalid plaintext size (must be multiple of 16 bytes)'); - } - - var ciphertext = createArray(plaintext.length); - var block = createArray(16); - - for (var i = 0; i < plaintext.length; i += 16) { - copyArray(plaintext, block, 0, i, i + 16); - - for (var j = 0; j < 16; j++) { - block[j] ^= this._lastCipherblock[j]; - } - - this._lastCipherblock = this._aes.encrypt(block); - copyArray(this._lastCipherblock, ciphertext, i); - } - - return ciphertext; - } - - ModeOfOperationCBC.prototype.decrypt = function(ciphertext) { - ciphertext = coerceArray(ciphertext); - - if ((ciphertext.length % 16) !== 0) { - throw new Error('invalid ciphertext size (must be multiple of 16 bytes)'); - } - - var plaintext = createArray(ciphertext.length); - var block = createArray(16); - - for (var i = 0; i < ciphertext.length; i += 16) { - copyArray(ciphertext, block, 0, i, i + 16); - block = this._aes.decrypt(block); - - for (var j = 0; j < 16; j++) { - plaintext[i + j] = block[j] ^ this._lastCipherblock[j]; - } - - copyArray(ciphertext, this._lastCipherblock, 0, i, i + 16); - } - - return plaintext; - } - - - /** - * Mode Of Operation - Cipher Feedback (CFB) - */ - var ModeOfOperationCFB = function(key, iv, segmentSize) { - if (!(this instanceof ModeOfOperationCFB)) { - throw Error('AES must be instanitated with `new`'); - } - - this.description = "Cipher Feedback"; - this.name = "cfb"; - - if (!iv) { - iv = createArray(16); - - } else if (iv.length != 16) { - throw new Error('invalid initialation vector size (must be 16 size)'); - } - - if (!segmentSize) { segmentSize = 1; } - - this.segmentSize = segmentSize; - - this._shiftRegister = coerceArray(iv, true); - - this._aes = new AES(key); - } - - ModeOfOperationCFB.prototype.encrypt = function(plaintext) { - if ((plaintext.length % this.segmentSize) != 0) { - throw new Error('invalid plaintext size (must be segmentSize bytes)'); - } - - var encrypted = coerceArray(plaintext, true); - - var xorSegment; - for (var i = 0; i < encrypted.length; i += this.segmentSize) { - xorSegment = this._aes.encrypt(this._shiftRegister); - for (var j = 0; j < this.segmentSize; j++) { - encrypted[i + j] ^= xorSegment[j]; - } - - // Shift the register - copyArray(this._shiftRegister, this._shiftRegister, 0, this.segmentSize); - copyArray(encrypted, this._shiftRegister, 16 - this.segmentSize, i, i + this.segmentSize); - } - - return encrypted; - } - - ModeOfOperationCFB.prototype.decrypt = function(ciphertext) { - if ((ciphertext.length % this.segmentSize) != 0) { - throw new Error('invalid ciphertext size (must be segmentSize bytes)'); - } - - var plaintext = coerceArray(ciphertext, true); - - var xorSegment; - for (var i = 0; i < plaintext.length; i += this.segmentSize) { - xorSegment = this._aes.encrypt(this._shiftRegister); - - for (var j = 0; j < this.segmentSize; j++) { - plaintext[i + j] ^= xorSegment[j]; - } - - // Shift the register - copyArray(this._shiftRegister, this._shiftRegister, 0, this.segmentSize); - copyArray(ciphertext, this._shiftRegister, 16 - this.segmentSize, i, i + this.segmentSize); - } - - return plaintext; - } - - /** - * Mode Of Operation - Output Feedback (OFB) - */ - var ModeOfOperationOFB = function(key, iv) { - if (!(this instanceof ModeOfOperationOFB)) { - throw Error('AES must be instanitated with `new`'); - } - - this.description = "Output Feedback"; - this.name = "ofb"; - - if (!iv) { - iv = createArray(16); - - } else if (iv.length != 16) { - throw new Error('invalid initialation vector size (must be 16 bytes)'); - } - - this._lastPrecipher = coerceArray(iv, true); - this._lastPrecipherIndex = 16; - - this._aes = new AES(key); - } - - ModeOfOperationOFB.prototype.encrypt = function(plaintext) { - var encrypted = coerceArray(plaintext, true); - - for (var i = 0; i < encrypted.length; i++) { - if (this._lastPrecipherIndex === 16) { - this._lastPrecipher = this._aes.encrypt(this._lastPrecipher); - this._lastPrecipherIndex = 0; - } - encrypted[i] ^= this._lastPrecipher[this._lastPrecipherIndex++]; - } - - return encrypted; - } - - // Decryption is symetric - ModeOfOperationOFB.prototype.decrypt = ModeOfOperationOFB.prototype.encrypt; - - - /** - * Counter object for CTR common mode of operation - */ - var Counter = function(initialValue) { - if (!(this instanceof Counter)) { - throw Error('Counter must be instanitated with `new`'); - } - - // We allow 0, but anything false-ish uses the default 1 - if (initialValue !== 0 && !initialValue) { initialValue = 1; } - - if (typeof(initialValue) === 'number') { - this._counter = createArray(16); - this.setValue(initialValue); - - } else { - this.setBytes(initialValue); - } - } - - Counter.prototype.setValue = function(value) { - if (typeof(value) !== 'number' || parseInt(value) != value) { - throw new Error('invalid counter value (must be an integer)'); - } - - // We cannot safely handle numbers beyond the safe range for integers - if (value > Number.MAX_SAFE_INTEGER) { - throw new Error('integer value out of safe range'); - } - - for (var index = 15; index >= 0; --index) { - this._counter[index] = value % 256; - value = parseInt(value / 256); - } - } - - Counter.prototype.setBytes = function(bytes) { - bytes = coerceArray(bytes, true); - - if (bytes.length != 16) { - throw new Error('invalid counter bytes size (must be 16 bytes)'); - } - - this._counter = bytes; - }; - - Counter.prototype.increment = function() { - for (var i = 15; i >= 0; i--) { - if (this._counter[i] === 255) { - this._counter[i] = 0; - } else { - this._counter[i]++; - break; - } - } - } - - - /** - * Mode Of Operation - Counter (CTR) - */ - var ModeOfOperationCTR = function(key, counter) { - if (!(this instanceof ModeOfOperationCTR)) { - throw Error('AES must be instanitated with `new`'); - } - - this.description = "Counter"; - this.name = "ctr"; - - if (!(counter instanceof Counter)) { - counter = new Counter(counter) - } - - this._counter = counter; - - this._remainingCounter = null; - this._remainingCounterIndex = 16; - - this._aes = new AES(key); - } - - ModeOfOperationCTR.prototype.encrypt = function(plaintext) { - var encrypted = coerceArray(plaintext, true); - - for (var i = 0; i < encrypted.length; i++) { - if (this._remainingCounterIndex === 16) { - this._remainingCounter = this._aes.encrypt(this._counter._counter); - this._remainingCounterIndex = 0; - this._counter.increment(); - } - encrypted[i] ^= this._remainingCounter[this._remainingCounterIndex++]; - } - - return encrypted; - } - - // Decryption is symetric - ModeOfOperationCTR.prototype.decrypt = ModeOfOperationCTR.prototype.encrypt; - - - /////////////////////// - // Padding - - // See:https://tools.ietf.org/html/rfc2315 - function pkcs7pad(data) { - data = coerceArray(data, true); - var padder = 16 - (data.length % 16); - var result = createArray(data.length + padder); - copyArray(data, result); - for (var i = data.length; i < result.length; i++) { - result[i] = padder; - } - return result; - } - - function pkcs7strip(data) { - data = coerceArray(data, true); - if (data.length < 16) { throw new Error('PKCS#7 invalid length'); } - - var padder = data[data.length - 1]; - if (padder > 16) { throw new Error('PKCS#7 padding byte out of range'); } - - var length = data.length - padder; - for (var i = 0; i < padder; i++) { - if (data[length + i] !== padder) { - throw new Error('PKCS#7 invalid padding byte'); - } - } - - var result = createArray(length); - copyArray(data, result, 0, 0, length); - return result; - } - - /////////////////////// - // Exporting - - - // The block cipher - var aesjs = { - AES: AES, - Counter: Counter, - - ModeOfOperation: { - ecb: ModeOfOperationECB, - cbc: ModeOfOperationCBC, - cfb: ModeOfOperationCFB, - ofb: ModeOfOperationOFB, - ctr: ModeOfOperationCTR - }, - - utils: { - hex: convertHex, - utf8: convertUtf8 - }, - - padding: { - pkcs7: { - pad: pkcs7pad, - strip: pkcs7strip - } - }, - - _arrayTest: { - coerceArray: coerceArray, - createArray: createArray, - copyArray: copyArray, - } - }; - - - // node.js - if (typeof exports !== 'undefined') { - module.exports = aesjs - - // RequireJS/AMD - // http://www.requirejs.org/docs/api.html - // https://github.com/amdjs/amdjs-api/wiki/AMD - } else if (typeof(define) === 'function' && define.amd) { - define([], function() { return aesjs; }); - - // Web Browsers - } else { - - // If there was an existing library at "aesjs" make sure it's still available - if (root.aesjs) { - aesjs._aesjs = root.aesjs; - } - - root.aesjs = aesjs; - } - - -})(this); \ No newline at end of file diff --git a/lnbits/extensions/watchonly/static/js/crypto/noble-secp256k1.js b/lnbits/extensions/watchonly/static/js/crypto/noble-secp256k1.js deleted file mode 100644 index 8be86729..00000000 --- a/lnbits/extensions/watchonly/static/js/crypto/noble-secp256k1.js +++ /dev/null @@ -1,1177 +0,0 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define(['exports'], factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.nobleSecp256k1 = {})); -})(this, (function (exports) { 'use strict'; - - const _nodeResolve_empty = {}; - - const nodeCrypto = /*#__PURE__*/Object.freeze({ - __proto__: null, - 'default': _nodeResolve_empty - }); - - /*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */ - const _0n = BigInt(0); - const _1n = BigInt(1); - const _2n = BigInt(2); - const _3n = BigInt(3); - const _8n = BigInt(8); - const POW_2_256 = _2n ** BigInt(256); - const CURVE = { - a: _0n, - b: BigInt(7), - P: POW_2_256 - _2n ** BigInt(32) - BigInt(977), - n: POW_2_256 - BigInt('432420386565659656852420866394968145599'), - h: _1n, - Gx: BigInt('55066263022277343669578718895168534326250603453777594175500187360389116729240'), - Gy: BigInt('32670510020758816978083085130507043184471273380659243275938904335757337482424'), - beta: BigInt('0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'), - }; - function weistrass(x) { - const { a, b } = CURVE; - const x2 = mod(x * x); - const x3 = mod(x2 * x); - return mod(x3 + a * x + b); - } - const USE_ENDOMORPHISM = CURVE.a === _0n; - class JacobianPoint { - constructor(x, y, z) { - this.x = x; - this.y = y; - this.z = z; - } - static fromAffine(p) { - if (!(p instanceof Point)) { - throw new TypeError('JacobianPoint#fromAffine: expected Point'); - } - return new JacobianPoint(p.x, p.y, _1n); - } - static toAffineBatch(points) { - const toInv = invertBatch(points.map((p) => p.z)); - return points.map((p, i) => p.toAffine(toInv[i])); - } - static normalizeZ(points) { - return JacobianPoint.toAffineBatch(points).map(JacobianPoint.fromAffine); - } - equals(other) { - if (!(other instanceof JacobianPoint)) - throw new TypeError('JacobianPoint expected'); - const { x: X1, y: Y1, z: Z1 } = this; - const { x: X2, y: Y2, z: Z2 } = other; - const Z1Z1 = mod(Z1 ** _2n); - const Z2Z2 = mod(Z2 ** _2n); - const U1 = mod(X1 * Z2Z2); - const U2 = mod(X2 * Z1Z1); - const S1 = mod(mod(Y1 * Z2) * Z2Z2); - const S2 = mod(mod(Y2 * Z1) * Z1Z1); - return U1 === U2 && S1 === S2; - } - negate() { - return new JacobianPoint(this.x, mod(-this.y), this.z); - } - double() { - const { x: X1, y: Y1, z: Z1 } = this; - const A = mod(X1 ** _2n); - const B = mod(Y1 ** _2n); - const C = mod(B ** _2n); - const D = mod(_2n * (mod((X1 + B) ** _2n) - A - C)); - const E = mod(_3n * A); - const F = mod(E ** _2n); - const X3 = mod(F - _2n * D); - const Y3 = mod(E * (D - X3) - _8n * C); - const Z3 = mod(_2n * Y1 * Z1); - return new JacobianPoint(X3, Y3, Z3); - } - add(other) { - if (!(other instanceof JacobianPoint)) - throw new TypeError('JacobianPoint expected'); - const { x: X1, y: Y1, z: Z1 } = this; - const { x: X2, y: Y2, z: Z2 } = other; - if (X2 === _0n || Y2 === _0n) - return this; - if (X1 === _0n || Y1 === _0n) - return other; - const Z1Z1 = mod(Z1 ** _2n); - const Z2Z2 = mod(Z2 ** _2n); - const U1 = mod(X1 * Z2Z2); - const U2 = mod(X2 * Z1Z1); - const S1 = mod(mod(Y1 * Z2) * Z2Z2); - const S2 = mod(mod(Y2 * Z1) * Z1Z1); - const H = mod(U2 - U1); - const r = mod(S2 - S1); - if (H === _0n) { - if (r === _0n) { - return this.double(); - } - else { - return JacobianPoint.ZERO; - } - } - const HH = mod(H ** _2n); - const HHH = mod(H * HH); - const V = mod(U1 * HH); - const X3 = mod(r ** _2n - HHH - _2n * V); - const Y3 = mod(r * (V - X3) - S1 * HHH); - const Z3 = mod(Z1 * Z2 * H); - return new JacobianPoint(X3, Y3, Z3); - } - subtract(other) { - return this.add(other.negate()); - } - multiplyUnsafe(scalar) { - const P0 = JacobianPoint.ZERO; - if (typeof scalar === 'bigint' && scalar === _0n) - return P0; - let n = normalizeScalar(scalar); - if (n === _1n) - return this; - if (!USE_ENDOMORPHISM) { - let p = P0; - let d = this; - while (n > _0n) { - if (n & _1n) - p = p.add(d); - d = d.double(); - n >>= _1n; - } - return p; - } - let { k1neg, k1, k2neg, k2 } = splitScalarEndo(n); - let k1p = P0; - let k2p = P0; - let d = this; - while (k1 > _0n || k2 > _0n) { - if (k1 & _1n) - k1p = k1p.add(d); - if (k2 & _1n) - k2p = k2p.add(d); - d = d.double(); - k1 >>= _1n; - k2 >>= _1n; - } - if (k1neg) - k1p = k1p.negate(); - if (k2neg) - k2p = k2p.negate(); - k2p = new JacobianPoint(mod(k2p.x * CURVE.beta), k2p.y, k2p.z); - return k1p.add(k2p); - } - precomputeWindow(W) { - const windows = USE_ENDOMORPHISM ? 128 / W + 1 : 256 / W + 1; - const points = []; - let p = this; - let base = p; - for (let window = 0; window < windows; window++) { - base = p; - points.push(base); - for (let i = 1; i < 2 ** (W - 1); i++) { - base = base.add(p); - points.push(base); - } - p = base.double(); - } - return points; - } - wNAF(n, affinePoint) { - if (!affinePoint && this.equals(JacobianPoint.BASE)) - affinePoint = Point.BASE; - const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1; - if (256 % W) { - throw new Error('Point#wNAF: Invalid precomputation window, must be power of 2'); - } - let precomputes = affinePoint && pointPrecomputes.get(affinePoint); - if (!precomputes) { - precomputes = this.precomputeWindow(W); - if (affinePoint && W !== 1) { - precomputes = JacobianPoint.normalizeZ(precomputes); - pointPrecomputes.set(affinePoint, precomputes); - } - } - let p = JacobianPoint.ZERO; - let f = JacobianPoint.ZERO; - const windows = 1 + (USE_ENDOMORPHISM ? 128 / W : 256 / W); - const windowSize = 2 ** (W - 1); - const mask = BigInt(2 ** W - 1); - const maxNumber = 2 ** W; - const shiftBy = BigInt(W); - for (let window = 0; window < windows; window++) { - const offset = window * windowSize; - let wbits = Number(n & mask); - n >>= shiftBy; - if (wbits > windowSize) { - wbits -= maxNumber; - n += _1n; - } - if (wbits === 0) { - let pr = precomputes[offset]; - if (window % 2) - pr = pr.negate(); - f = f.add(pr); - } - else { - let cached = precomputes[offset + Math.abs(wbits) - 1]; - if (wbits < 0) - cached = cached.negate(); - p = p.add(cached); - } - } - return { p, f }; - } - multiply(scalar, affinePoint) { - let n = normalizeScalar(scalar); - let point; - let fake; - if (USE_ENDOMORPHISM) { - const { k1neg, k1, k2neg, k2 } = splitScalarEndo(n); - let { p: k1p, f: f1p } = this.wNAF(k1, affinePoint); - let { p: k2p, f: f2p } = this.wNAF(k2, affinePoint); - if (k1neg) - k1p = k1p.negate(); - if (k2neg) - k2p = k2p.negate(); - k2p = new JacobianPoint(mod(k2p.x * CURVE.beta), k2p.y, k2p.z); - point = k1p.add(k2p); - fake = f1p.add(f2p); - } - else { - const { p, f } = this.wNAF(n, affinePoint); - point = p; - fake = f; - } - return JacobianPoint.normalizeZ([point, fake])[0]; - } - toAffine(invZ = invert(this.z)) { - const { x, y, z } = this; - const iz1 = invZ; - const iz2 = mod(iz1 * iz1); - const iz3 = mod(iz2 * iz1); - const ax = mod(x * iz2); - const ay = mod(y * iz3); - const zz = mod(z * iz1); - if (zz !== _1n) - throw new Error('invZ was invalid'); - return new Point(ax, ay); - } - } - JacobianPoint.BASE = new JacobianPoint(CURVE.Gx, CURVE.Gy, _1n); - JacobianPoint.ZERO = new JacobianPoint(_0n, _1n, _0n); - const pointPrecomputes = new WeakMap(); - class Point { - constructor(x, y) { - this.x = x; - this.y = y; - } - _setWindowSize(windowSize) { - this._WINDOW_SIZE = windowSize; - pointPrecomputes.delete(this); - } - static fromCompressedHex(bytes) { - const isShort = bytes.length === 32; - const x = bytesToNumber(isShort ? bytes : bytes.subarray(1)); - if (!isValidFieldElement(x)) - throw new Error('Point is not on curve'); - const y2 = weistrass(x); - let y = sqrtMod(y2); - const isYOdd = (y & _1n) === _1n; - if (isShort) { - if (isYOdd) - y = mod(-y); - } - else { - const isFirstByteOdd = (bytes[0] & 1) === 1; - if (isFirstByteOdd !== isYOdd) - y = mod(-y); - } - const point = new Point(x, y); - point.assertValidity(); - return point; - } - static fromUncompressedHex(bytes) { - const x = bytesToNumber(bytes.subarray(1, 33)); - const y = bytesToNumber(bytes.subarray(33, 65)); - const point = new Point(x, y); - point.assertValidity(); - return point; - } - static fromHex(hex) { - const bytes = ensureBytes(hex); - const len = bytes.length; - const header = bytes[0]; - if (len === 32 || (len === 33 && (header === 0x02 || header === 0x03))) { - return this.fromCompressedHex(bytes); - } - if (len === 65 && header === 0x04) - return this.fromUncompressedHex(bytes); - throw new Error(`Point.fromHex: received invalid point. Expected 32-33 compressed bytes or 65 uncompressed bytes, not ${len}`); - } - static fromPrivateKey(privateKey) { - return Point.BASE.multiply(normalizePrivateKey(privateKey)); - } - static fromSignature(msgHash, signature, recovery) { - msgHash = ensureBytes(msgHash); - const h = truncateHash(msgHash); - const { r, s } = normalizeSignature(signature); - if (recovery !== 0 && recovery !== 1) { - throw new Error('Cannot recover signature: invalid recovery bit'); - } - const prefix = recovery & 1 ? '03' : '02'; - const R = Point.fromHex(prefix + numTo32bStr(r)); - const { n } = CURVE; - const rinv = invert(r, n); - const u1 = mod(-h * rinv, n); - const u2 = mod(s * rinv, n); - const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2); - if (!Q) - throw new Error('Cannot recover signature: point at infinify'); - Q.assertValidity(); - return Q; - } - toRawBytes(isCompressed = false) { - return hexToBytes(this.toHex(isCompressed)); - } - toHex(isCompressed = false) { - const x = numTo32bStr(this.x); - if (isCompressed) { - const prefix = this.y & _1n ? '03' : '02'; - return `${prefix}${x}`; - } - else { - return `04${x}${numTo32bStr(this.y)}`; - } - } - toHexX() { - return this.toHex(true).slice(2); - } - toRawX() { - return this.toRawBytes(true).slice(1); - } - assertValidity() { - const msg = 'Point is not on elliptic curve'; - const { x, y } = this; - if (!isValidFieldElement(x) || !isValidFieldElement(y)) - throw new Error(msg); - const left = mod(y * y); - const right = weistrass(x); - if (mod(left - right) !== _0n) - throw new Error(msg); - } - equals(other) { - return this.x === other.x && this.y === other.y; - } - negate() { - return new Point(this.x, mod(-this.y)); - } - double() { - return JacobianPoint.fromAffine(this).double().toAffine(); - } - add(other) { - return JacobianPoint.fromAffine(this).add(JacobianPoint.fromAffine(other)).toAffine(); - } - subtract(other) { - return this.add(other.negate()); - } - multiply(scalar) { - return JacobianPoint.fromAffine(this).multiply(scalar, this).toAffine(); - } - multiplyAndAddUnsafe(Q, a, b) { - const P = JacobianPoint.fromAffine(this); - const aP = a === _0n || a === _1n || this !== Point.BASE ? P.multiplyUnsafe(a) : P.multiply(a); - const bQ = JacobianPoint.fromAffine(Q).multiplyUnsafe(b); - const sum = aP.add(bQ); - return sum.equals(JacobianPoint.ZERO) ? undefined : sum.toAffine(); - } - } - Point.BASE = new Point(CURVE.Gx, CURVE.Gy); - Point.ZERO = new Point(_0n, _0n); - function sliceDER(s) { - return Number.parseInt(s[0], 16) >= 8 ? '00' + s : s; - } - function parseDERInt(data) { - if (data.length < 2 || data[0] !== 0x02) { - throw new Error(`Invalid signature integer tag: ${bytesToHex(data)}`); - } - const len = data[1]; - const res = data.subarray(2, len + 2); - if (!len || res.length !== len) { - throw new Error(`Invalid signature integer: wrong length`); - } - if (res[0] === 0x00 && res[1] <= 0x7f) { - throw new Error('Invalid signature integer: trailing length'); - } - return { data: bytesToNumber(res), left: data.subarray(len + 2) }; - } - function parseDERSignature(data) { - if (data.length < 2 || data[0] != 0x30) { - throw new Error(`Invalid signature tag: ${bytesToHex(data)}`); - } - if (data[1] !== data.length - 2) { - throw new Error('Invalid signature: incorrect length'); - } - const { data: r, left: sBytes } = parseDERInt(data.subarray(2)); - const { data: s, left: rBytesLeft } = parseDERInt(sBytes); - if (rBytesLeft.length) { - throw new Error(`Invalid signature: left bytes after parsing: ${bytesToHex(rBytesLeft)}`); - } - return { r, s }; - } - class Signature { - constructor(r, s) { - this.r = r; - this.s = s; - this.assertValidity(); - } - static fromCompact(hex) { - const arr = isUint8a(hex); - const name = 'Signature.fromCompact'; - if (typeof hex !== 'string' && !arr) - throw new TypeError(`${name}: Expected string or Uint8Array`); - const str = arr ? bytesToHex(hex) : hex; - if (str.length !== 128) - throw new Error(`${name}: Expected 64-byte hex`); - return new Signature(hexToNumber(str.slice(0, 64)), hexToNumber(str.slice(64, 128))); - } - static fromDER(hex) { - const arr = isUint8a(hex); - if (typeof hex !== 'string' && !arr) - throw new TypeError(`Signature.fromDER: Expected string or Uint8Array`); - const { r, s } = parseDERSignature(arr ? hex : hexToBytes(hex)); - return new Signature(r, s); - } - static fromHex(hex) { - return this.fromDER(hex); - } - assertValidity() { - const { r, s } = this; - if (!isWithinCurveOrder(r)) - throw new Error('Invalid Signature: r must be 0 < r < n'); - if (!isWithinCurveOrder(s)) - throw new Error('Invalid Signature: s must be 0 < s < n'); - } - hasHighS() { - const HALF = CURVE.n >> _1n; - return this.s > HALF; - } - normalizeS() { - return this.hasHighS() ? new Signature(this.r, CURVE.n - this.s) : this; - } - toDERRawBytes(isCompressed = false) { - return hexToBytes(this.toDERHex(isCompressed)); - } - toDERHex(isCompressed = false) { - const sHex = sliceDER(numberToHexUnpadded(this.s)); - if (isCompressed) - return sHex; - const rHex = sliceDER(numberToHexUnpadded(this.r)); - const rLen = numberToHexUnpadded(rHex.length / 2); - const sLen = numberToHexUnpadded(sHex.length / 2); - const length = numberToHexUnpadded(rHex.length / 2 + sHex.length / 2 + 4); - return `30${length}02${rLen}${rHex}02${sLen}${sHex}`; - } - toRawBytes() { - return this.toDERRawBytes(); - } - toHex() { - return this.toDERHex(); - } - toCompactRawBytes() { - return hexToBytes(this.toCompactHex()); - } - toCompactHex() { - return numTo32bStr(this.r) + numTo32bStr(this.s); - } - } - function concatBytes(...arrays) { - if (!arrays.every(isUint8a)) - throw new Error('Uint8Array list expected'); - if (arrays.length === 1) - return arrays[0]; - const length = arrays.reduce((a, arr) => a + arr.length, 0); - const result = new Uint8Array(length); - for (let i = 0, pad = 0; i < arrays.length; i++) { - const arr = arrays[i]; - result.set(arr, pad); - pad += arr.length; - } - return result; - } - function isUint8a(bytes) { - return bytes instanceof Uint8Array; - } - const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0')); - function bytesToHex(uint8a) { - if (!(uint8a instanceof Uint8Array)) - throw new Error('Expected Uint8Array'); - let hex = ''; - for (let i = 0; i < uint8a.length; i++) { - hex += hexes[uint8a[i]]; - } - return hex; - } - function numTo32bStr(num) { - if (num > POW_2_256) - throw new Error('Expected number < 2^256'); - return num.toString(16).padStart(64, '0'); - } - function numTo32b(num) { - return hexToBytes(numTo32bStr(num)); - } - function numberToHexUnpadded(num) { - const hex = num.toString(16); - return hex.length & 1 ? `0${hex}` : hex; - } - function hexToNumber(hex) { - if (typeof hex !== 'string') { - throw new TypeError('hexToNumber: expected string, got ' + typeof hex); - } - return BigInt(`0x${hex}`); - } - function hexToBytes(hex) { - if (typeof hex !== 'string') { - throw new TypeError('hexToBytes: expected string, got ' + typeof hex); - } - if (hex.length % 2) - throw new Error('hexToBytes: received invalid unpadded hex' + hex.length); - const array = new Uint8Array(hex.length / 2); - for (let i = 0; i < array.length; i++) { - const j = i * 2; - const hexByte = hex.slice(j, j + 2); - const byte = Number.parseInt(hexByte, 16); - if (Number.isNaN(byte) || byte < 0) - throw new Error('Invalid byte sequence'); - array[i] = byte; - } - return array; - } - function bytesToNumber(bytes) { - return hexToNumber(bytesToHex(bytes)); - } - function ensureBytes(hex) { - return hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes(hex); - } - function normalizeScalar(num) { - if (typeof num === 'number' && Number.isSafeInteger(num) && num > 0) - return BigInt(num); - if (typeof num === 'bigint' && isWithinCurveOrder(num)) - return num; - throw new TypeError('Expected valid private scalar: 0 < scalar < curve.n'); - } - function mod(a, b = CURVE.P) { - const result = a % b; - return result >= _0n ? result : b + result; - } - function pow2(x, power) { - const { P } = CURVE; - let res = x; - while (power-- > _0n) { - res *= res; - res %= P; - } - return res; - } - function sqrtMod(x) { - const { P } = CURVE; - const _6n = BigInt(6); - const _11n = BigInt(11); - const _22n = BigInt(22); - const _23n = BigInt(23); - const _44n = BigInt(44); - const _88n = BigInt(88); - const b2 = (x * x * x) % P; - const b3 = (b2 * b2 * x) % P; - const b6 = (pow2(b3, _3n) * b3) % P; - const b9 = (pow2(b6, _3n) * b3) % P; - const b11 = (pow2(b9, _2n) * b2) % P; - const b22 = (pow2(b11, _11n) * b11) % P; - const b44 = (pow2(b22, _22n) * b22) % P; - const b88 = (pow2(b44, _44n) * b44) % P; - const b176 = (pow2(b88, _88n) * b88) % P; - const b220 = (pow2(b176, _44n) * b44) % P; - const b223 = (pow2(b220, _3n) * b3) % P; - const t1 = (pow2(b223, _23n) * b22) % P; - const t2 = (pow2(t1, _6n) * b2) % P; - return pow2(t2, _2n); - } - function invert(number, modulo = CURVE.P) { - if (number === _0n || modulo <= _0n) { - throw new Error(`invert: expected positive integers, got n=${number} mod=${modulo}`); - } - let a = mod(number, modulo); - let b = modulo; - let x = _0n, u = _1n; - while (a !== _0n) { - const q = b / a; - const r = b % a; - const m = x - u * q; - b = a, a = r, x = u, u = m; - } - const gcd = b; - if (gcd !== _1n) - throw new Error('invert: does not exist'); - return mod(x, modulo); - } - function invertBatch(nums, p = CURVE.P) { - const scratch = new Array(nums.length); - const lastMultiplied = nums.reduce((acc, num, i) => { - if (num === _0n) - return acc; - scratch[i] = acc; - return mod(acc * num, p); - }, _1n); - const inverted = invert(lastMultiplied, p); - nums.reduceRight((acc, num, i) => { - if (num === _0n) - return acc; - scratch[i] = mod(acc * scratch[i], p); - return mod(acc * num, p); - }, inverted); - return scratch; - } - const divNearest = (a, b) => (a + b / _2n) / b; - const POW_2_128 = _2n ** BigInt(128); - function splitScalarEndo(k) { - const { n } = CURVE; - const a1 = BigInt('0x3086d221a7d46bcde86c90e49284eb15'); - const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3'); - const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8'); - const b2 = a1; - const c1 = divNearest(b2 * k, n); - const c2 = divNearest(-b1 * k, n); - let k1 = mod(k - c1 * a1 - c2 * a2, n); - let k2 = mod(-c1 * b1 - c2 * b2, n); - const k1neg = k1 > POW_2_128; - const k2neg = k2 > POW_2_128; - if (k1neg) - k1 = n - k1; - if (k2neg) - k2 = n - k2; - if (k1 > POW_2_128 || k2 > POW_2_128) { - throw new Error('splitScalarEndo: Endomorphism failed, k=' + k); - } - return { k1neg, k1, k2neg, k2 }; - } - function truncateHash(hash) { - const { n } = CURVE; - const byteLength = hash.length; - const delta = byteLength * 8 - 256; - let h = bytesToNumber(hash); - if (delta > 0) - h = h >> BigInt(delta); - if (h >= n) - h -= n; - return h; - } - class HmacDrbg { - constructor() { - this.v = new Uint8Array(32).fill(1); - this.k = new Uint8Array(32).fill(0); - this.counter = 0; - } - hmac(...values) { - return utils.hmacSha256(this.k, ...values); - } - hmacSync(...values) { - if (typeof utils.hmacSha256Sync !== 'function') - throw new Error('utils.hmacSha256Sync is undefined, you need to set it'); - const res = utils.hmacSha256Sync(this.k, ...values); - if (res instanceof Promise) - throw new Error('To use sync sign(), ensure utils.hmacSha256 is sync'); - return res; - } - incr() { - if (this.counter >= 1000) { - throw new Error('Tried 1,000 k values for sign(), all were invalid'); - } - this.counter += 1; - } - async reseed(seed = new Uint8Array()) { - this.k = await this.hmac(this.v, Uint8Array.from([0x00]), seed); - this.v = await this.hmac(this.v); - if (seed.length === 0) - return; - this.k = await this.hmac(this.v, Uint8Array.from([0x01]), seed); - this.v = await this.hmac(this.v); - } - reseedSync(seed = new Uint8Array()) { - this.k = this.hmacSync(this.v, Uint8Array.from([0x00]), seed); - this.v = this.hmacSync(this.v); - if (seed.length === 0) - return; - this.k = this.hmacSync(this.v, Uint8Array.from([0x01]), seed); - this.v = this.hmacSync(this.v); - } - async generate() { - this.incr(); - this.v = await this.hmac(this.v); - return this.v; - } - generateSync() { - this.incr(); - this.v = this.hmacSync(this.v); - return this.v; - } - } - function isWithinCurveOrder(num) { - return _0n < num && num < CURVE.n; - } - function isValidFieldElement(num) { - return _0n < num && num < CURVE.P; - } - function kmdToSig(kBytes, m, d) { - const k = bytesToNumber(kBytes); - if (!isWithinCurveOrder(k)) - return; - const { n } = CURVE; - const q = Point.BASE.multiply(k); - const r = mod(q.x, n); - if (r === _0n) - return; - const s = mod(invert(k, n) * mod(m + d * r, n), n); - if (s === _0n) - return; - const sig = new Signature(r, s); - const recovery = (q.x === sig.r ? 0 : 2) | Number(q.y & _1n); - return { sig, recovery }; - } - function normalizePrivateKey(key) { - let num; - if (typeof key === 'bigint') { - num = key; - } - else if (typeof key === 'number' && Number.isSafeInteger(key) && key > 0) { - num = BigInt(key); - } - else if (typeof key === 'string') { - if (key.length !== 64) - throw new Error('Expected 32 bytes of private key'); - num = hexToNumber(key); - } - else if (isUint8a(key)) { - if (key.length !== 32) - throw new Error('Expected 32 bytes of private key'); - num = bytesToNumber(key); - } - else { - throw new TypeError('Expected valid private key'); - } - if (!isWithinCurveOrder(num)) - throw new Error('Expected private key: 0 < key < n'); - return num; - } - function normalizePublicKey(publicKey) { - if (publicKey instanceof Point) { - publicKey.assertValidity(); - return publicKey; - } - else { - return Point.fromHex(publicKey); - } - } - function normalizeSignature(signature) { - if (signature instanceof Signature) { - signature.assertValidity(); - return signature; - } - try { - return Signature.fromDER(signature); - } - catch (error) { - return Signature.fromCompact(signature); - } - } - function getPublicKey(privateKey, isCompressed = false) { - return Point.fromPrivateKey(privateKey).toRawBytes(isCompressed); - } - function recoverPublicKey(msgHash, signature, recovery, isCompressed = false) { - return Point.fromSignature(msgHash, signature, recovery).toRawBytes(isCompressed); - } - function isPub(item) { - const arr = isUint8a(item); - const str = typeof item === 'string'; - const len = (arr || str) && item.length; - if (arr) - return len === 33 || len === 65; - if (str) - return len === 66 || len === 130; - if (item instanceof Point) - return true; - return false; - } - function getSharedSecret(privateA, publicB, isCompressed = false) { - if (isPub(privateA)) - throw new TypeError('getSharedSecret: first arg must be private key'); - if (!isPub(publicB)) - throw new TypeError('getSharedSecret: second arg must be public key'); - const b = normalizePublicKey(publicB); - b.assertValidity(); - return b.multiply(normalizePrivateKey(privateA)).toRawBytes(isCompressed); - } - function bits2int(bytes) { - const slice = bytes.length > 32 ? bytes.slice(0, 32) : bytes; - return bytesToNumber(slice); - } - function bits2octets(bytes) { - const z1 = bits2int(bytes); - const z2 = mod(z1, CURVE.n); - return int2octets(z2 < _0n ? z1 : z2); - } - function int2octets(num) { - if (typeof num !== 'bigint') - throw new Error('Expected bigint'); - const hex = numTo32bStr(num); - return hexToBytes(hex); - } - function initSigArgs(msgHash, privateKey, extraEntropy) { - if (msgHash == null) - throw new Error(`sign: expected valid message hash, not "${msgHash}"`); - const h1 = ensureBytes(msgHash); - const d = normalizePrivateKey(privateKey); - const seedArgs = [int2octets(d), bits2octets(h1)]; - if (extraEntropy != null) { - if (extraEntropy === true) - extraEntropy = utils.randomBytes(32); - const e = ensureBytes(extraEntropy); - if (e.length !== 32) - throw new Error('sign: Expected 32 bytes of extra data'); - seedArgs.push(e); - } - const seed = concatBytes(...seedArgs); - const m = bits2int(h1); - return { seed, m, d }; - } - function finalizeSig(recSig, opts) { - let { sig, recovery } = recSig; - const { canonical, der, recovered } = Object.assign({ canonical: true, der: true }, opts); - if (canonical && sig.hasHighS()) { - sig = sig.normalizeS(); - recovery ^= 1; - } - const hashed = der ? sig.toDERRawBytes() : sig.toCompactRawBytes(); - return recovered ? [hashed, recovery] : hashed; - } - async function sign(msgHash, privKey, opts = {}) { - const { seed, m, d } = initSigArgs(msgHash, privKey, opts.extraEntropy); - let sig; - const drbg = new HmacDrbg(); - await drbg.reseed(seed); - while (!(sig = kmdToSig(await drbg.generate(), m, d))) - await drbg.reseed(); - return finalizeSig(sig, opts); - } - function signSync(msgHash, privKey, opts = {}) { - const { seed, m, d } = initSigArgs(msgHash, privKey, opts.extraEntropy); - let sig; - const drbg = new HmacDrbg(); - drbg.reseedSync(seed); - while (!(sig = kmdToSig(drbg.generateSync(), m, d))) - drbg.reseedSync(); - return finalizeSig(sig, opts); - } - const vopts = { strict: true }; - function verify(signature, msgHash, publicKey, opts = vopts) { - let sig; - try { - sig = normalizeSignature(signature); - msgHash = ensureBytes(msgHash); - } - catch (error) { - return false; - } - const { r, s } = sig; - if (opts.strict && sig.hasHighS()) - return false; - const h = truncateHash(msgHash); - let P; - try { - P = normalizePublicKey(publicKey); - } - catch (error) { - return false; - } - const { n } = CURVE; - const sinv = invert(s, n); - const u1 = mod(h * sinv, n); - const u2 = mod(r * sinv, n); - const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2); - if (!R) - return false; - const v = mod(R.x, n); - return v === r; - } - function finalizeSchnorrChallenge(ch) { - return mod(bytesToNumber(ch), CURVE.n); - } - function hasEvenY(point) { - return (point.y & _1n) === _0n; - } - class SchnorrSignature { - constructor(r, s) { - this.r = r; - this.s = s; - this.assertValidity(); - } - static fromHex(hex) { - const bytes = ensureBytes(hex); - if (bytes.length !== 64) - throw new TypeError(`SchnorrSignature.fromHex: expected 64 bytes, not ${bytes.length}`); - const r = bytesToNumber(bytes.subarray(0, 32)); - const s = bytesToNumber(bytes.subarray(32, 64)); - return new SchnorrSignature(r, s); - } - assertValidity() { - const { r, s } = this; - if (!isValidFieldElement(r) || !isWithinCurveOrder(s)) - throw new Error('Invalid signature'); - } - toHex() { - return numTo32bStr(this.r) + numTo32bStr(this.s); - } - toRawBytes() { - return hexToBytes(this.toHex()); - } - } - function schnorrGetPublicKey(privateKey) { - return Point.fromPrivateKey(privateKey).toRawX(); - } - function initSchnorrSigArgs(message, privateKey, auxRand) { - if (message == null) - throw new TypeError(`sign: Expected valid message, not "${message}"`); - const m = ensureBytes(message); - const d0 = normalizePrivateKey(privateKey); - const rand = ensureBytes(auxRand); - if (rand.length !== 32) - throw new TypeError('sign: Expected 32 bytes of aux randomness'); - const P = Point.fromPrivateKey(d0); - const px = P.toRawX(); - const d = hasEvenY(P) ? d0 : CURVE.n - d0; - return { m, P, px, d, rand }; - } - function initSchnorrNonce(d, t0h) { - return numTo32b(d ^ bytesToNumber(t0h)); - } - function finalizeSchnorrNonce(k0h) { - const k0 = mod(bytesToNumber(k0h), CURVE.n); - if (k0 === _0n) - throw new Error('sign: Creation of signature failed. k is zero'); - const R = Point.fromPrivateKey(k0); - const rx = R.toRawX(); - const k = hasEvenY(R) ? k0 : CURVE.n - k0; - return { R, rx, k }; - } - function finalizeSchnorrSig(R, k, e, d) { - return new SchnorrSignature(R.x, mod(k + e * d, CURVE.n)).toRawBytes(); - } - async function schnorrSign(message, privateKey, auxRand = utils.randomBytes()) { - const { m, px, d, rand } = initSchnorrSigArgs(message, privateKey, auxRand); - const t = initSchnorrNonce(d, await utils.taggedHash(TAGS.aux, rand)); - const { R, rx, k } = finalizeSchnorrNonce(await utils.taggedHash(TAGS.nonce, t, px, m)); - const e = finalizeSchnorrChallenge(await utils.taggedHash(TAGS.challenge, rx, px, m)); - const sig = finalizeSchnorrSig(R, k, e, d); - const isValid = await schnorrVerify(sig, m, px); - if (!isValid) - throw new Error('sign: Invalid signature produced'); - return sig; - } - function schnorrSignSync(message, privateKey, auxRand = utils.randomBytes()) { - const { m, px, d, rand } = initSchnorrSigArgs(message, privateKey, auxRand); - const t = initSchnorrNonce(d, utils.taggedHashSync(TAGS.aux, rand)); - const { R, rx, k } = finalizeSchnorrNonce(utils.taggedHashSync(TAGS.nonce, t, px, m)); - const e = finalizeSchnorrChallenge(utils.taggedHashSync(TAGS.challenge, rx, px, m)); - const sig = finalizeSchnorrSig(R, k, e, d); - const isValid = schnorrVerifySync(sig, m, px); - if (!isValid) - throw new Error('sign: Invalid signature produced'); - return sig; - } - function initSchnorrVerify(signature, message, publicKey) { - const raw = signature instanceof SchnorrSignature; - const sig = raw ? signature : SchnorrSignature.fromHex(signature); - if (raw) - sig.assertValidity(); - return { - ...sig, - m: ensureBytes(message), - P: normalizePublicKey(publicKey), - }; - } - function finalizeSchnorrVerify(r, P, s, e) { - const R = Point.BASE.multiplyAndAddUnsafe(P, normalizePrivateKey(s), mod(-e, CURVE.n)); - if (!R || !hasEvenY(R) || R.x !== r) - return false; - return true; - } - async function schnorrVerify(signature, message, publicKey) { - try { - const { r, s, m, P } = initSchnorrVerify(signature, message, publicKey); - const e = finalizeSchnorrChallenge(await utils.taggedHash(TAGS.challenge, numTo32b(r), P.toRawX(), m)); - return finalizeSchnorrVerify(r, P, s, e); - } - catch (error) { - return false; - } - } - function schnorrVerifySync(signature, message, publicKey) { - try { - const { r, s, m, P } = initSchnorrVerify(signature, message, publicKey); - const e = finalizeSchnorrChallenge(utils.taggedHashSync(TAGS.challenge, numTo32b(r), P.toRawX(), m)); - return finalizeSchnorrVerify(r, P, s, e); - } - catch (error) { - return false; - } - } - const schnorr = { - Signature: SchnorrSignature, - getPublicKey: schnorrGetPublicKey, - sign: schnorrSign, - verify: schnorrVerify, - signSync: schnorrSignSync, - verifySync: schnorrVerifySync, - }; - Point.BASE._setWindowSize(8); - const crypto = { - node: nodeCrypto, - web: typeof self === 'object' && 'crypto' in self ? self.crypto : undefined, - }; - const TAGS = { - challenge: 'BIP0340/challenge', - aux: 'BIP0340/aux', - nonce: 'BIP0340/nonce', - }; - const TAGGED_HASH_PREFIXES = {}; - const utils = { - isValidPrivateKey(privateKey) { - try { - normalizePrivateKey(privateKey); - return true; - } - catch (error) { - return false; - } - }, - privateAdd: (privateKey, tweak) => { - const p = normalizePrivateKey(privateKey); - const t = normalizePrivateKey(tweak); - return numTo32b(mod(p + t, CURVE.n)); - }, - privateNegate: (privateKey) => { - const p = normalizePrivateKey(privateKey); - return numTo32b(CURVE.n - p); - }, - pointAddScalar: (p, tweak, isCompressed) => { - const P = Point.fromHex(p); - const t = normalizePrivateKey(tweak); - const Q = Point.BASE.multiplyAndAddUnsafe(P, t, _1n); - if (!Q) - throw new Error('Tweaked point at infinity'); - return Q.toRawBytes(isCompressed); - }, - pointMultiply: (p, tweak, isCompressed) => { - const P = Point.fromHex(p); - const t = bytesToNumber(ensureBytes(tweak)); - return P.multiply(t).toRawBytes(isCompressed); - }, - hashToPrivateKey: (hash) => { - hash = ensureBytes(hash); - if (hash.length < 40 || hash.length > 1024) - throw new Error('Expected 40-1024 bytes of private key as per FIPS 186'); - const num = mod(bytesToNumber(hash), CURVE.n - _1n) + _1n; - return numTo32b(num); - }, - randomBytes: (bytesLength = 32) => { - if (crypto.web) { - return crypto.web.getRandomValues(new Uint8Array(bytesLength)); - } - else if (crypto.node) { - const { randomBytes } = crypto.node; - return Uint8Array.from(randomBytes(bytesLength)); - } - else { - throw new Error("The environment doesn't have randomBytes function"); - } - }, - randomPrivateKey: () => { - return utils.hashToPrivateKey(utils.randomBytes(40)); - }, - bytesToHex, - hexToBytes, - concatBytes, - mod, - invert, - sha256: async (...messages) => { - if (crypto.web) { - const buffer = await crypto.web.subtle.digest('SHA-256', concatBytes(...messages)); - return new Uint8Array(buffer); - } - else if (crypto.node) { - const { createHash } = crypto.node; - const hash = createHash('sha256'); - messages.forEach((m) => hash.update(m)); - return Uint8Array.from(hash.digest()); - } - else { - throw new Error("The environment doesn't have sha256 function"); - } - }, - hmacSha256: async (key, ...messages) => { - if (crypto.web) { - const ckey = await crypto.web.subtle.importKey('raw', key, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']); - const message = concatBytes(...messages); - const buffer = await crypto.web.subtle.sign('HMAC', ckey, message); - return new Uint8Array(buffer); - } - else if (crypto.node) { - const { createHmac } = crypto.node; - const hash = createHmac('sha256', key); - messages.forEach((m) => hash.update(m)); - return Uint8Array.from(hash.digest()); - } - else { - throw new Error("The environment doesn't have hmac-sha256 function"); - } - }, - sha256Sync: undefined, - hmacSha256Sync: undefined, - taggedHash: async (tag, ...messages) => { - let tagP = TAGGED_HASH_PREFIXES[tag]; - if (tagP === undefined) { - const tagH = await utils.sha256(Uint8Array.from(tag, (c) => c.charCodeAt(0))); - tagP = concatBytes(tagH, tagH); - TAGGED_HASH_PREFIXES[tag] = tagP; - } - return utils.sha256(tagP, ...messages); - }, - taggedHashSync: (tag, ...messages) => { - if (typeof utils.sha256Sync !== 'function') - throw new Error('utils.sha256Sync is undefined, you need to set it'); - let tagP = TAGGED_HASH_PREFIXES[tag]; - if (tagP === undefined) { - const tagH = utils.sha256Sync(Uint8Array.from(tag, (c) => c.charCodeAt(0))); - tagP = concatBytes(tagH, tagH); - TAGGED_HASH_PREFIXES[tag] = tagP; - } - return utils.sha256Sync(tagP, ...messages); - }, - precompute(windowSize = 8, point = Point.BASE) { - const cached = point === Point.BASE ? point : new Point(point.x, point.y); - cached._setWindowSize(windowSize); - cached.multiply(_3n); - return cached; - }, - }; - - exports.CURVE = CURVE; - exports.Point = Point; - exports.Signature = Signature; - exports.getPublicKey = getPublicKey; - exports.getSharedSecret = getSharedSecret; - exports.recoverPublicKey = recoverPublicKey; - exports.schnorr = schnorr; - exports.sign = sign; - exports.signSync = signSync; - exports.utils = utils; - exports.verify = verify; - - Object.defineProperty(exports, '__esModule', { value: true }); - -})); diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js deleted file mode 100644 index 880d6b30..00000000 --- a/lnbits/extensions/watchonly/static/js/index.js +++ /dev/null @@ -1,435 +0,0 @@ -const watchOnly = async () => { - Vue.component(VueQrcode.name, VueQrcode) - - await walletConfig('static/components/wallet-config/wallet-config.html') - await walletList('static/components/wallet-list/wallet-list.html') - await addressList('static/components/address-list/address-list.html') - await history('static/components/history/history.html') - await utxoList('static/components/utxo-list/utxo-list.html') - await feeRate('static/components/fee-rate/fee-rate.html') - await seedInput('static/components/seed-input/seed-input.html') - await sendTo('static/components/send-to/send-to.html') - await payment('static/components/payment/payment.html') - await serialSigner('static/components/serial-signer/serial-signer.html') - await serialPortConfig( - 'static/components/serial-port-config/serial-port-config.html' - ) - - Vue.filter('reverse', function (value) { - // slice to make a copy of array, then reverse the copy - return value.slice().reverse() - }) - - new Vue({ - el: '#vue', - mixins: [windowMixin], - data: function () { - return { - scan: { - scanning: false, - scanCount: 0, - scanIndex: 0 - }, - - currentAddress: null, - - tab: 'addresses', - - config: {sats_denominated: true}, - - qrCodeDialog: { - show: false, - data: null - }, - ...tables, - ...tableData, - - walletAccounts: [], - addresses: [], - history: [], - historyFilter: '', - - showAddress: false, - addressNote: '', - showPayment: false, - fetchedUtxos: false, - utxosFilter: '', - network: null, - - showEnterSignedPsbt: false, - signedBase64Psbt: null - } - }, - computed: { - mempoolHostname: function () { - if (!this.config.isLoaded) return - let hostname = new URL(this.config.mempool_endpoint).hostname - if (this.config.network === 'Testnet') { - hostname += '/testnet' - } - return hostname - } - }, - - methods: { - updateAmountForAddress: async function (addressData, amount = 0) { - try { - const wallet = this.g.user.wallets[0] - addressData.amount = amount - if (!addressData.isChange) { - const addressWallet = this.walletAccounts.find( - w => w.id === addressData.wallet - ) - if ( - addressWallet && - addressWallet.address_no < addressData.addressIndex - ) { - addressWallet.address_no = addressData.addressIndex - } - } - - // todo: account deleted - await LNbits.api.request( - 'PUT', - `/watchonly/api/v1/address/${addressData.id}`, - wallet.adminkey, - {amount} - ) - } catch (err) { - addressData.error = 'Failed to refresh amount for address' - this.$q.notify({ - type: 'warning', - message: `Failed to refresh amount for address ${addressData.address}`, - timeout: 10000 - }) - LNbits.utils.notifyApiError(err) - } - }, - updateNoteForAddress: async function ({addressId, note}) { - try { - const wallet = this.g.user.wallets[0] - await LNbits.api.request( - 'PUT', - `/watchonly/api/v1/address/${addressId}`, - wallet.adminkey, - {note} - ) - const updatedAddress = - this.addresses.find(a => a.id === addressId) || {} - updatedAddress.note = note - } catch (err) { - LNbits.utils.notifyApiError(err) - } - }, - - //################### ADDRESS HISTORY ################### - addressHistoryFromTxs: function (addressData, txs) { - const addressHistory = [] - txs.forEach(tx => { - const sent = tx.vin - .filter( - vin => vin.prevout.scriptpubkey_address === addressData.address - ) - .map(vin => mapInputToSentHistory(tx, addressData, vin)) - - const received = tx.vout - .filter(vout => vout.scriptpubkey_address === addressData.address) - .map(vout => mapOutputToReceiveHistory(tx, addressData, vout)) - addressHistory.push(...sent, ...received) - }) - return addressHistory - }, - - markSameTxAddressHistory: function () { - this.history - .filter(s => s.sent) - .forEach((el, i, arr) => { - if (el.isSubItem) return - - const sameTxItems = arr.slice(i + 1).filter(e => e.txId === el.txId) - if (!sameTxItems.length) return - sameTxItems.forEach(e => { - e.isSubItem = true - }) - - el.totalAmount = - el.amount + sameTxItems.reduce((t, e) => (t += e.amount || 0), 0) - el.sameTxItems = sameTxItems - }) - }, - - //################### PAYMENT ################### - - initPaymentData: async function () { - if (!this.payment.show) return - await this.refreshAddresses() - }, - - goToPaymentView: async function () { - this.showPayment = true - await this.initPaymentData() - }, - - //################### PSBT ################### - - updateSignedPsbt: async function (psbtBase64) { - this.$refs.paymentRef.updateSignedPsbt(psbtBase64) - }, - - showEnterSignedPsbtDialog: function () { - this.signedBase64Psbt = '' - this.showEnterSignedPsbt = true - }, - - checkPsbt: function () { - this.$refs.paymentRef.updateSignedPsbt(this.signedBase64Psbt) - }, - - //################### UTXOs ################### - scanAllAddresses: async function () { - await this.refreshAddresses() - this.history = [] - let addresses = this.addresses - this.utxos.data = [] - this.utxos.total = 0 - // Loop while new funds are found on the gap adresses. - // Use 1000 limit as a safety check (scan 20 000 addresses max) - for (let i = 0; i < 1000 && addresses.length; i++) { - await this.updateUtxosForAddresses(addresses) - const oldAddresses = this.addresses.slice() - await this.refreshAddresses() - const newAddresses = this.addresses.slice() - // check if gap addresses have been extended - addresses = newAddresses.filter( - newAddr => !oldAddresses.find(oldAddr => oldAddr.id === newAddr.id) - ) - if (addresses.length) { - this.$q.notify({ - type: 'positive', - message: 'Funds found! Scanning for more...', - timeout: 10000 - }) - } - } - }, - scanAddressWithAmount: async function () { - this.utxos.data = [] - this.utxos.total = 0 - this.history = [] - const addresses = this.addresses.filter(a => a.hasActivity) - await this.updateUtxosForAddresses(addresses) - }, - scanAddress: async function (addressData) { - this.updateUtxosForAddresses([addressData]) - this.$q.notify({ - type: 'positive', - message: 'Address Rescanned', - timeout: 10000 - }) - }, - refreshAddresses: async function () { - if (!this.walletAccounts) return - this.addresses = [] - for (const {id, type} of this.walletAccounts) { - const newAddresses = await this.getAddressesForWallet(id) - const uniqueAddresses = newAddresses.filter( - newAddr => !this.addresses.find(a => a.address === newAddr.address) - ) - - const lastActiveAddress = - uniqueAddresses.filter(a => !a.isChange && a.hasActivity).pop() || - {} - - uniqueAddresses.forEach(a => { - a.expanded = false - a.accountType = type - a.gapLimitExceeded = - !a.isChange && - a.addressIndex > - lastActiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT - }) - this.addresses.push(...uniqueAddresses) - } - this.$emit('update:addresses', this.addresses) - }, - getAddressesForWallet: async function (walletId) { - try { - const {data} = await LNbits.api.request( - 'GET', - '/watchonly/api/v1/addresses/' + walletId, - this.g.user.wallets[0].inkey - ) - return data.map(mapAddressesData) - } catch (error) { - this.$q.notify({ - type: 'warning', - message: `Failed to fetch addresses for wallet with id ${walletId}.`, - timeout: 10000 - }) - LNbits.utils.notifyApiError(error) - } - return [] - }, - updateUtxosForAddresses: async function (addresses = []) { - this.scan = {scanning: true, scanCount: addresses.length, scanIndex: 0} - - try { - for (addrData of addresses) { - const addressHistory = await this.getAddressTxsDelayed(addrData) - // remove old entries - this.history = this.history.filter( - h => h.address !== addrData.address - ) - - // add new entries - this.history.push(...addressHistory) - this.history.sort((a, b) => (!a.height ? -1 : b.height - a.height)) - this.markSameTxAddressHistory() - - if (addressHistory.length) { - // search only if it ever had any activity - const utxos = await this.getAddressTxsUtxoDelayed( - addrData.address - ) - this.updateUtxosForAddress(addrData, utxos) - } - - this.scan.scanIndex++ - } - } catch (error) { - console.error(error) - this.$q.notify({ - type: 'warning', - message: 'Failed to scan addresses', - timeout: 10000 - }) - } finally { - this.scan.scanning = false - } - }, - updateUtxosForAddress: function (addressData, utxos = []) { - const wallet = - this.walletAccounts.find(w => w.id === addressData.wallet) || {} - - const newUtxos = utxos.map(utxo => - mapAddressDataToUtxo(wallet, addressData, utxo) - ) - // remove old utxos - this.utxos.data = this.utxos.data.filter( - u => u.address !== addressData.address - ) - // add new utxos - this.utxos.data.push(...newUtxos) - if (utxos.length) { - this.utxos.data.sort((a, b) => b.sort - a.sort) - this.utxos.total = this.utxos.data.reduce( - (total, y) => (total += y?.amount || 0), - 0 - ) - } - const addressTotal = utxos.reduce( - (total, y) => (total += y?.value || 0), - 0 - ) - this.updateAmountForAddress(addressData, addressTotal) - }, - - //################### MEMPOOL API ################### - getAddressTxsDelayed: async function (addrData) { - const accounts = this.walletAccounts - const { - bitcoin: {addresses: addressesAPI} - } = mempoolJS({ - hostname: this.mempoolHostname - }) - const fn = async () => { - if (!accounts.find(w => w.id === addrData.wallet)) return [] - return addressesAPI.getAddressTxs({ - address: addrData.address - }) - } - const addressTxs = await retryWithDelay(fn) - return this.addressHistoryFromTxs(addrData, addressTxs) - }, - - getAddressTxsUtxoDelayed: async function (address) { - const endpoint = this.mempoolHostname - const { - bitcoin: {addresses: addressesAPI} - } = mempoolJS({ - hostname: endpoint - }) - - const fn = async () => { - if (endpoint !== this.mempoolHostname) return [] - return addressesAPI.getAddressTxsUtxo({ - address - }) - } - return retryWithDelay(fn) - }, - - //################### OTHER ################### - - openQrCodeDialog: function (addressData) { - this.currentAddress = addressData - this.addressNote = addressData.note || '' - this.showAddress = true - }, - searchInTab: function ({tab, value}) { - this.tab = tab - this[`${tab}Filter`] = value - }, - - updateAccounts: async function (accounts) { - this.walletAccounts = accounts - await this.refreshAddresses() - await this.scanAddressWithAmount() - }, - showAddressDetails: function (addressData) { - this.openQrCodeDialog(addressData) - }, - showAddressDetailsWithConfirmation: function ({addressData, wallet}) { - this.showAddressDetails(addressData) - if (this.$refs.serialSigner.isConnected()) { - if (this.$refs.serialSigner.isAuthenticated()) { - if (wallet.meta?.accountPath) { - const branchIndex = addressData.isChange ? 1 : 0 - const path = - wallet.meta.accountPath + - `/${branchIndex}/${addressData.addressIndex}` - this.$refs.serialSigner.hwwShowAddress(path, addressData.address) - } - } else { - this.$q.notify({ - type: 'warning', - message: 'Please login in order to confirm address on device', - timeout: 10000 - }) - } - } - }, - initUtxos: function (addresses) { - if (!this.fetchedUtxos && addresses.length) { - this.fetchedUtxos = true - this.addresses = addresses - this.scanAddressWithAmount() - } - }, - handleBroadcastSuccess: async function (txId) { - this.tab = 'history' - this.searchInTab({tab: 'history', value: txId}) - this.showPayment = false - await this.refreshAddresses() - await this.scanAddressWithAmount() - } - }, - created: async function () { - if (this.g.user.wallets.length) { - await this.refreshAddresses() - await this.scanAddressWithAmount() - } - } - }) -} -watchOnly() diff --git a/lnbits/extensions/watchonly/static/js/map.js b/lnbits/extensions/watchonly/static/js/map.js deleted file mode 100644 index 81093936..00000000 --- a/lnbits/extensions/watchonly/static/js/map.js +++ /dev/null @@ -1,81 +0,0 @@ -const mapAddressesData = a => ({ - id: a.id, - address: a.address, - amount: a.amount, - wallet: a.wallet, - note: a.note, - - isChange: a.branch_index === 1, - addressIndex: a.address_index, - hasActivity: a.has_activity -}) - -const mapInputToSentHistory = (tx, addressData, vin) => ({ - sent: true, - txId: tx.txid, - address: addressData.address, - isChange: addressData.isChange, - amount: vin.prevout.value, - date: blockTimeToDate(tx.status.block_time), - height: tx.status.block_height, - confirmed: tx.status.confirmed, - fee: tx.fee, - expanded: false -}) - -const mapOutputToReceiveHistory = (tx, addressData, vout) => ({ - received: true, - txId: tx.txid, - address: addressData.address, - isChange: addressData.isChange, - amount: vout.value, - date: blockTimeToDate(tx.status.block_time), - height: tx.status.block_height, - confirmed: tx.status.confirmed, - fee: tx.fee, - expanded: false -}) - -const mapUtxoToPsbtInput = utxo => ({ - tx_id: utxo.txId, - vout: utxo.vout, - amount: utxo.amount, - address: utxo.address, - branch_index: utxo.isChange ? 1 : 0, - address_index: utxo.addressIndex, - wallet: utxo.wallet, - accountType: utxo.accountType, - txHex: '' -}) - -const mapAddressDataToUtxo = (wallet, addressData, utxo) => ({ - id: addressData.id, - address: addressData.address, - isChange: addressData.isChange, - addressIndex: addressData.addressIndex, - wallet: addressData.wallet, - accountType: addressData.accountType, - masterpubFingerprint: wallet.fingerprint, - txId: utxo.txid, - vout: utxo.vout, - confirmed: utxo.status.confirmed, - amount: utxo.value, - date: blockTimeToDate(utxo.status?.block_time), - sort: utxo.status?.block_time, - expanded: false, - selected: false -}) - -const mapWalletAccount = function (o) { - return Object.assign({}, o, { - date: o.time - ? Quasar.utils.date.formatDate( - new Date(o.time * 1000), - 'YYYY-MM-DD HH:mm' - ) - : '', - meta: o.meta ? JSON.parse(o.meta) : null, - label: o.title, - expanded: false - }) -} diff --git a/lnbits/extensions/watchonly/static/js/tables.js b/lnbits/extensions/watchonly/static/js/tables.js deleted file mode 100644 index f437bcd5..00000000 --- a/lnbits/extensions/watchonly/static/js/tables.js +++ /dev/null @@ -1,61 +0,0 @@ -const tables = { - summaryTable: { - columns: [ - { - name: 'totalInputs', - align: 'center', - label: 'Selected Amount' - }, - { - name: 'totalOutputs', - align: 'center', - label: 'Payed Amount' - }, - { - name: 'fees', - align: 'center', - label: 'Fees' - }, - { - name: 'change', - align: 'center', - label: 'Change' - } - ] - } -} - -const tableData = { - utxos: { - data: [], - total: 0 - }, - payment: { - fee: 0, - txSize: 0, - tx: null, - psbtBase64: '', - psbtBase64Signed: '', - signedTx: null, - signedTxHex: null, - sentTxId: null, - - signModes: [ - { - label: 'Serial Port Device', - value: 'serial-port' - }, - { - label: 'Animated QR', - value: 'animated-qr', - disable: true - } - ], - signMode: '', - show: false, - showAdvanced: false - }, - summary: { - data: [{totalInputs: 0, totalOutputs: 0, fees: 0, change: 0}] - } -} diff --git a/lnbits/extensions/watchonly/static/js/utils.js b/lnbits/extensions/watchonly/static/js/utils.js deleted file mode 100644 index c73dd9c0..00000000 --- a/lnbits/extensions/watchonly/static/js/utils.js +++ /dev/null @@ -1,210 +0,0 @@ -const PSBT_BASE64_PREFIX = 'cHNidP8' - -const COMMAND_PING = '/ping' -const COMMAND_PASSWORD = '/password' -const COMMAND_PASSWORD_CLEAR = '/password-clear' -const COMMAND_ADDRESS = '/address' -const COMMAND_SEND_PSBT = '/psbt' -const COMMAND_SIGN_PSBT = '/sign' -const COMMAND_HELP = '/help' -const COMMAND_WIPE = '/wipe' -const COMMAND_SEED = '/seed' -const COMMAND_RESTORE = '/restore' -const COMMAND_CONFIRM_NEXT = '/confirm-next' -const COMMAND_CANCEL = '/cancel' -const COMMAND_XPUB = '/xpub' -const COMMAND_PAIR = '/pair' -const COMMAND_LOG = '/log' -const COMMAND_CHECK_PAIRING = '/check-pairing' - -const DEFAULT_RECEIVE_GAP_LIMIT = 20 -const PAIRING_CONTROL_TEXT = 'lnbits' - -const HWW_DEFAULT_CONFIG = Object.freeze({ - name: '', - buttonOnePin: '', - buttonTwoPin: '', - baudRate: 9600, - bufferSize: 255, - dataBits: 8, - flowControl: 'none', - parity: 'none', - stopBits: 1 -}) - -const blockTimeToDate = blockTime => - blockTime ? moment(blockTime * 1000).format('LLL') : '' - -const currentDateTime = () => moment().format('LLL') - -const sleep = ms => new Promise(r => setTimeout(r, ms)) - -const retryWithDelay = async function (fn, retryCount = 0) { - try { - await sleep(25) - // Do not return the call directly, use result. - // Otherwise the error will not be cought in this try-catch block. - const result = await fn() - return result - } catch (err) { - if (retryCount > 100) throw err - await sleep((retryCount + 1) * 1000) - return retryWithDelay(fn, retryCount + 1) - } -} - -const txSize = tx => { - // https://bitcoinops.org/en/tools/calc-size/ - // overhead size - const nVersion = 4 - const inCount = 1 - const outCount = 1 - const nlockTime = 4 - const hasSegwit = !!tx.inputs.find(inp => - ['p2wsh', 'p2wpkh', 'p2tr'].includes(inp.accountType) - ) - const segwitFlag = hasSegwit ? 0.5 : 0 - const overheadSize = nVersion + inCount + outCount + nlockTime + segwitFlag - - // inputs size - const outpoint = 36 // txId plus vout index number - const scriptSigLength = 1 - const nSequence = 4 - const inputsSize = tx.inputs.reduce((t, inp) => { - const scriptSig = - inp.accountType === 'p2pkh' ? 107 : inp.accountType === 'p2sh' ? 254 : 0 - const witnessItemCount = hasSegwit ? 0.25 : 0 - const witnessItems = - inp.accountType === 'p2wpkh' - ? 27 - : inp.accountType === 'p2wsh' - ? 63.5 - : inp.accountType === 'p2tr' - ? 16.5 - : 0 - t += - outpoint + - scriptSigLength + - nSequence + - scriptSig + - witnessItemCount + - witnessItems - return t - }, 0) - - // outputs size - const nValue = 8 - const scriptPubKeyLength = 1 - - const outputsSize = tx.outputs.reduce((t, out) => { - const type = guessAddressType(out.address) - - const scriptPubKey = - type === 'p2pkh' - ? 25 - : type === 'p2wpkh' - ? 22 - : type === 'p2sh' - ? 23 - : type === 'p2wsh' - ? 34 - : 34 // default to the largest size (p2tr included) - t += nValue + scriptPubKeyLength + scriptPubKey - return t - }, 0) - - return overheadSize + inputsSize + outputsSize -} -const guessAddressType = (a = '') => { - if (a.startsWith('1') || a.startsWith('n')) return 'p2pkh' - if (a.startsWith('3') || a.startsWith('2')) return 'p2sh' - if (a.startsWith('bc1q') || a.startsWith('tb1q')) - return a.length === 42 ? 'p2wpkh' : 'p2wsh' - if (a.startsWith('bc1p') || a.startsWith('tb1p')) return 'p2tr' -} - -const ACCOUNT_TYPES = { - p2tr: 'Taproot, BIP86, P2TR, Bech32m', - p2wpkh: 'SegWit, BIP84, P2WPKH, Bech32', - p2sh: 'BIP49, P2SH-P2WPKH, Base58', - p2pkh: 'Legacy, BIP44, P2PKH, Base58' -} - -const getAccountDescription = type => ACCOUNT_TYPES[type] || 'nonstandard' - -const readFromSerialPort = reader => { - let partialChunk - let fulliness = [] - - const readStringUntil = async (separator = '\n') => { - if (fulliness.length) return fulliness.shift().trim() - const chunks = [] - if (partialChunk) { - // leftovers from previous read - chunks.push(partialChunk) - partialChunk = undefined - } - while (true) { - const {value, done} = await reader.read() - if (value) { - const values = value.split(separator) - // found one or more separators - if (values.length > 1) { - chunks.push(values.shift()) // first element - partialChunk = values.pop() // last element - fulliness = values // full lines - return {value: chunks.join('').trim(), done: false} - } - chunks.push(value) - } - if (done) return {value: chunks.join('').trim(), done: true} - } - } - return readStringUntil -} - -function satOrBtc(val, showUnit = true, showSats = false) { - const value = showSats - ? LNbits.utils.formatSat(val) - : val == 0 - ? 0.0 - : (val / 100000000).toFixed(8) - if (!showUnit) return value - return showSats ? value + ' sat' : value + ' BTC' -} - -function loadTemplateAsync(path) { - const result = new Promise(resolve => { - const xhttp = new XMLHttpRequest() - - xhttp.onreadystatechange = function () { - if (this.readyState == 4) { - if (this.status == 200) resolve(this.responseText) - - if (this.status == 404) resolve(`
Page not found: ${path}
`) - } - } - - xhttp.open('GET', path, true) - xhttp.send() - }) - - return result -} - -function findAccountPathIssues(path = '') { - const p = path.split('/') - if (p[0] !== 'm') return "Path must start with 'm/'" - for (let i = 1; i < p.length; i++) { - if (p[i].endsWith('')) p[i] = p[i].substring(0, p[i].length - 1) - if (isNaN(p[i])) return `${p[i]} is not a valid value` - } -} - -function asciiToUint8Array(str) { - var chars = [] - for (var i = 0; i < str.length; ++i) { - chars.push(str.charCodeAt(i)) - } - return new Uint8Array(chars) -} diff --git a/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html b/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html deleted file mode 100644 index 0365df44..00000000 --- a/lnbits/extensions/watchonly/templates/watchonly/_api_docs.html +++ /dev/null @@ -1,65 +0,0 @@ - - -

- Onchain Wallet (watch-only) extension uses mempool.space
- For use with "account Extended Public Key" - https://iancoleman.io/bip39/ -
- Flash binaries - directly from browser - -
Created by - Ben Arc, - Tiago Vasconcelos, - motorina0 - (using, - Embit
) -
-
- Swagger REST API Documentation -

-
-
diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html deleted file mode 100644 index 84be5c81..00000000 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ /dev/null @@ -1,316 +0,0 @@ -{% extends "base.html" %} {% from "macros.jinja" import window_vars with context -%} {% block page %} -
-
- - - - - - - - {% raw %} - -
-
- Scan Blockchain -
-
- -
-
- - - - - New Payment - Create a new payment by selecting Inputs and - Outputs - - - - - From Signed PSBT - Paste a signed PSBT - - - - - - Back -
-
- -
- -
-
- - - - - - - - - - - - - - - - - - - - - - -
- - -
-
- - {% endraw %} - -
- - -
- {{SITE_TITLE}} Onchain Wallet (watch-only) Extension - (v0.3) -
-
- - - {% include "watchonly/_api_docs.html" %} - -
-
- {% raw %} - - -
Address Details
-
- - - - -

- - - {{ currentAddress.address }} - - -

-

- -

-
- Gap limit of 20 addresses exceeded. Other wallets might not detect - funds at this address. -
-
- Save Note - Close -
-
-
-
- - - -
Enter the Signed PSBT
-
- -

- -

- -
- Check PSBT - Close -
-
-
-
- - {% endraw %} -
- -{% endblock %} {% block scripts %} {{ window_vars(user) }} - - - - - - - - - - - - - - - - - - - - - - - - - -{% endblock %} diff --git a/lnbits/extensions/watchonly/views.py b/lnbits/extensions/watchonly/views.py deleted file mode 100644 index 8cebc6cc..00000000 --- a/lnbits/extensions/watchonly/views.py +++ /dev/null @@ -1,17 +0,0 @@ -from fastapi import Depends, Request -from fastapi.templating import Jinja2Templates -from starlette.responses import HTMLResponse - -from lnbits.core.models import User -from lnbits.decorators import check_user_exists - -from . import watchonly_ext, watchonly_renderer - -templates = Jinja2Templates(directory="templates") - - -@watchonly_ext.get("/", response_class=HTMLResponse) -async def index(request: Request, user: User = Depends(check_user_exists)): - return watchonly_renderer().TemplateResponse( - "watchonly/index.html", {"request": request, "user": user.dict()} - ) diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py deleted file mode 100644 index e0c427fe..00000000 --- a/lnbits/extensions/watchonly/views_api.py +++ /dev/null @@ -1,385 +0,0 @@ -import json -from http import HTTPStatus -from typing import List - -import httpx -from embit import finalizer, script -from embit.ec import PublicKey -from embit.networks import NETWORKS -from embit.psbt import PSBT, DerivationPath -from embit.transaction import Transaction, TransactionInput, TransactionOutput -from fastapi import Depends, HTTPException, Query, Request - -from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key - -from . import watchonly_ext -from .crud import ( - create_config, - create_fresh_addresses, - create_watch_wallet, - delete_addresses_for_wallet, - delete_watch_wallet, - get_addresses, - get_config, - get_fresh_address, - get_watch_wallet, - get_watch_wallets, - update_address, - update_config, - update_watch_wallet, -) -from .helpers import parse_key -from .models import ( - Config, - CreatePsbt, - CreateWallet, - ExtractPsbt, - SerializedTransaction, - SignedTransaction, - WalletAccount, -) - -###################WALLETS############################# - - -@watchonly_ext.get("/api/v1/wallet") -async def api_wallets_retrieve( - network: str = Query("Mainnet"), wallet: WalletTypeInfo = Depends(get_key_type) -): - - try: - return [ - wallet.dict() - for wallet in await get_watch_wallets(wallet.wallet.user, network) - ] - except: - return [] - - -@watchonly_ext.get("/api/v1/wallet/{wallet_id}", dependencies=[Depends(get_key_type)]) -async def api_wallet_retrieve(wallet_id: str): - w_wallet = await get_watch_wallet(wallet_id) - - if not w_wallet: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist." - ) - - return w_wallet.dict() - - -@watchonly_ext.post("/api/v1/wallet") -async def api_wallet_create_or_update( - data: CreateWallet, w: WalletTypeInfo = Depends(require_admin_key) -): - try: - descriptor, network = parse_key(data.masterpub) - assert network - if data.network != network["name"]: - raise ValueError( - "Account network error. This account is for '{}'".format( - network["name"] - ) - ) - - new_wallet = WalletAccount( - id="none", - masterpub=data.masterpub, - fingerprint=descriptor.keys[0].fingerprint.hex(), - type=descriptor.scriptpubkey_type(), - title=data.title, - address_no=-1, # so fresh address on empty wallet can get address with index 0 - balance=0, - network=network["name"], - meta=data.meta, - ) - - wallets = await get_watch_wallets(w.wallet.user, network["name"]) - existing_wallet = next( - ( - ew - for ew in wallets - if ew.fingerprint == new_wallet.fingerprint - and ew.network == new_wallet.network - and ew.masterpub == new_wallet.masterpub - ), - None, - ) - if existing_wallet: - raise ValueError( - "Account '{}' has the same master pulic key".format( - existing_wallet.title - ) - ) - - wallet = await create_watch_wallet(w.wallet.user, new_wallet) - - await api_get_addresses(wallet.id, w) - except Exception as e: - raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) - - config = await get_config(w.wallet.user) - if not config: - await create_config(user=w.wallet.user) - return wallet.dict() - - -@watchonly_ext.delete( - "/api/v1/wallet/{wallet_id}", dependencies=[Depends(require_admin_key)] -) -async def api_wallet_delete(wallet_id: str): - wallet = await get_watch_wallet(wallet_id) - - if not wallet: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist." - ) - - await delete_watch_wallet(wallet_id) - await delete_addresses_for_wallet(wallet_id) - - return "", HTTPStatus.NO_CONTENT - - -#############################ADDRESSES########################## - - -@watchonly_ext.get("/api/v1/address/{wallet_id}", dependencies=[Depends(get_key_type)]) -async def api_fresh_address(wallet_id: str): - address = await get_fresh_address(wallet_id) - assert address - return address.dict() - - -@watchonly_ext.put("/api/v1/address/{id}", dependencies=[Depends(require_admin_key)]) -async def api_update_address(id: str, req: Request): - body = await req.json() - params = {} - # amout is only updated if the address has history - if "amount" in body: - params["amount"] = int(body["amount"]) - params["has_activity"] = True - - if "note" in body: - params["note"] = body["note"] - - address = await update_address(**params, id=id) - assert address - - wallet = ( - await get_watch_wallet(address.wallet) - if address.branch_index == 0 and address.amount != 0 - else None - ) - - if wallet and wallet.address_no < address.address_index: - await update_watch_wallet( - address.wallet, **{"address_no": address.address_index} - ) - return address - - -@watchonly_ext.get("/api/v1/addresses/{wallet_id}") -async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type)): - wallet = await get_watch_wallet(wallet_id) - if not wallet: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist." - ) - - addresses = await get_addresses(wallet_id) - config = await get_config(w.wallet.user) - assert config - - if not addresses: - await create_fresh_addresses(wallet_id, 0, config.receive_gap_limit) - await create_fresh_addresses(wallet_id, 0, config.change_gap_limit, True) - addresses = await get_addresses(wallet_id) - - receive_addresses = list(filter(lambda addr: addr.branch_index == 0, addresses)) - change_addresses = list(filter(lambda addr: addr.branch_index == 1, addresses)) - - last_receive_address = list( - filter(lambda addr: addr.has_activity, receive_addresses) - )[-1:] - last_change_address = list( - filter(lambda addr: addr.has_activity, change_addresses) - )[-1:] - - if last_receive_address: - current_index = receive_addresses[-1].address_index - address_index = last_receive_address[0].address_index - await create_fresh_addresses( - wallet_id, current_index + 1, address_index + config.receive_gap_limit + 1 - ) - - if last_change_address: - current_index = change_addresses[-1].address_index - address_index = last_change_address[0].address_index - await create_fresh_addresses( - wallet_id, - current_index + 1, - address_index + config.change_gap_limit + 1, - True, - ) - - addresses = await get_addresses(wallet_id) - return [address.dict() for address in addresses] - - -#############################PSBT########################## - - -@watchonly_ext.post("/api/v1/psbt", dependencies=[Depends(require_admin_key)]) -async def api_psbt_create(data: CreatePsbt): - try: - vin = [ - TransactionInput(bytes.fromhex(inp.tx_id), inp.vout) for inp in data.inputs - ] - vout = [ - TransactionOutput(out.amount, script.address_to_scriptpubkey(out.address)) - for out in data.outputs - ] - - descriptors = {} - for _, masterpub in enumerate(data.masterpubs): - descriptors[masterpub.id] = parse_key(masterpub.public_key) - - inputs_extra: List[dict] = [] - - for i, inp in enumerate(data.inputs): - bip32_derivations = {} - descriptor = descriptors[inp.wallet][0] - d = descriptor.derive(inp.address_index, inp.branch_index) - for k in d.keys: - bip32_derivations[PublicKey.parse(k.sec())] = DerivationPath( - k.origin.fingerprint, k.origin.derivation - ) - inputs_extra.append( - { - "bip32_derivations": bip32_derivations, - "non_witness_utxo": Transaction.from_string(inp.tx_hex), - } - ) - - tx = Transaction(vin=vin, vout=vout) - psbt = PSBT(tx) - - for i, inp_extra in enumerate(inputs_extra): - psbt.inputs[i].bip32_derivations = inp_extra["bip32_derivations"] - psbt.inputs[i].non_witness_utxo = inp_extra.get("non_witness_utxo", None) - - outputs_extra = [] - bip32_derivations = {} - for i, out in enumerate(data.outputs): - if out.branch_index == 1: - assert out.wallet - descriptor = descriptors[out.wallet][0] - d = descriptor.derive(out.address_index, out.branch_index) - for k in d.keys: - bip32_derivations[PublicKey.parse(k.sec())] = DerivationPath( - k.origin.fingerprint, k.origin.derivation - ) - outputs_extra.append({"bip32_derivations": bip32_derivations}) - - for i, out_extra in enumerate(outputs_extra): - psbt.outputs[i].bip32_derivations = out_extra["bip32_derivations"] - - return psbt.to_string() - - except Exception as e: - raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) - - -@watchonly_ext.put("/api/v1/psbt/utxos") -async def api_psbt_utxos_tx( - req: Request, w: WalletTypeInfo = Depends(require_admin_key) -): - """Extract previous unspent transaction outputs (tx_id, vout) from PSBT""" - - body = await req.json() - try: - psbt = PSBT.from_base64(body["psbtBase64"]) - res = [] - for _, inp in enumerate(psbt.inputs): - res.append({"tx_id": inp.txid.hex(), "vout": inp.vout}) - - return res - except Exception as e: - raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) - - -@watchonly_ext.put("/api/v1/psbt/extract", dependencies=[Depends(require_admin_key)]) -async def api_psbt_extract_tx(data: ExtractPsbt): - network = NETWORKS["main"] if data.network == "Mainnet" else NETWORKS["test"] - try: - psbt = PSBT.from_base64(data.psbtBase64) - for i, inp in enumerate(data.inputs): - psbt.inputs[i].non_witness_utxo = Transaction.from_string(inp.tx_hex) - - final_psbt = finalizer.finalize_psbt(psbt) - if not final_psbt: - raise ValueError("PSBT cannot be finalized!") - - tx_hex = final_psbt.to_string() - transaction = Transaction.from_string(tx_hex) - tx = { - "locktime": transaction.locktime, - "version": transaction.version, - "outputs": [], - "fee": psbt.fee(), - } - - for out in transaction.vout: - tx["outputs"].append( - {"amount": out.value, "address": out.script_pubkey.address(network)} - ) - signed_tx = SignedTransaction(tx_hex=tx_hex, tx_json=json.dumps(tx)) - return signed_tx.dict() - except Exception as e: - raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) - - -@watchonly_ext.post("/api/v1/tx") -async def api_tx_broadcast( - data: SerializedTransaction, w: WalletTypeInfo = Depends(require_admin_key) -): - try: - config = await get_config(w.wallet.user) - if not config: - raise ValueError( - "Cannot broadcast transaction. Mempool endpoint not defined!" - ) - - endpoint = ( - config.mempool_endpoint - if config.network == "Mainnet" - else config.mempool_endpoint + "/testnet" - ) - async with httpx.AsyncClient() as client: - r = await client.post(endpoint + "/api/tx", content=data.tx_hex) - r.raise_for_status() - tx_id = r.text - return tx_id - except Exception as e: - raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) - - -#############################CONFIG########################## - - -@watchonly_ext.put("/api/v1/config") -async def api_update_config( - data: Config, w: WalletTypeInfo = Depends(require_admin_key) -): - config = await update_config(data, user=w.wallet.user) - assert config - return config.dict() - - -@watchonly_ext.get("/api/v1/config") -async def api_get_config(w: WalletTypeInfo = Depends(get_key_type)): - config = await get_config(w.wallet.user) - if not config: - config = await create_config(user=w.wallet.user) - return config.dict()