Jelajahi Sumber

Enable "manual" building + broadcast from shared details.

Shomari 1 bulan lalu
induk
melakukan
1b135d9ef9

+ 6 - 2
web/components/Wallet/Welcome.vue

@@ -49,8 +49,12 @@ const cashout = async () => {
     console.log('COMPLETE FUSION', response)
 }
 
-const consolidate = () => {
-    alert('WIP?? sorry...')
+const consolidate = async () => {
+    // alert('WIP?? sorry...')
+
+    const response = await Wallet.signFusion()
+        .catch(err => console.error(err))
+    console.log('SIGN FUSION', response)
 }
 
 const startFusion = () => {

+ 76 - 89
web/handlers/buildSharedTx.ts

@@ -7,115 +7,102 @@ import { utf8ToBin } from '@nexajs/utils'
 
 const bchjs = new BCHJS()
 
+/* Initialize constants. */
+const HUSH_PROTOCOL_ID = 0x48555348
+
+
 /**
  * Build Shared Transaction
  *
  * Combine all participating inputs and outputs into one (signed) transaction.
- *
- * NOTE: ALL inputs are ALREADY signed by their respective participants.
  */
-export default function (_inputs, _outputs) {
+export default function (_sessionid, _inputs, _outputs) {
+console.log('BUILD SHARED TX', _sessionid, _inputs, _outputs)
     /* Initialize locals. */
+    let accountIdx
+    let addressIdx
+    let changeIdx
+    let childNode
+    let data
+    let ecPair
+    let ownedInputs
+    let protocolId
+    let msg
     let rawTx
-    let safeBalance
-    let utxo
+    let redeemScript
+    let script
+    let wif
 
+    /* Initialize transaction builde.r */
     const transactionBuilder = new bchjs.TransactionBuilder()
-let inputIdx
-    this.fusionInputs.forEach((_utxo) => {
-        /* Set UTXO. */
-        utxo = _utxo
-        console.log('UTXO', utxo)
-inputIdx = 0//utxo.tx_pos
-        transactionBuilder.addInput(utxo.tx_hash, utxo.tx_pos)
-
-        const originalAmount = utxo.value
-
-        const remainder = originalAmount - satsNeeded
-        console.log('REMAINDER', remainder);
-
-        if (remainder < 0) {
-            throw new Error('Selected UTXO does not have enough satoshis')
-        }
-
-
-        const protocolId = '1337'
-        const msg = 'building...'
-
-        const script = [
-            utf8ToBin(protocolId),
-            utf8ToBin(msg),
-        ]
-        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)
-        const data = Buffer.from(encodeNullData(script))
-        console.log('OP_RETURN (data)', data)
 
-        // Add the OP_RETURN output.
-        // transactionBuilder.addOutput(data, 0)
-        transactionBuilder.addOutput(data, 0)
+console.log('DO WE HAVE FUSION INPUTS??', this.fusionInputs)
 
+    /* Initialize our addresses. */
+    const ourAddresses = []
 
+    /* Handle fusion inputs. */
+    Object.keys(this.fusionInputs).forEach(_outpoint => {
+        const ourInput = this.fusionInputs[_outpoint]
+        console.log('OUR INPUT', ourInput)
 
-        // Send payment
-        // transactionBuilder.addOutput(receiver, satsNeeded)
-        transactionBuilder.addOutput(receiver, paymentAmount)
-
-        // Send the BCH change back to the payment part
-        // transactionBuilder.addOutput(account.address, remainder - 300)
+        ourAddresses.push(ourInput.address)
     })
+    console.log('OUR ADDRESSES', ourAddresses)
 
-    /* Initialize redeem script. */
-    // FIXME Why do we need this??
-    let redeemScript
-
-    /* Convert mnemonic to seed. */
-    const seed = mnemonicToSeed(this.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. */
-const accountIdx = 0
-/* Set change index. */
-const changeIdx = 0
-/* Set address index. */
-const addressIdx = 0
+    /* Initialize address index. */
+    addressIdx = 0
 
-    /* Generate child node. */
-    const chidleNode = masterNode
-        .derivePath(`m/44'/145'/${accountIdx}'/${changeIdx}/${addressIdx}`)
+    /* Initialize owned inputs. */
+    ownedInputs = []
 
-    /* Generate wallet import format (WIF). */
-    const wif = bchjs.HDNode.toWIF(chidleNode)
-    // console.log('BCH WIF', wif)
+    /* Handle inputs. */
+    _inputs.forEach(_input => {
+        /* Add input. */
+        transactionBuilder.addInput(_input.tx_hash, _input.tx_pos)
 
-    /* Generate elliptic pair. */
-    const ecPair = bchjs.ECPair.fromWIF(wif)
-
-    /* Sign transaction. */
-    transactionBuilder.sign(
-        inputIdx,
-        ecPair,
-        redeemScript,
-        Transaction.SIGHASH_ALL,
-        utxo.value,
-    )
+console.log('VALIDATING (SELF) INPUT', _input)
+        /* Validate (our) address. */
+        if (ourAddresses.includes(_input.address)) {
+            ownedInputs.push(addressIdx)
+        }
+        addressIdx++
+    })
+    console.log('OUR INPUTS INDEX', ownedInputs)
+
+    /* Set protocol ID. */
+    protocolId = '1337'
+
+    /* Set protocol message. */
+    msg = 'mvp!'
+
+    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)
+    })
 
     /* Generate (incomplete) transaction. */
-    const tx = transactionBuilder.transaction.buildIncomplete()
-    // console.log('TRANSACTION', tx)
-
-    /* Convert to (raw) hex. */
-    rawTx = tx.toHex()
+    const transaction = transactionBuilder.transaction.buildIncomplete()
+    console.log('TRANSACTION', transaction)
 
     /* Return raw (hex) transaction. */
-    return rawTx
+    // return transaction
+    return transactionBuilder
 }

+ 36 - 10
web/handlers/signSharedTx.ts

@@ -17,7 +17,7 @@ const HUSH_PROTOCOL_ID = 0x48555348
  * 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)
+console.log('SIGN SHARED TX', _sessionid, _inputs, _outputs)
     /* Initialize locals. */
     let accountIdx
     let addressIdx
@@ -36,17 +36,45 @@ export default function (_sessionid, _mnemonic, _inputs, _outputs) {
     /* Initialize transaction builde.r */
     const transactionBuilder = new bchjs.TransactionBuilder()
 
+console.log('DO WE HAVE FUSION INPUTS??', this.fusionInputs)
+
+    /* Initialize our addresses. */
+    const ourAddresses = []
+
+    /* Handle fusion inputs. */
+    Object.keys(this.fusionInputs).forEach(_outpoint => {
+        const ourInput = this.fusionInputs[_outpoint]
+        console.log('OUR INPUT', ourInput)
+
+        ourAddresses.push(ourInput.address)
+    })
+    console.log('OUR ADDRESSES', ourAddresses)
+
+    /* Initialize address index. */
+    addressIdx = 0
+
+    /* Initialize owned inputs. */
+    ownedInputs = []
+
     /* Handle inputs. */
     _inputs.forEach(_input => {
         /* Add input. */
         transactionBuilder.addInput(_input.tx_hash, _input.tx_pos)
+
+console.log('VALIDATING (SELF) INPUT', _input)
+        /* Validate (our) address. */
+        if (ourAddresses.includes(_input.address)) {
+            ownedInputs.push(addressIdx)
+        }
+        addressIdx++
     })
+    console.log('OUR INPUTS INDEX', ownedInputs)
 
     /* Set protocol ID. */
     protocolId = '1337'
 
     /* Set protocol message. */
-    msg = 're-finalization...'
+    msg = 'mvp!'
 
     script = [
         utf8ToBin(protocolId),
@@ -70,8 +98,6 @@ export default function (_sessionid, _mnemonic, _inputs, _outputs) {
         transactionBuilder.addOutput(_output.address, _output.value)
     })
 
-
-
     /* Convert mnemonic to seed. */
     const seed = mnemonicToSeed(_mnemonic)
 
@@ -92,8 +118,8 @@ ownedInputs = [ 0, 1, 2, 3, 4, 5, 6 ]
         }
 
         /* Set account index. */
-        // accountIdx = 0
-        accountIdx = HUSH_PROTOCOL_ID
+        accountIdx = 0
+        // accountIdx = HUSH_PROTOCOL_ID
         /* Set change index. */
         changeIdx = 0
         /* Set address index. */
@@ -122,12 +148,12 @@ console.log('PARAMS', i, redeemScript, _inputs[i].value)
     }
 
     /* Generate (incomplete) transaction. */
-    const tx = transactionBuilder.transaction.buildIncomplete()
-    // console.log('TRANSACTION', tx)
+    const transaction = transactionBuilder.transaction.buildIncomplete()
+    // console.log('TRANSACTION', transaction)
 
     /* Convert to (raw) hex. */
-    rawTx = tx.toHex()
+    // rawTx = tx.toHex()
 
     /* Return raw (hex) transaction. */
-    return rawTx
+    return transaction
 }

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

@@ -28,6 +28,7 @@ export default defineEventHandler(async (event) => {
     let response
     let session
     let success
+    let unlocked
 
     /* Set database. */
     const Db = event.context.Db
@@ -58,11 +59,23 @@ export default defineEventHandler(async (event) => {
     // const rawTx = body.rawTx
     // console.log('RAW TRANSACTION', rawTx)
 
-    components = body.components
-    components = decryptForPubkey(binToHex(Wallet.privateKey), components)
-    components = binToUtf8(components)
-    components = JSON.parse(components)
-    console.log('COMPONENTS', components)
+    /* Validate components. */
+    if (body.components) {
+        components = body.components
+        components = decryptForPubkey(binToHex(Wallet.privateKey), components)
+        components = binToUtf8(components)
+        components = JSON.parse(components)
+        console.log('COMPONENTS', components)
+    }
+
+    /* Validate unlocked. */
+    if (body.unlocked) {
+        unlocked = body.unlocked
+        unlocked = decryptForPubkey(binToHex(Wallet.privateKey), unlocked)
+        unlocked = binToUtf8(unlocked)
+        unlocked = JSON.parse(unlocked)
+        console.log('UNLOCKED', unlocked)
+    }
 
     /* Validate auth ID. */
     if (typeof authid === 'undefined' || authid === null) {
@@ -118,6 +131,42 @@ console.log('FUSION', fusion)
         return 'Oops! Authorization failed!'
     }
 
+
+
+    if (actionid === 'unlock-components') {
+        /* Update progress. */
+        // fusion.progress = 75.5
+
+        /* Set (new) updated at (timestamp). */
+        fusion.updatedAt = moment().unix()
+
+        console.log('***UNLOCK COMPONENT(S)***', unlocked)
+        // await Db.put('fusions', '4e9654f9-3de9-4f9a-8169-3834f40847f5', fusion)
+
+        const inputs = fusion.inputs
+        console.log('INPUTS', inputs)
+
+        /* Handle inputs. */
+        Object.keys(inputs).forEach(_outpoint => {
+console.log('OUTPOINT', _outpoint)
+            /* Handle unlocked (scripts). */
+            Object.keys(unlocked).forEach(_lockpoint => {
+console.log('LOCKPOINT', _lockpoint)
+                const unlock = unlocked[_lockpoint]
+console.log('UNLOCK', unlock)
+                /* Validate transaction hash. */
+                if (inputs[_outpoint].tx_hash === unlock.tx_hash) {
+console.log('FOUND A MATCH', unlock.tx_hash)
+                    /* Set unlocking script. */
+                    inputs[_outpoint].unlocking = unlock.unlocking
+                }
+            })
+        })
+console.log('UPDATED FUSION', fusion)
+        return 'almost there...'
+    }
+
+
     fusion.numGuests = Object.keys(fusion.guests).length
 
     /* Update progress. */
@@ -130,7 +179,7 @@ console.log('FUSION', fusion)
             componentid = sha256(_component.tx_hash + ':' + _component.tx_pos)
             fusion.inputs[componentid] = {
                 ..._component,
-                signature: null,
+                unlocking: null,
             }
         }
 

+ 6 - 0
web/stores/wallet.ts

@@ -12,6 +12,7 @@ import _completeFusion from './wallet/completeFusion.ts'
 import _setEntropy from './wallet/setEntropy.ts'
 import _setupKeychain from './wallet/setupKeychain.ts'
 import _setupHushKeychain from './wallet/setupHushKeychain.ts'
+import _signFusion from './wallet/signFusion.ts'
 import _startFusion from './wallet/startFusion.ts'
 
 /* Initialize constants. */
@@ -479,6 +480,11 @@ _setupHushKeychain.bind(this)()
             return _startFusion.bind(this)()
         },
 
+        async signFusion() {
+            /* Start fusions. */
+            return _signFusion.bind(this)()
+        },
+
         async completeFusion() {
             /* Start fusions. */
             return _completeFusion.bind(this)()

+ 61 - 5
web/stores/wallet/completeFusion.ts

@@ -1,5 +1,8 @@
 /* Import modules. */
-import signSharedTx from '../../handlers/signSharedTx.ts'
+import { TransactionBuilder } from 'bitcoinjs-lib'
+import { hexToBin } from '@nexajs/utils'
+
+import buildSharedTx from '../../handlers/buildSharedTx.ts'
 
 const DUST_VAL = 546
 
@@ -90,11 +93,64 @@ export default async function () {
     // console.log('OUTPUTS (sorted)', sortedOutputs)
 
     /* Sign shared transaction. */
-    rawTx = signSharedTx(
-        sessionid, this.mnemonic, sortedInputs, sortedOutputs)
-    console.log('RAW TX', rawTx)
+    const transactionBuilder = buildSharedTx.bind(this)(
+        sessionid, sortedInputs, sortedOutputs)
+
+const script = Buffer.from(hexToBin(inputs['185ad6a10ea70d977d943a910f54dc446163a16771017c6df35c7893c1db0c35'].unlocking))
+console.log('SCRIPT', script)
+// transactionBuilder.transaction.tx.ins[0].script = script
+    console.log('FINALIZED TRANSACTION', transactionBuilder)
+
+// console.log('UNLOCKING', inputs['185ad6a10ea70d977d943a910f54dc446163a16771017c6df35c7893c1db0c35'])
+
+
+    const transaction = transactionBuilder.transaction.buildIncomplete()
+    // const transaction = transactionBuilder.transaction
+    console.log('TRANSACTION', transaction)
+    transaction.ins[0].script = script
+    console.log('TRANSACTION (hex)', transaction.toHex())
+
+    // transaction.ins[0].script = Buffer.from(hexToBin(inputs['185ad6a10ea70d977d943a910f54dc446163a16771017c6df35c7893c1db0c35'].unlocking))
+
+    // const transaction2 = transactionBuilder.transaction.build()
+
+    // const transactionBuilder2 = TransactionBuilder.fromTransaction(
+    //     transaction,
+    //     'mainnet'
+    //   )
+    // console.log('TX BUILDER-2', transactionBuilder2)
 
-    response = await this.broadcast('BCH', rawTx)
+      // build tx
+    //   const tx = transactionBuilder2.build()
+    //   const tx = transactionBuilder2.transaction.buildIncomplete()
+    //   console.log('TX BUILDER 2', tx)
+    //   // output rawhex
+    //   const txHex = tx.toHex()
+    //   console.log('TX HEX', txHex)
+
+    /* Convert to (raw) hex. */
+    // rawTx = tx.toHex()
+
+    response = await this.broadcast('BCH', transaction.toHex())
         .catch(err => console.error(err))
     console.log('BROADCAST (response)', response)
 }
+
+
+    // // Overwrite the tx inputs of the first partially-signed TX with the signed
+    // // inputs from the other two transactions.
+    // txObj.ins[1].script = txObj2.ins[1].script
+    // txObj.ins[2].script = txObj3.ins[2].script
+
+    // // console.log(`Fully-signed txObj.ins: ${JSON.stringify(txObj.ins, null, 2)}`)
+
+    // // Port the transaction object into the TransactionBuilder.
+    // const transactionBuilder = Bitcoin.TransactionBuilder.fromTransaction(
+    //   txObj,
+    //   'mainnet'
+    // )
+
+    // // build tx
+    // const tx = transactionBuilder.build()
+    // // output rawhex
+    // const txHex = tx.toHex()

+ 160 - 0
web/stores/wallet/signFusion.ts

@@ -0,0 +1,160 @@
+/* Import modules. */
+import { encryptForPubkey } from '@nexajs/crypto'
+import { binToHex } from '@nexajs/utils'
+
+import signSharedTx from '../../handlers/signSharedTx.ts'
+
+const DUST_VAL = 546
+
+export default async function () {
+    /* Initialize locals. */
+    let blindComponents
+    let clubWallet
+    let inputs
+    let keys
+    let outputs
+    let publicKey
+    // let rawTx
+    let response
+    let session
+    let sessionid
+    let transaction
+
+    // FIXME Where do we get the session ID from??
+    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 => {
+        /* Set input. */
+        const input = inputs[_keyid]
+        console.log('HANDLE INPUT', input)
+
+        let addressIdx = 0
+
+        /* Find address index for input. */
+        Object.keys(this.fusionInputs).forEach(_outpoint => {
+            /* Set fusion input. */
+            const fusionInput = this.fusionInputs[_outpoint]
+            console.log('FUSION INPUT', fusionInput)
+
+            if (fusionInput.address === input.address) {
+                input.address_idx = addressIdx
+                console.log('ADDED ADDRESS INDEX', addressIdx)
+                // break
+            }
+
+            addressIdx++
+        })
+
+        /* Add input. */
+        sortedInputs.push(input)
+    })
+    // 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. */
+    transaction = signSharedTx.bind(this)(
+        sessionid, this.mnemonic, sortedInputs, sortedOutputs)
+    console.log('SIGNED TRANSACTION', transaction)
+    // txObj.ins[1].script = txObj2.ins[1].script
+
+    /* Initailize unlocked. */
+    let unlocked = {}
+
+    transaction.ins.forEach(_input => {
+        /* Validate (unlocked) script. */
+        if (_input.script) {
+            /* Add input. */
+            unlocked[binToHex(_input.hash)] = {
+                tx_hash: binToHex(_input.hash.reverse()),
+                unlocking: binToHex(_input.script),
+            }
+        }
+    })
+    console.log('UNLOCKED', unlocked)
+
+    /* Prepare unlocked (inputs) for encryption. */
+    unlocked = JSON.stringify(unlocked)
+    // console.log('FUSION (unlocked)', unlocked)
+
+    // TODO Handle any filtering required BEFORE submitting for fusion.
+
+    clubWallet = await $fetch('/api/wallet')
+        .catch(err => console.error(err))
+    // console.log('CLUB WALLET', clubWallet)
+
+    // FIXME Retrieve public key from a "public" endpoint.
+    publicKey = clubWallet.publicKey
+    // console.log('CLUB PUBLIC KEY', publicKey)
+
+    /* Generate blind components. */
+    blindComponents = encryptForPubkey(publicKey, unlocked)
+    // console.log('BLINDED COMPONENTS', blindComponents)
+
+    const body = {
+        authid: binToHex(this.wallet.publicKey),
+        actionid: 'unlock-components',
+        // tierid,
+        unlocked: blindComponents,
+    }
+    console.log('BODY', body)
+
+    response = await $fetch('/v1', {
+        method: 'POST',
+        body,
+    })
+    .catch(err => console.error(err))
+    console.log('RESPONSE', response)
+}

+ 14 - 7
web/stores/wallet/startFusion.ts

@@ -31,7 +31,11 @@ export default async function () {
     const maxOutputCount = 17
 
     /* Clone fusion inputs. */
-    fusionInputs = [ ...this.fusionInputs ]
+    fusionInputs = []
+
+    Object.keys(this.fusionInputs).forEach(_outpoint => {
+        fusionInputs.push(this.fusionInputs[_outpoint])
+    })
 
     const tierScales = [
         10000,      12000,      15000,      18000,      22000,      27000,      33000,      39000,      47000,      56000,      68000,      82000,
@@ -148,14 +152,17 @@ export default async function () {
     blindComponents = encryptForPubkey(publicKey, components)
     // console.log('BLINDED COMPONENTS', blindComponents)
 
+    const body = {
+        authid: binToHex(this.wallet.publicKey),
+        actionid: 'submit-components',
+        tierid,
+        components: blindComponents,
+    }
+    // console.log('BODY', body)
+
     response = await $fetch('/v1', {
         method: 'POST',
-        body: {
-            authid: binToHex(this.wallet.publicKey),
-            actionid: 'submit-components',
-            tierid,
-            components: blindComponents,
-        },
+        body,
     })
     .catch(err => console.error(err))
     console.log('RESPONSE', response)