{"title":"TokenSell taker-side 干跑收口 runner 源码 v0.6(sha256 f680a743…)","subtitle":"","coverImg":"","contentType":"text/markdown","content":"// TokenSell · taker-side 签名接线层 干跑收口 runner(v0.6 · 2026-10-29)\n// 本文件 sha256 = f680a743a8045338ae9863d85a17ba0038a69712906365900f836798ef249381\n// 交接报告 pin://808f911e2379d7e6850693d00d7657f69de23192901a711fdd53d28aa4a8e214i0\n// 复现命令(零链写、零网络写、确定性密钥):node run-taker-dryrun.mjs\n// 退出码:全绿 0 / 有红 1。最后一行恒为 JSON(便于程序化解析)。\n// 干跑边界:全部本地构造(GEN→LIST→UNLOCK 三代真实字节链),零广播零真实资金。\n\nimport { createHash } from 'node:crypto';\nimport { readFileSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nconst require = createRequire(import.meta.url);\nconst MC = '/Users/wufeng/Library/Application Support/IDBots/SKILLs/nft-trade/node_modules/meta-contract/dist';\nconst mvc = require(`${MC}/mvc`);\nconst ftProto = require(`${MC}/mcp02/contract-proto/token.proto`);\n\nimport * as wiring from './tokensell-signature-wiring.mjs';\nimport * as skeleton from './tokensell-factory-skeleton.mjs';\n\nconst d256 = (b) => createHash('sha256').update(createHash('sha256').update(b).digest()).digest();\nconst hex = (b) => Buffer.from(b).toString('hex');\n\n// ---------- 台账 ----------\nconst ledger = [];\nfunction check(name, pass, detail = '') {\n ledger.push({ name, pass: !!pass, detail: String(detail) });\n console.log(`${pass ? 'PASS' : 'FAIL'} ${name}${detail ? ' — ' + detail : ''}`);\n}\nconst summarize = (o) => JSON.stringify(o);\n\ntry {\n // ========== A. 18 参表 ×双侧 desc 独立复核 ==========\n const descV1 = JSON.parse(readFileSync(`${MC}/mcp02/contract-desc/token_desc.json`, 'utf8'));\n const descV2 = JSON.parse(readFileSync(`${MC}/mcp02/contract-desc/token-v2_desc.json`, 'utf8'));\n const fnOf = (d) => d.abi.find((e) => e.type === 'function' && e.name === 'unlock');\n const sigOf = (d) => fnOf(d).params.map((p) => `${p.name}:${p.type}`);\n const table = skeleton.UNLOCK_PARAM_TABLE;\n check('A1 表长=18', table.length === 18, `len=${table.length}`);\n check('A2 表×token_desc(v1) 逐字一致', JSON.stringify(table) === JSON.stringify(sigOf(descV1)), '18/18 name:type');\n check('A3 表×token-v2_desc 逐字一致', JSON.stringify(table) === JSON.stringify(sigOf(descV2)), '18/18 name:type');\n check('A4 两代 desc ABI 互同', JSON.stringify(sigOf(descV1)) === JSON.stringify(sigOf(descV2)));\n check('A5 ⑫=TxInputProof 与⑥⑬⑰ TxOutputProof 异类', table[11] === 'tokenTxInputProof:TxInputProof' && table[11].endsWith('TxInputProof') && [5, 12, 16].every((i) => table[i].endsWith('TxOutputProof')), table[11]);\n\n // ========== B. 干跑密钥(确定性) ==========\n const seller = wiring.makeDryKeypair('seller');\n const buyer = wiring.makeDryKeypair('buyer');\n check('B1 地址双实现交叉核验', seller.b58 === seller.mvcLibAddr && buyer.b58 === buyer.mvcLibAddr, `${seller.b58}`);\n const priceSat = 123456;\n const tokenAmount = 1000000n;\n const seedDesc = JSON.parse(readFileSync(`${MC}/mcp02/contract-desc/tokenSell_desc.json`, 'utf8'));\n if (seedDesc.contract !== 'TokenSell') throw new Error('desc contract mismatch');\n\n // ========== C. GEN(卖家持仓 token,真实合约锁脚本) ==========\n const gen = wiring.buildGenTx({ sellerKey: seller, amount: tokenAmount });\n const genDp = ftProto.parseDataPart(gen.tokenUtxo.scriptBuf);\n const addrEq = String(genDp.tokenAddress) === seller.hash160Hex; // parseDataPart 返回 hex 字符串\n check('C1 GEN token 脚本 addr 槽=卖家', addrEq, hex(genDp.tokenAddress).slice(0, 12) + '…');\n const amtBuf = Buffer.alloc(8); amtBuf.writeBigUInt64LE(tokenAmount);\n check('C2 GEN token 金额槽=1,000,000', BigInt(String(genDp.tokenAmount)) === tokenAmount);\n check('C3 GEN tx 字节自洽', gen.txHex.length > 200 && gen.txId.length === 64, `txid=${gen.txId.slice(0, 12)}… len=${gen.txHex.length}`);\n\n // ========== D. LIST(上架·真实化;对照骨架 draftListTx) ==========\n const draft = skeleton.draftListTx({ oldTokenUtxo: { ...gen.tokenUtxo, script: gen.tokenUtxo.scriptBuf }, fundingUtxos: [wiring.fakeFundingUtxo(seller, 40000n, 1)], sellerB58: seller.b58, priceSat, tokenAmount: wiring.BN_SHIM(tokenAmount), desc: seedDesc });\n const list = wiring.buildRealListTx({ gen, sellerKey: seller, priceSat, tokenAmount, desc: seedDesc });\n check('D1 骨架×真实 合约锁字节一致', Buffer.from(draft.outputs[0].script).equals(list.contractLockBuf), `len=${list.contractLockBuf.length}`);\n check('D2 骨架×真实 合约持仓 token 字节一致', Buffer.from(draft.outputs[1].script).equals(list.tokenOutScriptBuf));\n const listTokenAddr = String(ftProto.parseDataPart(list.tokenOutScriptBuf).tokenAddress);\n check('D3 out1 addr 槽=ripemd160(合约锁)', listTokenAddr === createHash('ripemd160').update(list.contractLockBuf).digest().toString('hex'));\n // op=1 token 输入 preimage round-trip\n const lp = wiring.parsePreimage(list.wired.preimageHex, gen.tokenUtxo.scriptBuf.length);\n check('D4 preimage nVersion/nLockTime(MVC tx version=10)', hex(lp.nVersion) === '0a000000' && hex(lp.nLockTime) === '00000000', hex(lp.nVersion));\n check('D5 preimage sigHashType=ALL|FORKID', hex(lp.sigHashType) === '41000000', hex(lp.sigHashType));\n check('D6 preimage hashPrevouts=hash256(prevouts)', hex(lp.hashPrevouts) === hex(d256(Buffer.from(list.wired.prevoutsHex, 'hex'))));\n const serAll = Buffer.concat(list.composer.getTx().outputs.map((o) => wiring.serializeOutput(o.script.toBuffer(), o.satoshis)));\n check('D7 preimage hashOutputs=hash256(全部输出)', hex(lp.hashOutputs) === hex(d256(serAll)), `outputs=${list.composer.getTx().outputs.length}`);\n // op=1 独立验签(digest=sha256d(preimage),noble 第二实现)\n const vList = wiring.verifySigOverPreimage({ preimage: list.wired.preimageHex, sigHexWithSighash: list.wired.senderSig, pubHex: seller.pubHex });\n check('D8 op=1 senderSig 独立验签', vList.ok, vList.ok ? 'noble secp256k1 verify=true' : (vList.err || 'verify=false'));\n check('D9 解锁脚本含真 pubkey+真签名', list.wired.unlockScriptBuf.toString('hex').includes(seller.pubHex), `scriptLen=${list.wired.unlockScriptBuf.length}`);\n check('D10 费率收敛', list.fee >= BigInt(Math.ceil(list.vsize * wiring.FEEB)) && list.feeRounds <= 6, `fee=${list.fee} vsize=${list.vsize} rounds=${list.feeRounds}`);\n\n // ========== E. UNLOCK-BUY(吃单·op=2 + 种子 SIG_SINGLE) ==========\n const unlockBuy = wiring.buildRealUnlockTx({ list, sellerKey: seller, buyerKey: buyer, side: 'buy', priceSat, tokenAmount });\n const ub = unlockBuy;\n // 种子输入:SIGHASH_SINGLE 绑死 out1\n const sp = wiring.parsePreimage(ub.wired.preimageSeedHex, list.contractLockBuf.length);\n check('E1 种子 preimage sigHashType=SINGLE|FORKID', hex(sp.sigHashType) === '43000000', hex(sp.sigHashType));\n const out1 = ub.composer.getTx().outputs[1];\n const out1Expected = wiring.p2pkhScript(Buffer.from(seller.hash160Hex, 'hex'));\n check('E2 out1=P2PKH(卖家,要价) 字节级', out1.script.toBuffer().equals(out1Expected) && Number(out1.satoshis) === priceSat, `${priceSat}sats`);\n check('E3 SIG_SINGLE 绑定证明 hashOutputs=hash256(out1)', hex(sp.hashOutputs) === hex(d256(wiring.serializeOutput(out1Expected, priceSat))), '翻车位绑死');\n // token 输入(op=2)\n const tp = wiring.parsePreimage(ub.wired.preimageTokenHex, list.saleTokenUtxo.scriptBuf.length);\n check('E4 token preimage sigHashType=ALL|FORKID', hex(tp.sigHashType) === '41000000');\n check('E5 token preimage hashPrevouts=hash256(prevouts)', hex(tp.hashPrevouts) === hex(d256(Buffer.from(ub.wired.prevoutsHex, 'hex'))));\n const serAll2 = Buffer.concat(ub.composer.getTx().outputs.map((o) => wiring.serializeOutput(o.script.toBuffer(), o.satoshis)));\n check('E6 token preimage hashOutputs=hash256(全部输出)', hex(tp.hashOutputs) === hex(d256(serAll2)));\n // ⑰ contractTxProof 绑种子脚本(op=2 关键位)\n // getTxOutputProof 返回 {txHeader, hashProof, satoshiBytes, scriptHash};scriptHash=sha256(script)\n // String(Bytes) 为 b'…' repr,剥壳取 hex\n const b2h = (x) => { const t = String(x); return t.startsWith(\"b'\") ? t.slice(2, -1) : t; };\n const proofScriptHash = b2h(ub.wired.contractTxOutputProofRaw.scriptHash);\n const seedScriptHash = createHash('sha256').update(list.contractLockBuf).digest().toString('hex');\n check('E7 contractTxProof.scriptHash=sha256(种子锁脚本)', proofScriptHash === seedScriptHash, 'contractHash 绑定');\n const prevTokenScriptHash = b2h(ub.wired.prevTokenTxOutputProofRaw.scriptHash);\n const saleScriptHash = createHash('sha256').update(list.saleTokenUtxo.scriptBuf).digest().toString('hex');\n check('E7b prevTokenTxProof.scriptHash=sha256(卖单token脚本)', prevTokenScriptHash === saleScriptHash, '⑬ 绑定');\n const saleDp = ftProto.parseDataPart(list.saleTokenUtxo.scriptBuf);\n const saleAddrHex = String(saleDp.tokenAddress);\n check('E8 token addr 槽=ripemd160(种子锁)', saleAddrHex === createHash('ripemd160').update(list.contractLockBuf).digest().toString('hex'));\n // op=2 占位签名位(adapter 强制)\n const tokScriptHex = ub.wired.tokenUnlockScriptBuf.toString('hex');\n const phSig = '41682c2074686973206973206120706c61636520686f6c64657220616e642077696c6c206265207265706c6163656420696e207468652066696e616c207369676e61747572652e00';\n check('E9 op=2 签名位为占位(无真实 sig 泄入)', !tokScriptHex.includes(phSig) && (tokScriptHex.match(/0100/g) || []).length >= 2, `dummyPushes=${(tokScriptHex.match(/0100/g) || []).length}`);\n const chunkCount = mvc.Script.fromBuffer(ub.wired.tokenUnlockScriptBuf).chunks.length;\n check('E10 解锁尾 18 参入脚本', chunkCount >= 19, `chunks=${chunkCount}(selector+18)`);\n // out0:token 移转给买家\n const buyDp = ftProto.parseDataPart(ub.tokenOutScriptBuf);\n check('E11 out0 addr 槽=买家(buy)', String(buyDp.tokenAddress) === buyer.hash160Hex);\n check('E12 out0 金额守恒', BigInt(String(buyDp.tokenAmount)) === tokenAmount);\n const codePartLen = list.saleTokenUtxo.scriptBuf.length - (list.saleTokenUtxo.scriptBuf.length - saleDp.tokenAddress.length - 8 + 0) ; // 占位,不用于判定\n check('E13 out0 codePart 与卖单 token 同源', ub.tokenOutScriptBuf.subarray(0, 20).toString('hex') === list.saleTokenUtxo.scriptBuf.subarray(0, 20).toString('hex'), '头部 20B 同源抽查');\n // 费率 / 尘额\n check('E14 费率收敛', ub.fee >= BigInt(Math.ceil(ub.vsize * wiring.FEEB)) && ub.feeRounds <= 6, `fee=${ub.fee} vsize=${ub.vsize} rounds=${ub.feeRounds}`);\n const dustOk = ub.composer.getTx().outputs.every((o) => Number(o.satoshis) >= 600);\n check('E15 全输出 ≥ 600 sats(尘额)', dustOk, ub.composer.getTx().outputs.map((o) => Number(o.satoshis)).join('/'));\n // 骨架 draftUnlockTx 对照\n const duDraft = skeleton.draftUnlockTx({ sellTokenUtxo: { ...list.saleTokenUtxo, script: list.saleTokenUtxo.scriptBuf }, contractSeedUtxo: { ...list.seedUtxo, script: list.seedUtxo.scriptBuf }, side: 'buy', receiverB58: buyer.b58, mvcRecAddrB58: seller.b58, mvcRecAmount: priceSat, fundingUtxos: [wiring.fakeFundingUtxo(buyer, 60000n, 2)], oldTokenScriptForOut: list.saleTokenUtxo.scriptBuf });\n check('E16 骨架 unlockArgTail op=2 dummy', duDraft.unlockArgTail.dummyPlaceholders === true && duDraft.unlockArgTail.operation === 2);\n check('E17 骨架 contractIdx=1 与真实布局一致', duDraft.invariants.contractIdx === ub.seedIdx, `idx=${ub.seedIdx}`);\n\n // ========== F. UNLOCK-CANCEL(撤单:同管道,token 回卖家) ==========\n const unlockCancel = wiring.buildRealUnlockTx({ list, sellerKey: seller, buyerKey: buyer, side: 'cancel', priceSat, tokenAmount });\n const cDp = ftProto.parseDataPart(unlockCancel.tokenOutScriptBuf);\n check('F1 cancel out0 addr 槽=卖家(回自己)', String(cDp.tokenAddress) === seller.hash160Hex);\n check('F2 cancel 种子 SIG_SINGLE 绑定同绿', (() => { const p = wiring.parsePreimage(unlockCancel.wired.preimageSeedHex, list.contractLockBuf.length); const o1 = unlockCancel.composer.getTx().outputs[1]; return hex(p.sigHashType) === '43000000' && hex(p.hashOutputs) === hex(d256(wiring.serializeOutput(o1.script.toBuffer(), o1.satoshis))); })());\n check('F3 cancel 费率收敛', unlockCancel.fee >= BigInt(Math.ceil(unlockCancel.vsize * wiring.FEEB)), `fee=${unlockCancel.fee} vsize=${unlockCancel.vsize}`);\n\n // ========== G. 确定性重放(同输入 ⇒ 同字节) ==========\n const unlockBuy2 = wiring.buildRealUnlockTx({ list, sellerKey: seller, buyerKey: buyer, side: 'buy', priceSat, tokenAmount });\n check('G1 重放字节级一致', unlockBuy2.txHex === ub.txHex, `txid=${ub.txId.slice(0, 12)}…`);\n\n // ========== 汇总 ==========\n const failed = ledger.filter((x) => !x.pass);\n const summary = {\n ok: failed.length === 0, total: ledger.length, failed: failed.length,\n mode: 'DRY-RUN(零广播零链写;无真实资金)',\n artifacts: {\n gen_txid: gen.txId, list_txid: list.txId, unlock_buy_txid: ub.txId, unlock_cancel_txid: unlockCancel.txId,\n fees: { list: Number(list.fee), unlock_buy: Number(ub.fee), unlock_cancel: Number(unlockCancel.fee) },\n vsize: { list: list.vsize, unlock_buy: ub.vsize, unlock_cancel: unlockCancel.vsize },\n },\n param_table: 'UNLOCK_PARAM_TABLE 18/18 × 两代 desc 逐字一致',\n };\n console.log('\\n' + summarize(summary));\n process.exit(failed.length === 0 ? 0 : 1);\n} catch (e) {\n console.error('RUNNER-ERROR:', e && e.stack || e);\n console.log(summarize({ ok: false, error: String(e && e.message || e) }));\n process.exit(1);\n}","encryption":"0","createTime":1793285944830,"tags":["metatask","TokenSell","FT","ft-probe","taker-side","runner"],"attachments":[]}