diff --git a/frontend/cypress/integration/liquid/liquid.spec.ts b/frontend/cypress/integration/liquid/liquid.spec.ts index c1d1c1a02..38abe6bb4 100644 --- a/frontend/cypress/integration/liquid/liquid.spec.ts +++ b/frontend/cypress/integration/liquid/liquid.spec.ts @@ -44,38 +44,77 @@ describe('Liquid', () => { }); describe('assets', () => { - it('shows the assets screen', () => { - cy.visit('/liquid'); - cy.get('li:nth-of-type(5) > a').click().then(() => { - cy.get('table tr').should('have.length', 5); - }); - }); - - it('allows searching assets', () => { - cy.visit('/liquid'); - cy.get('li:nth-of-type(5) > a').click().then(() => { - cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => { - cy.get('table tr').should('have.length', 1); + it('shows the assets screen', () => { + cy.visit('/liquid'); + cy.get('li:nth-of-type(5) > a').click().then(() => { + cy.get('table tr').should('have.length', 5); }); }); - }); - it('shows a specific asset ID', () => { - cy.visit('/liquid'); - cy.get('li:nth-of-type(5) > a').click().then(() => { - cy.get('.container-xl input').click().type('Liquid CAD').then(() => { - cy.get('table tr td:nth-of-type(4) a').click(); + it('allows searching assets', () => { + cy.visit('/liquid'); + cy.get('li:nth-of-type(5) > a').click().then(() => { + cy.get('.container-xl input').click().type('Liquid Bitcoin').then(() => { + cy.get('table tr').should('have.length', 1); + }); }); }); - }); - it('shows a specific asset issuance TX', () => { - cy.visit('/liquid'); - cy.get('li:nth-of-type(5) > a').click().then(() => { - cy.get('.container-xl input').click().type('Liquid CAD').then(() => { - cy.get('table tr td:nth-of-type(5) a').click(); + it('shows a specific asset ID', () => { + cy.visit('/liquid'); + cy.get('li:nth-of-type(5) > a').click().then(() => { + cy.get('.container-xl input').click().type('Liquid CAD').then(() => { + cy.get('table tr td:nth-of-type(4) a').click(); + }); + }); + }); + + it('shows a specific asset issuance TX', () => { + cy.visit('/liquid'); + cy.get('li:nth-of-type(5) > a').click().then(() => { + cy.get('.container-xl input').click().type('Liquid CAD').then(() => { + cy.get('table tr td:nth-of-type(5) a').click(); + }); }); }); - }); }); + + + describe('unblinded TX', () => { + it('show unblinded TX', () => { + cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a'); + cy.get('#table-tx-vin tr').should('have.class', 'assetBox'); + cy.get('#table-tx-vout tr').should('have.class', 'assetBox'); + }); + + it('show empty unblinded TX', () => { + cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded='); + cy.get('#table-tx-vin tr').should('have.class', ''); + cy.get('#table-tx-vout tr').should('have.class', ''); + }); + + it('show invalid unblinded TX hex', () => { + cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=123'); + cy.get('#table-tx-vin tr').should('have.class', ''); + cy.get('#table-tx-vout tr').should('have.class', ''); + cy.get('.error-unblinded' ).contains('Error: Invalid blinding data (invalid hex)'); + }); + + it('show first unblinded vout', () => { + cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc'); + cy.get('#table-tx-vout tr:first-child()').should('have.class', 'assetBox'); + }); + + it('show second unblinded vout', () => { + cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3a'); + cy.get('#table-tx-vout tr').should('have.class', 'assetBox'); + }); + + it('show invalid error unblinded TX', () => { + cy.visit('/liquid/tx/f2f41c0850e8e7e3f1af233161fd596662e67c11ef10ed15943884186fbb7f46#blinded=100000,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,0ab9f70650f16b1db8dfada05237f7d0d65191c3a13183da8a2ddddfbde9a2ad,fd98b2edc5530d76acd553f206a431f4c1fab27e10e290ad719582af878e98fc,2364760,6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d,90c7a43b15b905bca045ca42a01271cfe71d2efe3133f4197792c24505cb32ed,12eb5959d9293b8842e7dd8bc9aa9639fd3fd031c5de3ba911adeca94eb57a3c'); + cy.get('#table-tx-vout tr').should('have.class', 'assetBox'); + cy.get('.error-unblinded' ).contains('Error: Invalid blinding data.'); + }); + }); + }); diff --git a/frontend/src/app/components/transaction/libwally.js b/frontend/src/app/components/transaction/libwally.js new file mode 100644 index 000000000..f72cbfc00 --- /dev/null +++ b/frontend/src/app/components/transaction/libwally.js @@ -0,0 +1,184 @@ +/* +The MIT License (MIT) + +Copyright 2021 Blockstream Corp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +const WALLY_OK = 0, + ASSET_COMMITMENT_LEN = 33, + ASSET_GENERATOR_LEN = 33, + ASSET_TAG_LEN = 32, + BLINDING_FACTOR_LEN = 32; + +const WASM_URL = `./resources/wallycore/wallycore.js`; + +let load_promise, Module; +export function load() { + return ( + load_promise || + (load_promise = new Promise((resolve, reject) => { + const script = document.createElement("script"); + script.src = WASM_URL; + script.addEventListener("error", reject); + script.addEventListener("load", () => + InitWally().then((module) => { + Module = module; + resolve(); + }, reject) + ); + document.body.appendChild(script); + })) + ); +} + +// Simple wrapper to execute both asset_generator_from_bytes and asset_value_commitment, +// with hex conversions +export function generate_commitments( + value, + asset_hex, + value_blinder_hex, + asset_blinder_hex +) { + const asset = parseHex(asset_hex, ASSET_TAG_LEN), + value_blinder = parseHex(value_blinder_hex, BLINDING_FACTOR_LEN), + asset_blinder = parseHex(asset_blinder_hex, BLINDING_FACTOR_LEN); + + const asset_commitment = asset_generator_from_bytes(asset, asset_blinder), + value_commitment = asset_value_commitment( + value, + value_blinder, + asset_commitment + ); + + return { + asset_commitment: encodeHex(asset_commitment), + value_commitment: encodeHex(value_commitment), + }; +} + +export function asset_generator_from_bytes(asset, asset_blinder) { + const asset_commitment_ptr = Module._malloc(ASSET_GENERATOR_LEN); + checkCode( + Module.ccall( + "wally_asset_generator_from_bytes", + "number", + ["array", "number", "array", "number", "number", "number"], + [ + asset, + asset.length, + asset_blinder, + asset_blinder.length, + asset_commitment_ptr, + ASSET_GENERATOR_LEN, + ] + ) + ); + + const asset_commitment = readBytes(asset_commitment_ptr, ASSET_GENERATOR_LEN); + Module._free(asset_commitment_ptr); + return asset_commitment; +} + +export function asset_value_commitment(value, value_blinder, asset_commitment) { + // Emscripten transforms int64 function arguments into two int32 arguments, see: + // https://emscripten.org/docs/getting_started/FAQ.html#how-do-i-pass-int64-t-and-uint64-t-values-from-js-into-wasm-functions + const [value_lo, value_hi] = split_int52_lo_hi(value); + + const value_commitment_ptr = Module._malloc(ASSET_COMMITMENT_LEN); + checkCode( + Module.ccall( + "wally_asset_value_commitment", + "number", + [ + "number", + "number", + "array", + "number", + "array", + "number", + "number", + "number", + ], + [ + value_lo, + value_hi, + value_blinder, + value_blinder.length, + asset_commitment, + asset_commitment.length, + value_commitment_ptr, + ASSET_COMMITMENT_LEN, + ] + ) + ); + + const value_commitment = readBytes( + value_commitment_ptr, + ASSET_COMMITMENT_LEN + ); + Module._free(value_commitment_ptr); + return value_commitment; +} + +function checkCode(code) { + if (code != WALLY_OK) throw new Error(`libwally failed with code ${code}`); +} + +function readBytes(ptr, size) { + const bytes = new Uint8Array(size); + for (let i = 0; i < size; i++) bytes[i] = Module.getValue(ptr + i, "i8"); + return bytes; +} + +// Split a 52-bit JavaScript number into two 32-bits numbers for the low and high bits +// https://stackoverflow.com/a/19274574 +function split_int52_lo_hi(i) { + let lo = i | 0; + if (lo < 0) lo += 4294967296; + + let hi = i - lo; + hi /= 4294967296; + + if (hi < 0 || hi >= 1048576) throw new Error("not an int52: " + i); + + return [lo, hi]; +} + +function encodeHex(bytes) { + // return Buffer.from(bytes).toString("hex"); + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +// Parse hex string encoded in *reverse* +function parseHex(str, expected_size) { + if (!/^([0-9a-f]{2})+$/.test(str)) + throw new Error("Invalid blinders (invalid hex)"); + if (str.length != expected_size * 2) + throw new Error("Invalid blinders (invalid length)"); + return new Uint8Array( + str + .match(/.{2}/g) + .map((hex_byte) => parseInt(hex_byte, 16)) + .reverse() + ); +} diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index ed35889e8..d2080498b 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -9,33 +9,33 @@ -
-
-

Transaction

-
+
+
+

Transaction

+
- + -
- - - - - - +
+ + + + + + +
-
@@ -198,7 +198,7 @@
- +

Details

diff --git a/frontend/src/app/components/transaction/transaction.component.ts b/frontend/src/app/components/transaction/transaction.component.ts index 74d181a50..f3f2191a4 100644 --- a/frontend/src/app/components/transaction/transaction.component.ts +++ b/frontend/src/app/components/transaction/transaction.component.ts @@ -1,7 +1,13 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { ElectrsApiService } from '../../services/electrs-api.service'; import { ActivatedRoute, ParamMap } from '@angular/router'; -import { switchMap, filter, catchError, retryWhen, delay } from 'rxjs/operators'; +import { + switchMap, + filter, + catchError, + retryWhen, + delay, +} from 'rxjs/operators'; import { Transaction, Block } from '../../interfaces/electrs.interface'; import { of, merge, Subscription, Observable, Subject } from 'rxjs'; import { StateService } from '../../services/state.service'; @@ -14,7 +20,7 @@ import { CpfpInfo } from 'src/app/interfaces/node-api.interface'; @Component({ selector: 'app-transaction', templateUrl: './transaction.component.html', - styleUrls: ['./transaction.component.scss'] + styleUrls: ['./transaction.component.scss'], }) export class TransactionComponent implements OnInit, OnDestroy { network = ''; @@ -23,6 +29,7 @@ export class TransactionComponent implements OnInit, OnDestroy { txInBlockIndex: number; isLoadingTx = true; error: any = undefined; + errorUnblinded: any = undefined; waitingForTransaction = false; latestBlock: Block; transactionTime = -1; @@ -32,6 +39,7 @@ export class TransactionComponent implements OnInit, OnDestroy { cpfpInfo: CpfpInfo | null; showCpfpDetails = false; fetchCpfp$ = new Subject(); + commitments: Map; constructor( private route: ActivatedRoute, @@ -40,28 +48,36 @@ export class TransactionComponent implements OnInit, OnDestroy { private websocketService: WebsocketService, private audioService: AudioService, private apiService: ApiService, - private seoService: SeoService, - ) { } + private seoService: SeoService + ) {} ngOnInit() { this.websocketService.want(['blocks', 'mempool-blocks']); - this.stateService.networkChanged$.subscribe((network) => this.network = network); + this.stateService.networkChanged$.subscribe( + (network) => (this.network = network) + ); this.fetchCpfpSubscription = this.fetchCpfp$ .pipe( - switchMap((txId) => this.apiService.getCpfpinfo$(txId) - .pipe( - retryWhen((errors) => errors.pipe(delay(2000))) - ) - ), + switchMap((txId) => + this.apiService + .getCpfpinfo$(txId) + .pipe(retryWhen((errors) => errors.pipe(delay(2000)))) + ) ) .subscribe((cpfpInfo) => { if (!this.tx) { return; } - const lowerFeeParents = cpfpInfo.ancestors.filter((parent) => (parent.fee / (parent.weight / 4)) < this.tx.feePerVsize); - let totalWeight = this.tx.weight + lowerFeeParents.reduce((prev, val) => prev + val.weight, 0); - let totalFees = this.tx.fee + lowerFeeParents.reduce((prev, val) => prev + val.fee, 0); + const lowerFeeParents = cpfpInfo.ancestors.filter( + (parent) => parent.fee / (parent.weight / 4) < this.tx.feePerVsize + ); + let totalWeight = + this.tx.weight + + lowerFeeParents.reduce((prev, val) => prev + val.weight, 0); + let totalFees = + this.tx.fee + + lowerFeeParents.reduce((prev, val) => prev + val.fee, 0); if (cpfpInfo.bestDescendant) { totalWeight += cpfpInfo.bestDescendant.weight; @@ -69,98 +85,116 @@ export class TransactionComponent implements OnInit, OnDestroy { } this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4); - this.stateService.markBlock$.next({ txFeePerVSize: this.tx.effectiveFeePerVsize }); + this.stateService.markBlock$.next({ + txFeePerVSize: this.tx.effectiveFeePerVsize, + }); this.cpfpInfo = cpfpInfo; }); - this.subscription = this.route.paramMap.pipe( - switchMap((params: ParamMap) => { - this.txId = params.get('id') || ''; - this.seoService.setTitle($localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`); - this.resetTransaction(); - return merge( - of(true), - this.stateService.connectionState$.pipe( - filter((state) => state === 2 && this.tx && !this.tx.status.confirmed) - ), - ); - }), - switchMap(() => { - let transactionObservable$: Observable; - if (history.state.data) { - transactionObservable$ = of(history.state.data); - } else { - transactionObservable$ = this.electrsApiService.getTransaction$(this.txId).pipe( - catchError(this.handleLoadElectrsTransactionError.bind(this)) + this.subscription = this.route.paramMap + .pipe( + switchMap(async (params: ParamMap) => { + this.txId = params.get('id') || ''; + + await this.checkUnblindedTx(); + this.seoService.setTitle( + $localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:` ); - } - return merge( - transactionObservable$, - this.stateService.mempoolTransactions$ - ); - }) - ) - .subscribe((tx: Transaction) => { - if (!tx) { - return; - } - this.tx = tx; - if (tx.fee === undefined) { - this.tx.fee = 0; - } - this.tx.feePerVsize = tx.fee / (tx.weight / 4); - this.isLoadingTx = false; - this.error = undefined; - this.waitingForTransaction = false; - this.setMempoolBlocksSubscription(); + this.resetTransaction(); + return merge( + of(true), + this.stateService.connectionState$.pipe( + filter( + (state) => state === 2 && this.tx && !this.tx.status.confirmed + ) + ) + ); + }), + switchMap(() => { + let transactionObservable$: Observable; + if (history.state.data) { + transactionObservable$ = of(history.state.data); + } else { + transactionObservable$ = this.electrsApiService + .getTransaction$(this.txId) + .pipe( + catchError(this.handleLoadElectrsTransactionError.bind(this)) + ); + } + return merge( + transactionObservable$, + this.stateService.mempoolTransactions$ + ); + }) + ) + .subscribe( + async (tx: Transaction) => { + if (!tx) { + return; + } + this.tx = tx; + if (tx.fee === undefined) { + this.tx.fee = 0; + } + this.tx.feePerVsize = tx.fee / (tx.weight / 4); + this.isLoadingTx = false; + this.error = undefined; + this.waitingForTransaction = false; + this.setMempoolBlocksSubscription(); - if (!tx.status.confirmed) { - this.websocketService.startTrackTransaction(tx.txid); + if (!tx.status.confirmed) { + this.websocketService.startTrackTransaction(tx.txid); - if (tx.firstSeen) { - this.transactionTime = tx.firstSeen; - } else { - this.getTransactionTime(); - } - } + if (tx.firstSeen) { + this.transactionTime = tx.firstSeen; + } else { + this.getTransactionTime(); + } + } - if (this.tx.status.confirmed) { - this.stateService.markBlock$.next({ blockHeight: tx.status.block_height }); - } else { - if (tx.cpfpChecked) { - this.stateService.markBlock$.next({ txFeePerVSize: tx.effectiveFeePerVsize }); - this.cpfpInfo = { - ancestors: tx.ancestors, - bestDescendant: tx.bestDescendant, - }; - } else { - this.fetchCpfp$.next(this.tx.txid); + if (this.tx.status.confirmed) { + this.stateService.markBlock$.next({ + blockHeight: tx.status.block_height, + }); + } else { + if (tx.cpfpChecked) { + this.stateService.markBlock$.next({ + txFeePerVSize: tx.effectiveFeePerVsize, + }); + this.cpfpInfo = { + ancestors: tx.ancestors, + bestDescendant: tx.bestDescendant, + }; + } else { + this.fetchCpfp$.next(this.tx.txid); + } + } + await this.checkUnblindedTx(); + }, + (error) => { + this.error = error; + this.isLoadingTx = false; } + ); + + this.stateService.blocks$.subscribe(([block, txConfirmed]) => { + this.latestBlock = block; + + if (txConfirmed && this.tx) { + this.tx.status = { + confirmed: true, + block_height: block.height, + block_hash: block.id, + block_time: block.timestamp, + }; + this.stateService.markBlock$.next({ blockHeight: block.height }); + this.audioService.playSound('magic'); } - }, - (error) => { - this.error = error; - this.isLoadingTx = false; }); - this.stateService.blocks$ - .subscribe(([block, txConfirmed]) => { - this.latestBlock = block; - - if (txConfirmed && this.tx) { - this.tx.status = { - confirmed: true, - block_height: block.height, - block_hash: block.id, - block_time: block.timestamp, - }; - this.stateService.markBlock$.next({ blockHeight: block.height }); - this.audioService.playSound('magic'); - } - }); - - this.stateService.txReplaced$ - .subscribe((rbfTransaction) => this.rbfTransaction = rbfTransaction); + this.stateService.txReplaced$.subscribe( + (rbfTransaction) => (this.rbfTransaction = rbfTransaction) + ); } handleLoadElectrsTransactionError(error: any): Observable { @@ -174,26 +208,30 @@ export class TransactionComponent implements OnInit, OnDestroy { } setMempoolBlocksSubscription() { - this.stateService.mempoolBlocks$ - .subscribe((mempoolBlocks) => { - if (!this.tx) { - return; - } + this.stateService.mempoolBlocks$.subscribe((mempoolBlocks) => { + if (!this.tx) { + return; + } - const txFeePerVSize = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4); + const txFeePerVSize = + this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4); - for (const block of mempoolBlocks) { - for (let i = 0; i < block.feeRange.length - 1; i++) { - if (txFeePerVSize <= block.feeRange[i + 1] && txFeePerVSize >= block.feeRange[i]) { - this.txInBlockIndex = mempoolBlocks.indexOf(block); - } + for (const block of mempoolBlocks) { + for (let i = 0; i < block.feeRange.length - 1; i++) { + if ( + txFeePerVSize <= block.feeRange[i + 1] && + txFeePerVSize >= block.feeRange[i] + ) { + this.txInBlockIndex = mempoolBlocks.indexOf(block); } } - }); + } + }); } getTransactionTime() { - this.apiService.getTransactionTimes$([this.tx.txid]) + this.apiService + .getTransactionTimes$([this.tx.txid]) .subscribe((transactionTimes) => { this.transactionTime = transactionTimes[0]; }); @@ -226,4 +264,145 @@ export class TransactionComponent implements OnInit, OnDestroy { this.fetchCpfpSubscription.unsubscribe(); this.leaveTransaction(); } + + // Parse the blinders data from a string encoded as a comma separated list, in the following format: + // ,,, + // This can be repeated with a comma separator to specify blinders for multiple outputs. + + parseBlinders(str: string) { + const parts = str.split(','); + const blinders = []; + while (parts.length) { + blinders.push({ + value: this.verifyNum(parts.shift()), + asset: this.verifyHex32(parts.shift()), + value_blinder: this.verifyHex32(parts.shift()), + asset_blinder: this.verifyHex32(parts.shift()), + }); + } + return blinders; + } + + verifyNum(num: string) { + if (!+num) { + throw new Error('Invalid blinding data (invalid number)'); + } + return +num; + } + verifyHex32(str: string) { + if (!str || !/^[0-9a-f]{64}$/i.test(str)) { + throw new Error('Invalid blinding data (invalid hex)'); + } + return str; + } + + async makeCommitmentMap(blinders: any) { + const libwally = await import('./libwally.js'); + await libwally.load(); + const commitments = new Map(); + blinders.forEach(b => { + const { asset_commitment, value_commitment } = + libwally.generate_commitments(b.value, b.asset, b.value_blinder, b.asset_blinder); + + commitments.set(`${asset_commitment}:${value_commitment}`, { + asset: b.asset, + value: b.value, + }); + }); + return commitments; + } + + // Look for the given output, returning an { value, asset } object + find(vout: any) { + return vout.assetcommitment && vout.valuecommitment && + this.commitments.get(`${vout.assetcommitment}:${vout.valuecommitment}`); + } + + // Lookup all transaction inputs/outputs and attach the unblinded data + tryUnblindTx(tx: any) { + if (tx) { + if (tx._unblinded) { return tx._unblinded; } + let matched = 0; + if (tx.vout !== undefined) { + tx.vout.forEach(vout => matched += +this.tryUnblindOut(vout)); + tx.vin.filter(vin => vin.prevout).forEach(vin => matched += +this.tryUnblindOut(vin.prevout)); + } + if (this.commitments !== undefined) { + tx._unblinded = { matched, total: this.commitments.size }; + this.deduceBlinded(tx); + if (matched < this.commitments.size) { + this.errorUnblinded = `Error: Invalid blinding data.`; + } + tx._deduced = false; // invalidate cache so deduction is attempted again + return tx._unblinded; + } + } + } + + // Look the given output and attach the unblinded data + tryUnblindOut(vout: any) { + const unblinded = this.find(vout); + if (unblinded) { Object.assign(vout, unblinded); } + return !!unblinded; + } + + // Attempt to deduce the blinded input/output based on the available information + deduceBlinded(tx: any) { + if (tx._deduced) { return; } + tx._deduced = true; + + // Find ins/outs with unknown amounts (blinded ant not revealed via the `#blinded` hash fragment) + const unknownIns = tx.vin.filter(vin => vin.prevout && vin.prevout.value == null); + const unknownOuts = tx.vout.filter(vout => vout.value == null); + + // If the transaction has a single unknown input/output, we can deduce its asset/amount + // based on the other known inputs/outputs. + if (unknownIns.length + unknownOuts.length === 1) { + + // Keep a per-asset tally of all known input amounts, minus all known output amounts + const totals = new Map(); + tx.vin.filter(vin => vin.prevout && vin.prevout.value != null) + .forEach(({ prevout }) => + totals.set(prevout.asset, (totals.get(prevout.asset) || 0) + prevout.value)); + tx.vout.filter(vout => vout.value != null) + .forEach(vout => + totals.set(vout.asset, (totals.get(vout.asset) || 0) - vout.value)); + + // There should only be a single asset where the inputs and outputs amounts mismatch, + // which is the asset of the blinded input/output + const remainder = Array.from(totals.entries()).filter(([ asset, value ]) => value !== 0); + if (remainder.length !== 1) { throw new Error('unexpected remainder while deducing blinded tx'); } + const [ blindedAsset, blindedValue ] = remainder[0]; + + // A positive remainder (when known in > known out) is the asset/amount of the unknown blinded output, + // a negative one is the input. + if (blindedValue > 0) { + if (!unknownOuts.length) { throw new Error('expected unknown output'); } + unknownOuts[0].asset = blindedAsset; + unknownOuts[0].value = blindedValue; + } else { + if (!unknownIns.length) { throw new Error('expected unknown input'); } + unknownIns[0].prevout.asset = blindedAsset; + unknownIns[0].prevout.value = blindedValue * -1; + } + } + } + + async checkUnblindedTx() { + try { + if (this.network === 'liquid') { + const windowLocationHash = window.location.hash.substring('#blinded='.length); + if (windowLocationHash.length > 0) { + + const blinders = this.parseBlinders(windowLocationHash); + if (blinders) { + this.commitments = await this.makeCommitmentMap(blinders); + this.tryUnblindTx(this.tx); + } + } + } + } catch (error) { + this.errorUnblinded = error; + } + } } diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.html b/frontend/src/app/components/transactions-list/transactions-list.component.html index 03ab90afd..20dee92e5 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.html +++ b/frontend/src/app/components/transactions-list/transactions-list.component.html @@ -12,13 +12,16 @@
+
+ +
{{ errorUnblinded }}
- +
- + -
@@ -66,7 +69,7 @@
+ @@ -114,10 +117,10 @@
-
+
- + -
{{ vout.scriptpubkey_address | shortenString : 16 }} @@ -145,7 +148,7 @@
-
+
@@ -162,7 +165,7 @@
+ @@ -235,5 +238,4 @@ {{ assetsMinimal[item.asset][0] }}
{{ item.asset | shortenString : 13 }} -

diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.scss b/frontend/src/app/components/transactions-list/transactions-list.component.scss index fd95e3759..b85069650 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.scss +++ b/frontend/src/app/components/transactions-list/transactions-list.component.scss @@ -4,9 +4,10 @@ .arrow { display: inline-block!important; - position: relative; + position: absolute; width: 14px; - height: 22px; + margin-top: 10px; + margin-left: -5px; box-sizing: content-box } @@ -66,24 +67,6 @@ } } -.details-table { - margin-top: 5px; -} - -.details-table td { - padding: 0.75rem; - &:first-child { - white-space: pre-wrap; - } -} - -.details-table td:nth-child(2) { - word-break: break-all; - white-space: normal; - font-family: "Courier New", Courier, monospace; - font-size: 12px; -} - .smaller-text { font-size: 12px; @media (min-width: 576px) { @@ -137,4 +120,36 @@ padding: 10px; margin-bottom: 10px; margin-top: 10px; +} +.assetBox { + background-color: #653b9c90; +} + +.details-container { + padding: 0px; + tr td { + padding: 0.75rem; + font-size: 12px; + &:first-child { + color: #ffffff66; + white-space: pre-wrap; + @media (min-width: 476px) { + white-space: nowrap; + } + } + &:nth-child(2) { + word-break: break-all; + white-space: normal; + font-family: "Courier New", Courier, monospace; + } + } +} + +.error-unblinded { + display: block; + width: 100%; + color: #d43131; + text-align: right; + margin-top: 0px; + margin-bottom: 10px; } \ No newline at end of file diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts index dd7350954..4425317c0 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.ts +++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts @@ -21,6 +21,7 @@ export class TransactionsListComponent implements OnInit, OnChanges { @Input() transactions: Transaction[]; @Input() showConfirmations = false; @Input() transactionPage = false; + @Input() errorUnblinded = false; @Output() loadMore = new EventEmitter(); diff --git a/frontend/src/app/interfaces/electrs.interface.ts b/frontend/src/app/interfaces/electrs.interface.ts index dcf7508ae..ff948551f 100644 --- a/frontend/src/app/interfaces/electrs.interface.ts +++ b/frontend/src/app/interfaces/electrs.interface.ts @@ -17,6 +17,8 @@ export interface Transaction { bestDescendant?: BestDescendant | null; cpfpChecked?: boolean; deleteAfter?: number; + _unblinded?: any; + _deduced?: boolean; } interface Ancestor { diff --git a/frontend/src/resources/wallycore/wallycore.js b/frontend/src/resources/wallycore/wallycore.js new file mode 100644 index 000000000..8a61046f2 --- /dev/null +++ b/frontend/src/resources/wallycore/wallycore.js @@ -0,0 +1,802 @@ +/* +The MIT License (MIT) + +Copyright 2021 Blockstream Corp + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE +*/ + +var InitWally = (function () { + var _scriptDir = + typeof document !== "undefined" && document.currentScript + ? document.currentScript.src + : undefined; + if (typeof __filename !== "undefined") _scriptDir = _scriptDir || __filename; + return function (InitWally) { + InitWally = InitWally || {}; + + var Module = typeof InitWally !== "undefined" ? InitWally : {}; + var readyPromiseResolve, readyPromiseReject; + Module["ready"] = new Promise(function (resolve, reject) { + readyPromiseResolve = resolve; + readyPromiseReject = reject; + }); + var moduleOverrides = {}; + var key; + for (key in Module) { + if (Module.hasOwnProperty(key)) { + moduleOverrides[key] = Module[key]; + } + } + var arguments_ = []; + var thisProgram = "./this.program"; + var quit_ = function (status, toThrow) { + throw toThrow; + }; + var ENVIRONMENT_IS_WEB = false; + var ENVIRONMENT_IS_WORKER = false; + var ENVIRONMENT_IS_NODE = false; + var ENVIRONMENT_IS_SHELL = false; + ENVIRONMENT_IS_WEB = typeof window === "object"; + ENVIRONMENT_IS_WORKER = typeof importScripts === "function"; + ENVIRONMENT_IS_NODE = + typeof process === "object" && + typeof process.versions === "object" && + typeof process.versions.node === "string"; + ENVIRONMENT_IS_SHELL = + !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER; + var scriptDirectory = ""; + function locateFile(path) { + if (Module["locateFile"]) { + return Module["locateFile"](path, scriptDirectory); + } + return scriptDirectory + path; + } + var read_, readAsync, readBinary, setWindowTitle; + var nodeFS; + var nodePath; + if (ENVIRONMENT_IS_NODE) { + if (ENVIRONMENT_IS_WORKER) { + scriptDirectory = require("path").dirname(scriptDirectory) + "/"; + } else { + scriptDirectory = __dirname + "/"; + } + read_ = function shell_read(filename, binary) { + if (!nodeFS) nodeFS = require("fs"); + if (!nodePath) nodePath = require("path"); + filename = nodePath["normalize"](filename); + return nodeFS["readFileSync"](filename, binary ? null : "utf8"); + }; + readBinary = function readBinary(filename) { + var ret = read_(filename, true); + if (!ret.buffer) { + ret = new Uint8Array(ret); + } + assert(ret.buffer); + return ret; + }; + if (process["argv"].length > 1) { + thisProgram = process["argv"][1].replace(/\\/g, "/"); + } + arguments_ = process["argv"].slice(2); + process["on"]("uncaughtException", function (ex) { + if (!(ex instanceof ExitStatus)) { + throw ex; + } + }); + process["on"]("unhandledRejection", abort); + quit_ = function (status) { + process["exit"](status); + }; + Module["inspect"] = function () { + return "[Emscripten Module object]"; + }; + } else if (ENVIRONMENT_IS_SHELL) { + if (typeof read != "undefined") { + read_ = function shell_read(f) { + return read(f); + }; + } + readBinary = function readBinary(f) { + var data; + if (typeof readbuffer === "function") { + return new Uint8Array(readbuffer(f)); + } + data = read(f, "binary"); + assert(typeof data === "object"); + return data; + }; + if (typeof scriptArgs != "undefined") { + arguments_ = scriptArgs; + } else if (typeof arguments != "undefined") { + arguments_ = arguments; + } + if (typeof quit === "function") { + quit_ = function (status) { + quit(status); + }; + } + if (typeof print !== "undefined") { + if (typeof console === "undefined") console = {}; + console.log = print; + console.warn = console.error = + typeof printErr !== "undefined" ? printErr : print; + } + } else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { + if (ENVIRONMENT_IS_WORKER) { + scriptDirectory = self.location.href; + } else if (typeof document !== "undefined" && document.currentScript) { + scriptDirectory = document.currentScript.src; + } + if (_scriptDir) { + scriptDirectory = _scriptDir; + } + if (scriptDirectory.indexOf("blob:") !== 0) { + scriptDirectory = scriptDirectory.substr( + 0, + scriptDirectory.lastIndexOf("/") + 1 + ); + } else { + scriptDirectory = ""; + } + { + read_ = function shell_read(url) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, false); + xhr.send(null); + return xhr.responseText; + }; + if (ENVIRONMENT_IS_WORKER) { + readBinary = function readBinary(url) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, false); + xhr.responseType = "arraybuffer"; + xhr.send(null); + return new Uint8Array(xhr.response); + }; + } + readAsync = function readAsync(url, onload, onerror) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", url, true); + xhr.responseType = "arraybuffer"; + xhr.onload = function xhr_onload() { + if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) { + onload(xhr.response); + return; + } + onerror(); + }; + xhr.onerror = onerror; + xhr.send(null); + }; + } + setWindowTitle = function (title) { + document.title = title; + }; + } else { + } + var out = Module["print"] || console.log.bind(console); + var err = Module["printErr"] || console.warn.bind(console); + for (key in moduleOverrides) { + if (moduleOverrides.hasOwnProperty(key)) { + Module[key] = moduleOverrides[key]; + } + } + moduleOverrides = null; + if (Module["arguments"]) arguments_ = Module["arguments"]; + if (Module["thisProgram"]) thisProgram = Module["thisProgram"]; + if (Module["quit"]) quit_ = Module["quit"]; + var wasmBinary; + if (Module["wasmBinary"]) wasmBinary = Module["wasmBinary"]; + var noExitRuntime; + if (Module["noExitRuntime"]) noExitRuntime = Module["noExitRuntime"]; + if (typeof WebAssembly !== "object") { + abort("no native wasm support detected"); + } + function getValue(ptr, type, noSafe) { + type = type || "i8"; + if (type.charAt(type.length - 1) === "*") type = "i32"; + switch (type) { + case "i1": + return HEAP8[ptr >> 0]; + case "i8": + return HEAP8[ptr >> 0]; + case "i16": + return HEAP16[ptr >> 1]; + case "i32": + return HEAP32[ptr >> 2]; + case "i64": + return HEAP32[ptr >> 2]; + case "float": + return HEAPF32[ptr >> 2]; + case "double": + return HEAPF64[ptr >> 3]; + default: + abort("invalid type for getValue: " + type); + } + return null; + } + var wasmMemory; + var ABORT = false; + var EXITSTATUS = 0; + function assert(condition, text) { + if (!condition) { + abort("Assertion failed: " + text); + } + } + function getCFunc(ident) { + var func = Module["_" + ident]; + assert( + func, + "Cannot call unknown function " + ident + ", make sure it is exported" + ); + return func; + } + function ccall(ident, returnType, argTypes, args, opts) { + var toC = { + string: function (str) { + var ret = 0; + if (str !== null && str !== undefined && str !== 0) { + var len = (str.length << 2) + 1; + ret = stackAlloc(len); + stringToUTF8(str, ret, len); + } + return ret; + }, + array: function (arr) { + var ret = stackAlloc(arr.length); + writeArrayToMemory(arr, ret); + return ret; + }, + }; + function convertReturnValue(ret) { + if (returnType === "string") return UTF8ToString(ret); + if (returnType === "boolean") return Boolean(ret); + return ret; + } + var func = getCFunc(ident); + var cArgs = []; + var stack = 0; + if (args) { + for (var i = 0; i < args.length; i++) { + var converter = toC[argTypes[i]]; + if (converter) { + if (stack === 0) stack = stackSave(); + cArgs[i] = converter(args[i]); + } else { + cArgs[i] = args[i]; + } + } + } + var ret = func.apply(null, cArgs); + ret = convertReturnValue(ret); + if (stack !== 0) stackRestore(stack); + return ret; + } + var UTF8Decoder = + typeof TextDecoder !== "undefined" ? new TextDecoder("utf8") : undefined; + function UTF8ArrayToString(heap, idx, maxBytesToRead) { + var endIdx = idx + maxBytesToRead; + var endPtr = idx; + while (heap[endPtr] && !(endPtr >= endIdx)) ++endPtr; + if (endPtr - idx > 16 && heap.subarray && UTF8Decoder) { + return UTF8Decoder.decode(heap.subarray(idx, endPtr)); + } else { + var str = ""; + while (idx < endPtr) { + var u0 = heap[idx++]; + if (!(u0 & 128)) { + str += String.fromCharCode(u0); + continue; + } + var u1 = heap[idx++] & 63; + if ((u0 & 224) == 192) { + str += String.fromCharCode(((u0 & 31) << 6) | u1); + continue; + } + var u2 = heap[idx++] & 63; + if ((u0 & 240) == 224) { + u0 = ((u0 & 15) << 12) | (u1 << 6) | u2; + } else { + u0 = ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heap[idx++] & 63); + } + if (u0 < 65536) { + str += String.fromCharCode(u0); + } else { + var ch = u0 - 65536; + str += String.fromCharCode(55296 | (ch >> 10), 56320 | (ch & 1023)); + } + } + } + return str; + } + function UTF8ToString(ptr, maxBytesToRead) { + return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : ""; + } + function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) { + if (!(maxBytesToWrite > 0)) return 0; + var startIdx = outIdx; + var endIdx = outIdx + maxBytesToWrite - 1; + for (var i = 0; i < str.length; ++i) { + var u = str.charCodeAt(i); + if (u >= 55296 && u <= 57343) { + var u1 = str.charCodeAt(++i); + u = (65536 + ((u & 1023) << 10)) | (u1 & 1023); + } + if (u <= 127) { + if (outIdx >= endIdx) break; + heap[outIdx++] = u; + } else if (u <= 2047) { + if (outIdx + 1 >= endIdx) break; + heap[outIdx++] = 192 | (u >> 6); + heap[outIdx++] = 128 | (u & 63); + } else if (u <= 65535) { + if (outIdx + 2 >= endIdx) break; + heap[outIdx++] = 224 | (u >> 12); + heap[outIdx++] = 128 | ((u >> 6) & 63); + heap[outIdx++] = 128 | (u & 63); + } else { + if (outIdx + 3 >= endIdx) break; + heap[outIdx++] = 240 | (u >> 18); + heap[outIdx++] = 128 | ((u >> 12) & 63); + heap[outIdx++] = 128 | ((u >> 6) & 63); + heap[outIdx++] = 128 | (u & 63); + } + } + heap[outIdx] = 0; + return outIdx - startIdx; + } + function stringToUTF8(str, outPtr, maxBytesToWrite) { + return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite); + } + function writeArrayToMemory(array, buffer) { + HEAP8.set(array, buffer); + } + var buffer, + HEAP8, + HEAPU8, + HEAP16, + HEAPU16, + HEAP32, + HEAPU32, + HEAPF32, + HEAPF64; + function updateGlobalBufferAndViews(buf) { + buffer = buf; + Module["HEAP8"] = HEAP8 = new Int8Array(buf); + Module["HEAP16"] = HEAP16 = new Int16Array(buf); + Module["HEAP32"] = HEAP32 = new Int32Array(buf); + Module["HEAPU8"] = HEAPU8 = new Uint8Array(buf); + Module["HEAPU16"] = HEAPU16 = new Uint16Array(buf); + Module["HEAPU32"] = HEAPU32 = new Uint32Array(buf); + Module["HEAPF32"] = HEAPF32 = new Float32Array(buf); + Module["HEAPF64"] = HEAPF64 = new Float64Array(buf); + } + var INITIAL_MEMORY = Module["INITIAL_MEMORY"] || 16777216; + if (Module["wasmMemory"]) { + wasmMemory = Module["wasmMemory"]; + } else { + wasmMemory = new WebAssembly.Memory({ + initial: INITIAL_MEMORY / 65536, + maximum: INITIAL_MEMORY / 65536, + }); + } + if (wasmMemory) { + buffer = wasmMemory.buffer; + } + INITIAL_MEMORY = buffer.byteLength; + updateGlobalBufferAndViews(buffer); + var wasmTable; + var __ATPRERUN__ = []; + var __ATINIT__ = []; + var __ATMAIN__ = []; + var __ATPOSTRUN__ = []; + var runtimeInitialized = false; + function preRun() { + if (Module["preRun"]) { + if (typeof Module["preRun"] == "function") + Module["preRun"] = [Module["preRun"]]; + while (Module["preRun"].length) { + addOnPreRun(Module["preRun"].shift()); + } + } + callRuntimeCallbacks(__ATPRERUN__); + } + function initRuntime() { + runtimeInitialized = true; + callRuntimeCallbacks(__ATINIT__); + } + function preMain() { + callRuntimeCallbacks(__ATMAIN__); + } + function postRun() { + if (Module["postRun"]) { + if (typeof Module["postRun"] == "function") + Module["postRun"] = [Module["postRun"]]; + while (Module["postRun"].length) { + addOnPostRun(Module["postRun"].shift()); + } + } + callRuntimeCallbacks(__ATPOSTRUN__); + } + function addOnPreRun(cb) { + __ATPRERUN__.unshift(cb); + } + function addOnPostRun(cb) { + __ATPOSTRUN__.unshift(cb); + } + var runDependencies = 0; + var runDependencyWatcher = null; + var dependenciesFulfilled = null; + function addRunDependency(id) { + runDependencies++; + if (Module["monitorRunDependencies"]) { + Module["monitorRunDependencies"](runDependencies); + } + } + function removeRunDependency(id) { + runDependencies--; + if (Module["monitorRunDependencies"]) { + Module["monitorRunDependencies"](runDependencies); + } + if (runDependencies == 0) { + if (runDependencyWatcher !== null) { + clearInterval(runDependencyWatcher); + runDependencyWatcher = null; + } + if (dependenciesFulfilled) { + var callback = dependenciesFulfilled; + dependenciesFulfilled = null; + callback(); + } + } + } + Module["preloadedImages"] = {}; + Module["preloadedAudios"] = {}; + function abort(what) { + if (Module["onAbort"]) { + Module["onAbort"](what); + } + what += ""; + err(what); + ABORT = true; + EXITSTATUS = 1; + what = "abort(" + what + "). Build with -s ASSERTIONS=1 for more info."; + var e = new WebAssembly.RuntimeError(what); + readyPromiseReject(e); + throw e; + } + function hasPrefix(str, prefix) { + return String.prototype.startsWith + ? str.startsWith(prefix) + : str.indexOf(prefix) === 0; + } + var dataURIPrefix = "data:application/octet-stream;base64,"; + function isDataURI(filename) { + return hasPrefix(filename, dataURIPrefix); + } + var fileURIPrefix = "file://"; + function isFileURI(filename) { + return hasPrefix(filename, fileURIPrefix); + } + var wasmBinaryFile = "wallycore.wasm"; + if (!isDataURI(wasmBinaryFile)) { + wasmBinaryFile = locateFile(wasmBinaryFile); + } + function getBinary() { + try { + if (wasmBinary) { + return new Uint8Array(wasmBinary); + } + if (readBinary) { + return readBinary(wasmBinaryFile); + } else { + throw "both async and sync fetching of the wasm failed"; + } + } catch (err) { + abort(err); + } + } + function getBinaryPromise() { + if ( + !wasmBinary && + (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) && + typeof fetch === "function" && + !isFileURI(wasmBinaryFile) + ) { + return fetch(wasmBinaryFile, { credentials: "same-origin" }) + .then(function (response) { + if (!response["ok"]) { + throw ( + "failed to load wasm binary file at '" + wasmBinaryFile + "'" + ); + } + return response["arrayBuffer"](); + }) + .catch(function () { + return getBinary(); + }); + } + return Promise.resolve().then(getBinary); + } + function createWasm() { + var info = { a: asmLibraryArg }; + function receiveInstance(instance, module) { + var exports = instance.exports; + Module["asm"] = exports; + wasmTable = Module["asm"]["h"]; + removeRunDependency("wasm-instantiate"); + } + addRunDependency("wasm-instantiate"); + function receiveInstantiatedSource(output) { + receiveInstance(output["instance"]); + } + function instantiateArrayBuffer(receiver) { + return getBinaryPromise() + .then(function (binary) { + return WebAssembly.instantiate(binary, info); + }) + .then(receiver, function (reason) { + err("failed to asynchronously prepare wasm: " + reason); + abort(reason); + }); + } + function instantiateAsync() { + if ( + !wasmBinary && + typeof WebAssembly.instantiateStreaming === "function" && + !isDataURI(wasmBinaryFile) && + !isFileURI(wasmBinaryFile) && + typeof fetch === "function" + ) { + return fetch(wasmBinaryFile, { credentials: "same-origin" }).then( + function (response) { + var result = WebAssembly.instantiateStreaming(response, info); + return result.then(receiveInstantiatedSource, function (reason) { + err("wasm streaming compile failed: " + reason); + err("falling back to ArrayBuffer instantiation"); + return instantiateArrayBuffer(receiveInstantiatedSource); + }); + } + ); + } else { + return instantiateArrayBuffer(receiveInstantiatedSource); + } + } + if (Module["instantiateWasm"]) { + try { + var exports = Module["instantiateWasm"](info, receiveInstance); + return exports; + } catch (e) { + err("Module.instantiateWasm callback failed with error: " + e); + return false; + } + } + instantiateAsync().catch(readyPromiseReject); + return {}; + } + function callRuntimeCallbacks(callbacks) { + while (callbacks.length > 0) { + var callback = callbacks.shift(); + if (typeof callback == "function") { + callback(Module); + continue; + } + var func = callback.func; + if (typeof func === "number") { + if (callback.arg === undefined) { + wasmTable.get(func)(); + } else { + wasmTable.get(func)(callback.arg); + } + } else { + func(callback.arg === undefined ? null : callback.arg); + } + } + } + function _abort() { + abort(); + } + function _emscripten_memcpy_big(dest, src, num) { + HEAPU8.copyWithin(dest, src, src + num); + } + function abortOnCannotGrowMemory(requestedSize) { + abort("OOM"); + } + function _emscripten_resize_heap(requestedSize) { + requestedSize = requestedSize >>> 0; + abortOnCannotGrowMemory(requestedSize); + } + var SYSCALLS = { + mappings: {}, + buffers: [null, [], []], + printChar: function (stream, curr) { + var buffer = SYSCALLS.buffers[stream]; + if (curr === 0 || curr === 10) { + (stream === 1 ? out : err)(UTF8ArrayToString(buffer, 0)); + buffer.length = 0; + } else { + buffer.push(curr); + } + }, + varargs: undefined, + get: function () { + SYSCALLS.varargs += 4; + var ret = HEAP32[(SYSCALLS.varargs - 4) >> 2]; + return ret; + }, + getStr: function (ptr) { + var ret = UTF8ToString(ptr); + return ret; + }, + get64: function (low, high) { + return low; + }, + }; + function _fd_close(fd) { + return 0; + } + function _fd_seek(fd, offset_low, offset_high, whence, newOffset) {} + function _fd_write(fd, iov, iovcnt, pnum) { + var num = 0; + for (var i = 0; i < iovcnt; i++) { + var ptr = HEAP32[(iov + i * 8) >> 2]; + var len = HEAP32[(iov + (i * 8 + 4)) >> 2]; + for (var j = 0; j < len; j++) { + SYSCALLS.printChar(fd, HEAPU8[ptr + j]); + } + num += len; + } + HEAP32[pnum >> 2] = num; + return 0; + } + __ATINIT__.push({ + func: function () { + ___wasm_call_ctors(); + }, + }); + var asmLibraryArg = { + b: _abort, + e: _emscripten_memcpy_big, + f: _emscripten_resize_heap, + g: _fd_close, + d: _fd_seek, + c: _fd_write, + a: wasmMemory, + }; + var asm = createWasm(); + var ___wasm_call_ctors = (Module["___wasm_call_ctors"] = function () { + return (___wasm_call_ctors = Module["___wasm_call_ctors"] = + Module["asm"]["i"]).apply(null, arguments); + }); + var _wally_asset_generator_from_bytes = (Module[ + "_wally_asset_generator_from_bytes" + ] = function () { + return (_wally_asset_generator_from_bytes = Module[ + "_wally_asset_generator_from_bytes" + ] = + Module["asm"]["j"]).apply(null, arguments); + }); + var _wally_asset_value_commitment = (Module[ + "_wally_asset_value_commitment" + ] = function () { + return (_wally_asset_value_commitment = Module[ + "_wally_asset_value_commitment" + ] = + Module["asm"]["k"]).apply(null, arguments); + }); + var _wally_init = (Module["_wally_init"] = function () { + return (_wally_init = Module["_wally_init"] = Module["asm"]["l"]).apply( + null, + arguments + ); + }); + var _malloc = (Module["_malloc"] = function () { + return (_malloc = Module["_malloc"] = Module["asm"]["m"]).apply( + null, + arguments + ); + }); + var _free = (Module["_free"] = function () { + return (_free = Module["_free"] = Module["asm"]["n"]).apply( + null, + arguments + ); + }); + var stackSave = (Module["stackSave"] = function () { + return (stackSave = Module["stackSave"] = Module["asm"]["o"]).apply( + null, + arguments + ); + }); + var stackRestore = (Module["stackRestore"] = function () { + return (stackRestore = Module["stackRestore"] = Module["asm"]["p"]).apply( + null, + arguments + ); + }); + var stackAlloc = (Module["stackAlloc"] = function () { + return (stackAlloc = Module["stackAlloc"] = Module["asm"]["q"]).apply( + null, + arguments + ); + }); + Module["ccall"] = ccall; + Module["getValue"] = getValue; + var calledRun; + function ExitStatus(status) { + this.name = "ExitStatus"; + this.message = "Program terminated with exit(" + status + ")"; + this.status = status; + } + dependenciesFulfilled = function runCaller() { + if (!calledRun) run(); + if (!calledRun) dependenciesFulfilled = runCaller; + }; + function run(args) { + args = args || arguments_; + if (runDependencies > 0) { + return; + } + preRun(); + if (runDependencies > 0) return; + function doRun() { + if (calledRun) return; + calledRun = true; + Module["calledRun"] = true; + if (ABORT) return; + initRuntime(); + preMain(); + readyPromiseResolve(Module); + if (Module["onRuntimeInitialized"]) Module["onRuntimeInitialized"](); + postRun(); + } + if (Module["setStatus"]) { + Module["setStatus"]("Running..."); + setTimeout(function () { + setTimeout(function () { + Module["setStatus"](""); + }, 1); + doRun(); + }, 1); + } else { + doRun(); + } + } + Module["run"] = run; + if (Module["preInit"]) { + if (typeof Module["preInit"] == "function") + Module["preInit"] = [Module["preInit"]]; + while (Module["preInit"].length > 0) { + Module["preInit"].pop()(); + } + } + noExitRuntime = true; + run(); + + return InitWally.ready; + }; +})(); +if (typeof exports === "object" && typeof module === "object") + module.exports = InitWally; +else if (typeof define === "function" && define["amd"]) + define([], function () { + return InitWally; + }); +else if (typeof exports === "object") exports["InitWally"] = InitWally; diff --git a/frontend/src/resources/wallycore/wallycore.wasm b/frontend/src/resources/wallycore/wallycore.wasm new file mode 100755 index 000000000..d941b9504 Binary files /dev/null and b/frontend/src/resources/wallycore/wallycore.wasm differ