选择
概览
Coinselect是一个为比特币交易管理而设计的TypeScript库. 它利用比特币描述符来定义输入和输出,使UTXO(Unspent Contraction Out)能够进行最佳选择并准确计算交易大小.
关于比特币描述词的导言,请参阅: @ bitcoinerlab/ 描述器 如果你不熟悉他们.
详细的API文件,访问 https://bitcoinerlab.com/modules/coinselect/api 页面存档备份,存于互联网档案馆。 。 。 .
特征
-
使用比特币描述标记来表示UTXOs和目标.
-
精确地计算出以UTXOs为单位的以小标注为基础的描述符的见证大小,即使存在多条支出路径. 用户可以使用@bitcoinerlab/scriptioners中概述的方法选择确定证人大小的具体支出路径. 下面的Miniscript一节有详细的例子.
-
防止产生低于尘分阈值的输出,而比特币节点通常不会中继.
v2.0.0 (突发变化)
- 所有佐藤标定的值现在
bigint(单位:千美元)utxos[].value, (中文(简体) ),targets[].value,更改值,fee,以及dustThreshold) (中文(简体) ). feeRate财务报告和审定财务报表dustRelayFeeRate保留number数值(sat/vB).- 项目与
@bitcoinerlab/descriptorsv3 API 和字节阵列使用量 (Uint8Array改为Buffer(在公共场合). - 通过磁盘脚本路径工作流程支持
tr(KEY,TREE)说明taprootSpendPath: 'script'和可选tapLeaf。 。 。 .
使用量
要启动, 首先安装必要的依赖性 :
npm install @bitcoinerlab/secp256k1 \
@bitcoinerlab/descriptors \
@bitcoinerlab/coinselect
这里有一个直接的例子,使用 addr 描述符 :
想象一下你有两个UTXO:
2000与地址一起坐在输出中bc1qzne9qykh9j55qt8ccqamusp099spdfr49tje60。 。 。 .4000与地址一起坐在输出中12higDjoCCNXSA95xZMWUdPvXNmkAduhWv。 。 。 .
假设你想送 3000 坐过 bc1qxtuy67s0rnz7uq2cyejqx5lj8p25mh0fz2pltm 并想要发送到 bc1qwfh5mj2kms4rrf8amr66f7d5ckmpdqdzlpr082。这是你如何实现的:
import * as secp256k1 from '@bitcoinerlab/secp256k1';
import { DescriptorsFactory } from '@bitcoinerlab/descriptors';
const { Output } = DescriptorsFactory(secp256k1);
import { coinselect } from '@bitcoinerlab/coinselect';
const { utxos, targets, fee, vsize } = coinselect({
utxos: [
{
output: new Output({ descriptor: 'addr(bc1qzne9qykh9j55qt8ccqamusp099spdfr49tje60)' }),
value: 2000n
},
{
output: new Output({ descriptor: 'addr(12higDjoCCNXSA95xZMWUdPvXNmkAduhWv)' }),
value: 4000n
}
],
targets: [
{
output: new Output({ descriptor: 'addr(bc1qxtuy67s0rnz7uq2cyejqx5lj8p25mh0fz2pltm)' }),
value: 3000n
}
],
remainder: new Output({ descriptor: 'addr(bc1qwfh5mj2kms4rrf8amr66f7d5ckmpdqdzlpr082)' }),
feeRate: 1.34
});
此代码产生以下结果:
{
"utxos": [
{
"output": {}, // The same OutputInstance as above: utxos[0].output
"value": 4000n
}
],
"targets": [
{
"output": {}, // The same OutputInstance as above: targets[0].output
"value": 3000n
},
{
"output": {}, // A new OutputInstance corresponding to the change
// address passed in remainder
"value": 705n // The final change value that you will receive
}
],
"fee": 295n, // The theoretical fee to pay (approximation before tx signing)
"vsize": 220 // The theoretical virtual size (in bytes) of the tx
// (approximation before tx signing)
}
关于无解决方案的说明 : 若为 coinselect 和此库中的类似算法无法为指定目标找到可行的UTXO组合,它们返回 undefined,这意味着交易在给定的投入和制约下不可行. 确保在您的代码中处理此类情况,也许可以通过通知用户或修改输入参数.
说明 addr()数字 : 使用时 addr(SH_ADDRESS) 说明书,图书馆假设他们代表Segwit SH_WPKH_ADDRESS对于脚本,请使用 sh(MINISCRIPT) 描述符格式。 同样,在使用时 addr(TR_ADDRESS) 描述符,库中假设它们代表了BIP86中定义的单键 Tapproot地址,没有脚本路径.
此外,如果你只需要计算 vsize 对于一组具体的投入和产出,可以使用以下方法:
import { vsize } from '@bitcoinerlab/coinselect';
const numBytes = vsize(
[
new Output({ descriptor: 'addr(bc1qzne9qykh9j55qt8ccqamusp099spdfr49tje60)' }),
new Output({ descriptor: 'addr(12higDjoCCNXSA95xZMWUdPvXNmkAduhWv)' })
],
[ new Output({ descriptor: 'addr(bc1qxtuy67s0rnz7uq2cyejqx5lj8p25mh0fz2pltm)' }) ]
);
对于脚本/脚本-路径支出,请明确使用 tr(KEY,TREE) 描述符 :
import { vsize } from '@bitcoinerlab/coinselect';
const INTERNAL_KEY = 'a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd';
const LEAF_KEY_A = '669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0';
const LEAF_KEY_B = 'c6e26fdf91debe78458853f1ba08d8de71b7672a099e1be5b6204dab83c046e5';
const tapscriptUtxo = new Output({
descriptor: `tr(${INTERNAL_KEY},{pk(${LEAF_KEY_A}),pk(${LEAF_KEY_B})})`,
taprootSpendPath: 'script',
tapLeaf: `pk(${LEAF_KEY_A})`
});
const estimatedVsize = vsize(
[tapscriptUtxo],
[new Output({ descriptor: `tr(${INTERNAL_KEY})` })]
);
经过图书馆的基本使用,现在让我们集中研究一些基本的概念,这些概念可以增进你们对图书馆的理解和有效利用. 其中包括理解 尘埃,管理 收费率和虚拟大小,并掌握 可变性。 。 。 .
尘埃
如果你付出更多的费用 比产出的价值 花费的东西, 那么这被认为是灰尘.
为了确保交易能够被比特币节点所接通,这个库执行一个灰尘阈值. 产出低于这一门槛值的交易可能不被网络接受。 库主动地为尘分阈值以下的目标投出出错误来防止此类交易的产生. 此外,库如果低于尘分阈值,则不会产生变化.
使用 dustThreshold 函数在设定目标前确保输出值超过最小阈值(minOutputValue:
import { dustThreshold } from '@bitcoinerlab/coinselect';
const minOutputValue = dustThreshold(
new Output({ descriptor: 'addr(bc1qzne9qykh9j55qt8ccqamusp099spdfr49tje60)' })
);
关于更详细的解释,请参阅: aPI 文档。 。 。 .
收费率和虚拟大小
默认, feeRate 以最低 0.1 用于TRUC
交易时,您可以通过路由降低验证层 minimumFeeRate数字 :
const selected = coinselect({
utxos,
targets,
remainder,
feeRate: 0,
minimumFeeRate: 0
});
费率(fee / vsize页:1 coinselect 可能高于指定的 feeRate。这种差异是由于四舍五入效应(目标值为整数satoshis,表示为 bigint如第一个代码片段所示,如果变化低于尘分阈值,则不产生变化的可能性.
签约后,决赛 vsize 可能低于 coinselect()编号 :vsize()。这是因为 vsize 假设 DER 编码签名为 72 字节,尽管它们偶尔可以是 71 字节。 因此,最后决定 feeRate 可能超过预签估计数。 总而言之, feeRateAfterSigning >= (fee / vsize) >= feeRate。 。 。 .
可变性
此图书馆所遵循的 二. 可实现性原则 用于更平滑地与反应UI框架进行整合.
- 已返回
utxos财务报告和审定财务报表targets除非修改(例如去除自定义或增加目标变化),否则保留其原始引用. - 要检测是否添加了更改地址, 请比较
inputTargets与outputTargets。类似地,要检查是否选择了所有的UTXO,请比较inputUtxos与outputUtxos。 。 。 . - 若为
inputTargets与outputTargets中,它表示在remainder。修改总是在targets阵列. - 识别已丢弃的 UTXOs( 当
inputUtxos≠outputUtxos),使用:nonSelectedUtxos = inputUtxos.filter(utxo => !outputUtxos.includes(utxo))。 。 。 .
算法
此库中的 UTXO 选择算法从中汲取灵感 比特币/硬币选择。 。 。 .
我们感谢并赞赏比特币-利布小组的重大贡献。 近十年来,他们的算法在众多钱包和项目中都起了作用.
默认算法
默认算法 coinselect 库,如前一例所示,按其下行净值排序的UTXO(每个UTXO的值减去花费它所需的费用)。 它最初试图利用 avoidChange 算法,旨在选择不需要更改的UTXOs。 如果无法做到这一点,则适用 addUntilReach 算法,它会添加UTXOs,直到总值超过目标值加费用. 入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入出入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入入 这个 avoidChange 财务报告和审定财务报表 addUntilReach 以后各节将进一步阐述算法.
请注意,在选择过程中,对每个UTXO对交易费的贡献进行评估. 未提供足够价值以支付各自缴费的UTXO自动排除在外. 这一排除标准适用于库内的所有算法,以确保有效的交易管理.
发送最大基金
这个 maxFunds 算法是将所有可用的资金从UTXOs转移给指定的接收者的理想. 要使用此算法,请在 remainder。如果需要,还可以设定额外的固定价值目标.
如果 remainder 值会低于灰尘阈值,函数返回 undefined。 。 。 .
示例
import { maxFunds } from '@bitcoinerlab/coinselect';
const { utxos, targets, fee, vsize } = maxFunds({
utxos: [
{
output: new Output({ descriptor: 'addr(bc1qzne9qykh9j55qt8ccqamusp099spdfr49tje60)' }),
value: 2000n
},
{
output: new Output({ descriptor: 'addr(12higDjoCCNXSA95xZMWUdPvXNmkAduhWv)' }),
value: 4000n
}
],
targets: [
// Additional fixed-value targets can be included here
],
remainder: new Output({ descriptor: 'addr(bc1qwfh5mj2kms4rrf8amr66f7d5ckmpdqdzlpr082)' }),
feeRate: 1.34
});
接受方(剩余方)的值通过减去下列数值的总和来确定: targets 编辑 fee 从总价值中 utxos。要访问收件人的值,请使用 targets[targets.length - 1].value。 。 。 .
避免更改
这个 avoidChange 函数寻求选择不需要更改的UTXO。 虽然函数签名与标准相同 coinselect (要求a) remainder),变化永远不会被创建. 这个 remainder 用于评估假设的改变是否不被视为尘埃,因此一种解决办法不可行.
3⁄4 ̄ ̧漯B.
添加到到达
类似于默认 coinselect,则 addUntilReach 算法持续地增加UTXO,直到综合值超过目标加费用. 此函数在选中前不重排 UTXOs 。 它评估产生变化是否可行,同时考虑到变化是否超过粉尘阈值.
相对于比特币/硬币选择的差异
此库中的算法灵感来自 bitcoinjs/coinselect。默认算法对应主 coinselect 比特币的算法。 同样, avoidChange 改编自比特币的 blackjack,以及 addUntilReach 这是基于他们的 accumulative 算法.
尽管有相似之处,但比特币/币选有显著区别:
- 和比特币js一样,这个库如果值低于一定阈值,则避免产生变化. 在这里,门槛被称为
dustThreshold(详见前几节),与Bitcoin Core的执行一致. 相比之下,比特币/硬币选择使用一个与指定值相关的阈值feeRate。 。 。 . - 如果目标值低于
dustThreshold。 。 。 . - 它使用描述符来定义输入和输出脚本类型,而不是比特币-lib中P2PKH的假设. 在比特币js-lib中也有可能支持Segwit。 。 。 .
- 小数
feeRate支持数值.
最小标记
该库支持通过基于Miniscript的描述符处理任意的脚本,提供超越标准地址用法的高级功能.
理解最小标记
Miniscript是表达比特币脚本的结构化方式,可以增强脚本写作的可读性和灵活性. 对主要关心标准地址的人来说,如前所述,这种先进的特征可能没有必要。 然而,Miniscript为脚本的创作和解释提供了更广泛的可能性.
最小描述示例
考虑代表UTXO的最小描述符:
wsh(
andor(
pk(Key1),
older(10),
pkh(Key2)
)
)
在这个例子中, Key1 财务报告和审定财务报表 Key2 是可用各种形式表示的关键表达式,包括: 六分码公用钥匙或使用BIP32标记以下各节将举例说明这一点.
上面的描述语定义了对一个UTXO的控制,具有两个开销条件:
-
第一处数字 :
- 私人密钥的签名, 对应
Key1。 。 。 . - 自UTXO上一次交易被开采后再等10个街区.
- 私人密钥的签名, 对应
-
第二处数字 :
- 私人密钥的签名, 对应
Key2。 。 。 .
- 私人密钥的签名, 对应
涉及 Miniscript UTXO 的交易的大小取决于所选择的支出路径,因为所需要的目击证人各不相同.
脚本翻译和目击大小
上面的迷你文字翻译为以下比特币证人Script/lockingScript: <Key1> OP_CHECKSIG OP_NOTIF OP_DUP OP_HASH160 <HASH160(Key2)> OP_EQUALVERIFY OP_CHECKSIG OP_ELSE 10 OP_CHECKSEQUENCEVERIFY OP_ENDIF。 。 。 .
要使用第一个分支,脚本Witness/解锁脚本应该是: <sig(Key1)> (带tx) nSequence 设置为 10) (中文(简体) ). 对于第二分支,脚本Witness/解锁脚本是: <sig(Key2)> <Key2> 0。请注意,投入的权重因支出路径不同而不同,影响了整个交易规模.
在UTXOs中指定开支处
这个 @ bitcoinerlab/ 描述器 库提供了一种在Miniscript中指定支出路径的方法。 例如,在创建 Output,您可以指定想要的支出路径:
import * as secp256k1 from '@bitcoinerlab/secp256k1';
import { OutputInstance, DescriptorsFactory } from '@bitcoinerlab/descriptors';
const { parseKeyExpression, Output } = DescriptorsFactory(secp256k1);
const Key1: string = "[73c5da0a/44'/0'/0']xpubDC5FSnBiZDMmhiuCmWAYsLwgLYrrT9rAqvTySfuCCrgsWz8wxMXUS9Tb9iVMvcRbvFcAHGkMD5Kx8koh4GquNGNTfohfk7pgjhaPCdXpoba/0/0"
const Key2: string = ...; // Some other Key Expression...
const pubKey1: Uint8Array = parseKeyExpression({ keyExpression: Key1 }).pubkey;
const output: OutputInstance = new Output({
descriptor: `wsh(andor(pk(${Key1}),older(10),pkh(${Key2})))`,
signersPubKeys: [pubKey1]
});
此设置通知 OutputInstance ,使硬币选择算法能够精确地计算出证人大小。 库基于以下假设构建一个满意度(解锁脚本/scriptWitness): signersPubKeys 必须使用,用这些签名选择尽可能尺寸高效的证人。 如果支出路径取决于预图,您还应当包括: preimages 参数。 关于详细理解,请参见: API 文档。 。 。 .
需要注意的是,在计算证人人数时,实际的签名还没有出现. 因此,假设是72字节的DER编码签名。 因此,交易签署后的最后规模可能略小于最初的估计.
与 Coinselect 算法整合
一旦你准备好 OutputInstance 您可以在定义的支出路径中输入 coinselect 算法。 包括这个 Output 作为《京都议定书》 utxos 矩阵及其相应的值。 这个 coinselect 然后算法会考虑这一点 Output 在选择过程中,在指定的支出路径和目击大小中考虑准确和优化交易计算.
例如:
import { coinselect } from '@bitcoinerlab/coinselect';
// Assuming 'output' is the OutputInstance from the previous example
// and 'outputValue' is the value associated with this output
const result = coinselect({
utxos: [
{ output: output, value: outputValue },
// ... other UTXOs
],
// ... targets, remainder, and feeRate
});
通过这种方式 coinselect 算法可以有效处理复杂的脚本条件,确保交易按照您指定的要求高效和准确地构建.
作者和贡献者
该项目最初是开发的,目前由 何塞-路易斯·兰达巴索欢迎其他开发者的贡献和帮助.
在实施任何代码之前,确保它通过所有测试:
npm run test
以下是一些帮助您开始捐款的资源:
从源头建楼
要下载源代码并构建项目,请遵循这些步骤:
- 清除仓库 :
git clone https://github.com/bitcoinerlab/coinselect.git
- 安装依赖性 :
npm install
- 构建工程 :
npm run build
这将构建工程并生成必要的文件 dist 目录.
许可证
这个项目根据麻省理工学院的许可证获得许可.