1
0
Эх сурвалжийг харах

Sign shared tx + broadcast.

Shomari 1 сар өмнө
parent
commit
f998ac1576

+ 85 - 3
web/components/Wallet/Welcome.vue

@@ -1,6 +1,7 @@
 <script setup lang="ts">
 /* Import modules. */
 import numeral from 'numeral'
+import signSharedTx from '../handlers/signSharedTx.ts'
 
 /* Define properties. */
 // https://vuejs.org/guide/components/props.html#props-declaration
@@ -20,6 +21,7 @@ const hushAddresses = ref(null)
 const nexaAddresses = ref(null)
 
 const HUSH_PROTOCOL_ID = 0x48555348
+const DUST_VAL = 546
 
 const balance = computed(() => {
     if (!Wallet.fusionInputs) {
@@ -34,8 +36,88 @@ const balance = computed(() => {
     return numeral(totalValue).format('0,0')
 })
 
-const cashout = () => {
-    alert('WIP?? sorry...')
+const cashout = async () => {
+    // alert('WIP?? sorry...')
+
+    /* Initialize locals. */
+    let inputs
+    let keys
+    let outputs
+    let rawTx
+    let response
+    let session
+
+    // FIXME
+    const sessionid = '4e9654f9-3de9-4f9a-8169-3834f40847f5'
+
+    /* Request session details. */
+    session = await $fetch(`http://localhost:39159/v1/fusion/${sessionid}`)
+        .catch(err => console.error(err))
+    console.log('SESSION', session)
+
+    /* Set inputs. */
+    inputs = session.inputs
+    // console.log('INPUTS', inputs)
+
+    /* Initialize keys. */
+    keys = []
+
+    /* Handle (input) keys. */
+    Object.keys(inputs).forEach(_inputid => {
+        keys.push(_inputid)
+    })
+    // console.log('KEYS', keys)
+
+    /* Sort (input) keys. */
+    keys.sort()
+    // console.log('KEYS (sorted)', keys)
+
+    /* Initialize sorted inputs. */
+    const sortedInputs = []
+
+    /* Handle (input) keys. */
+    keys.forEach(_keyid => {
+        /* Add input. */
+        sortedInputs.push(inputs[_keyid])
+    })
+    // console.log('INPUTS (sorted)', sortedInputs)
+
+    outputs = session.outputs
+    // console.log('OUTPUTS', outputs)
+
+    /* Initialize (output) keys. */
+    keys = []
+
+    /* Handle keys. */
+    Object.keys(outputs).forEach(_outputid => {
+        keys.push(_outputid)
+    })
+    // console.log('KEYS', keys)
+
+    /* Sort (output) keys. */
+    keys.sort()
+    // console.log('KEYS (sorted)', keys)
+
+    /* Initialize sorted outputs. */
+    const sortedOutputs = []
+
+    /* Handle (output) keys. */
+    keys.forEach(_keyid => {
+        if (outputs[_keyid].value >= DUST_VAL) {
+            /* Add input. */
+            sortedOutputs.push(outputs[_keyid])
+        }
+    })
+    // console.log('OUTPUTS (sorted)', sortedOutputs)
+
+    /* Sign shared transaction. */
+    rawTx = signSharedTx(
+        sessionid, Wallet.mnemonic, sortedInputs, sortedOutputs)
+    console.log('RAW TX', rawTx)
+
+    response = await Wallet.broadcast('BCH', rawTx)
+        .catch(err => console.error(err))
+    console.log('BROADCAST (response)', response)
 }
 
 const consolidate = () => {
@@ -64,7 +146,7 @@ const init = () => {
 }
 
 onMounted(() => {
-    console.log('KEYCHAIN', Wallet.keychain)
+    // console.log('KEYCHAIN', Wallet.keychain)
     // init()
     setTimeout(init, 1000)
 })

+ 127 - 0
web/handlers/signSharedTx.ts

@@ -0,0 +1,127 @@
+/* Import modules. */
+import BCHJS from '@psf/bch-js'
+import { Transaction } from 'bitcoinjs-lib'
+import { mnemonicToSeed } from '@nexajs/hdnode'
+import { encodeNullData } from '@nexajs/script'
+import { utf8ToBin } from '@nexajs/utils'
+
+const bchjs = new BCHJS()
+
+/**
+ * Build Shared Transaction
+ *
+ * Combine all participating inputs and outputs into one (signed) transaction.
+ */
+export default function (_sessionid, _mnemonic, _inputs, _outputs) {
+console.log('SIGN SHARED TX', _sessionid, _inputs, _outputs)
+    /* Initialize locals. */
+    let accountIdx
+    let addressIdx
+    let changeIdx
+    let chidleNode
+    let data
+    let ecPair
+    let ownedInputs
+    let protocolId
+    let msg
+    let rawTx
+    let redeemScript
+    let script
+    let wif
+
+    /* Initialize transaction builde.r */
+    const transactionBuilder = new bchjs.TransactionBuilder()
+
+    /* Handle inputs. */
+    _inputs.forEach(_input => {
+        /* Add input. */
+        transactionBuilder.addInput(_input.tx_hash, _input.tx_pos)
+    })
+
+    /* Set protocol ID. */
+    protocolId = '1337'
+
+    /* Set protocol message. */
+    msg = 'testing...'
+
+    script = [
+        utf8ToBin(protocolId),
+        utf8ToBin(msg),
+        // utf8ToBin(_sessionid),
+    ]
+    // console.log('my SCRIPT', script)
+    // console.log('encodeNullData', encodeNullData(script))
+
+    // Compile the script array into a bitcoin-compliant hex encoded string.
+    // const data = bchjs.Script.encode(script)
+    data = Buffer.from(encodeNullData(script))
+    // console.log('OP_RETURN (data)', data)
+
+    // Add the OP_RETURN output.
+    transactionBuilder.addOutput(data, 0)
+
+    /* Handle outputs. */
+    _outputs.forEach(_output => {
+        /* Add output. */
+        transactionBuilder.addOutput(_output.address, _output.value)
+    })
+
+
+
+    /* Convert mnemonic to seed. */
+    const seed = mnemonicToSeed(_mnemonic)
+
+    /* Conver to seed buffer. */
+    // FIXME Migrate to TypedArrays.
+    const seedBuffer = Buffer.from(seed, 'hex')
+
+    /* Generate master node. */
+    const masterNode = bchjs.HDNode.fromSeed(seedBuffer)
+
+/* Set account index. */
+accountIdx = 0
+/* Set change index. */
+changeIdx = 0
+/* Set address index. */
+addressIdx = 0
+
+    /* Generate child node. */
+    chidleNode = masterNode
+        .derivePath(`m/44'/145'/${accountIdx}'/${changeIdx}/${addressIdx}`)
+
+    /* Generate wallet import format (WIF). */
+    wif = bchjs.HDNode.toWIF(chidleNode)
+    // console.log('BCH WIF', wif)
+
+    /* Generate elliptic pair. */
+    ecPair = bchjs.ECPair.fromWIF(wif)
+
+// FIXME Identify our owned inputs.
+ownedInputs = [ 0, 1, 2 ]
+
+    for (let i = 0; i < _inputs.length; i++) {
+        /* Verify input ownership. */
+        if (!ownedInputs.includes(i)) {
+            continue
+        }
+
+        /* Sign (our own) input. */
+        transactionBuilder.sign(
+            i,
+            ecPair,
+            redeemScript,
+            Transaction.SIGHASH_ALL,
+            _inputs[i].value,
+        )
+    }
+
+    /* Generate (incomplete) transaction. */
+    const tx = transactionBuilder.transaction.buildIncomplete()
+    // console.log('TRANSACTION', tx)
+
+    /* Convert to (raw) hex. */
+    rawTx = tx.toHex()
+
+    /* Return raw (hex) transaction. */
+    return rawTx
+}

+ 17 - 6
web/server/routes/v1.post.ts

@@ -1,6 +1,9 @@
 /* Import modules. */
 import moment from 'moment'
-import { decryptForPubkey } from '@nexajs/crypto'
+import {
+    decryptForPubkey,
+    sha256,
+ } from '@nexajs/crypto'
 import {
     binToHex,
     binToUtf8,
@@ -48,7 +51,7 @@ export default defineEventHandler(async (event) => {
     components = decryptForPubkey(binToHex(Wallet.privateKey), components)
     components = binToUtf8(components)
     components = JSON.parse(components)
-    console.log('COMPONENTS', components)
+    // console.log('COMPONENTS', components)
 
     /* Validate auth ID. */
     if (typeof authid === 'undefined' || authid === null) {
@@ -93,19 +96,27 @@ console.log('DEBUG::INSERTING A NEW FUSION')
 const fusion = Db.fusions['4e9654f9-3de9-4f9a-8169-3834f40847f5']
 console.log('FUSION', fusion)
 
+    let componentid
+
     components.forEach(_component => {
         if (_component.tx_hash) {
-            fusion.inputs[_component.tx_hash + ':' + _component.tx_pos] = _component
+            componentid = sha256(_component.tx_hash + ':' + _component.tx_pos)
+            fusion.inputs[componentid] = {
+                ..._component,
+                signature: null,
+            }
         }
 
         if (_component.tierid >= 10000) {
             _component.outputs.forEach(_output => {
-                fusion.outputs[_output.address + ':' + _output.value] = true
+                componentid = sha256(_output.address + ':' + _output.value)
+                fusion.outputs[componentid] = _output
             })
         }
     })
-    // fusion.components = components
-    // fusion.rawTx = rawTx
+
+    /* Update progress. */
+    fusion.progress = 12.5
 
     /* Set (new) updated at (timestamp). */
     fusion.updatedAt = moment().unix()

+ 7 - 5
web/stores/wallet.ts

@@ -457,13 +457,15 @@ _setupHushKeychain.bind(this)()
                                 const numOutputs = response.length
                                 // console.log('NUM OUTPUTS', numOutputs)
 
-                                const fee = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: numOutputs })
+                                // const fee = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: numOutputs })
+                                const fee = bchjs.BitcoinCash.getByteCount({ P2PKH: 1 }, { P2PKH: 1 })
                                 // console.log('FEE', fee)
 
                                 const outputs = response.map(_outputValue => {
                                     return {
                                         address: this.getFusionAddress(),
-                                        value: _outputValue - Math.ceil(fee / numOutputs),
+                                        // value: (_outputValue - Math.ceil(fee / numOutputs)),
+                                        value: (_outputValue - fee),
                                     }
                                 })
 
@@ -529,9 +531,9 @@ _setupHushKeychain.bind(this)()
             }
         },
 
-        broadcast(_receivers) {
-            /* Broadcast to receivers. */
-            return _broadcast.bind(this)(_receivers)
+        broadcast(_network, _rawTx) {
+            /* Broadcast to raw (hex) transaction to mainnet. */
+            return _broadcast.bind(this)(_network, _rawTx)
         },
 
         setEntropy(_entropy) {

+ 11 - 101
web/stores/wallet/broadcast.ts

@@ -1,106 +1,16 @@
-/* Import modules. */
-// import { sha256 } from '@nexajs/crypto'
-// import { encodePrivateKeyWif } from '@nexajs/hdnode'
+// REST API servers.
+const BCHN_MAINNET = 'https://bchn.fullstack.cash/v5/'
 
-import { encodeAddress } from '@nexajs/address'
+// bch-js-examples require code from the main bch-js repo
+import BCHJS from '@psf/bch-js'
 
-// import { sha256 } from '@nexajs/crypto'
+// Instantiate bch-js based on the network.
+const bchjs = new BCHJS({ restURL: BCHN_MAINNET })
 
-import {
-    getCoins,
-    sendCoins,
-} from '@nexajs/purse'
+export default async function (_network = 'NEXA', _rawTx) {
+    // FIXME Add Nexa broadcast functions.
 
-import {
-    encodeDataPush,
-    encodeNullData,
-    OP,
-} from '@nexajs/script'
-
-import {
-    getTokens,
-    sendToken,
-} from '@nexajs/token'
-
-// const TOKEN_ID_HEX = '57f46c1766dc0087b207acde1b3372e9f90b18c7e67242657344dcd2af660000' // AVAS
-const TOKEN_ID_HEX = '9732745682001b06e332b6a4a0dd0fffc4837c707567f8cbfe0f6a9b12080000' // STUDIO
-
-export default async function (_receivers) {
-    /* Initialize locals. */
-    let body
-    let coins
-    let nexaAddress
-    let nullData
-    // let publicKey
-    let publicKeyHash
-    let receivers
-    let response
-    let scriptPubKey
-    // let scriptPushPubKey
-    let tokens
-    let txResult
-    let userData
-    // let wif
-
-    console.info('\n  Nexa address:', this.address)
-
-    coins = await getCoins(this.wif)
-        .catch(err => console.error(err))
-    console.log('\n  Coins:', coins)
-
-    tokens = await getTokens(this.wif)
-        .catch(err => console.error(err))
-    console.log('\n  Tokens:', tokens)
-
-    /* Filter tokens. */
-    // NOTE: Currently limited to a "single" Id.
-    tokens = tokens.filter(_token => {
-        return _token.tokenidHex === TOKEN_ID_HEX
-    })
-    console.log('\n  Tokens (filtered):', tokens)
-
-    userData = [
-        'RAIN',
-        `$STUDIO Telegram Airdrop`,
-    ]
-
-    /* Initialize hex data. */
-    nullData = encodeNullData(userData)
-
-    receivers = [
-        {
-            data: nullData,
-        },
-    ]
-
-    _receivers.forEach(_receiver => {
-        receivers.push(        {
-            address: _receiver.address,
-            tokenid: TOKEN_ID_HEX, // TODO Allow auto-format conversion.
-            tokens: BigInt(_receiver.tokens),
-        },
-)
-    })
-
-    receivers.push({
-        address: this.address,
-    })
-    console.log('\n  Receivers:', receivers)
-// return
-    /* Send UTXO request. */
-    response = await sendToken(coins, tokens, receivers)
-    console.log('Send UTXO (response):', response)
-
-    try {
-        txResult = JSON.parse(response)
-        console.log('TX RESULT', txResult)
-
-        if (txResult.error) {
-            console.error(txResult.error)
-        }
-    } catch (err) {
-        console.error(err)
-    }
-
-    return txResult
+    /* Send raw transaction. */
+    const txid = await bchjs.RawTransactions.sendRawTransaction([ _rawTx ])
+    console.log(`Transaction ID: ${txid}`)
 }