免责声明
NKaspa 目前是 alpha 版本(0.1.11-alpha),未经安全审计。它是社区项目,不是 Kaspa 官方项目。本教程基于离线样例samples/PsktMultisig;脚本或阈值写错可能导致资金无法花费。请先用小额在 Kaspa testnet 验证。不要把本文理解成对任何钱包、硬件或其他项目的安全/性能评价。
想在 C# 里试 Kaspa 多签?Kaspa 的部分签名交易叫 PSKT,不是比特币的 PSBT。本文用 NKaspa 仓库里的离线样例 samples/PsktMultisig,带你走一遍 Kaspa PSKT 多签的 2-of-2 Schnorr 流程:创建、双方签名、合并、终结、提取,最后在本地验签。全程离线,不广播任何交易。文中代码对照 NKaspa 0.1.11-alpha(NuGet 包与 GitHub tag v0.1.11-alpha 中都包含这个样例和对应测试 tests/NKaspa.Tests/PsktTests.cs)。
PSKT 不是 PSBT
- 格式不同。 在 NKaspa 里,PSKT 序列化后以
PSKT开头,多个 PSKT 打包成 Bundle 时以PSKB开头。不要把比特币 PSBT 的字节当成 PSKT 用。 - 角色流程。 样例按「创建 → 双方签名 → 合并 → 终结 → 提取」的顺序走,对应 Creator、Constructor、Updater、Signer、Combiner、Finalizer、Extractor 这些角色。角色只存在于内存里的对象上,不会写进 PSKT 的 JSON;从十六进制读回一个 PSKT 时,它处在 Combiner 角色。
- 签名类型。 本文的样例是 2-of-2 Schnorr 多签,赎回脚本最后一个操作码是
OP_CHECKMULTISIG,第一个是OP_2(阈值 2)。
五分钟跑通样例
先克隆仓库,直接运行样例(只需 .NET 8 SDK,不连网也能跑):
git clone https://github.com/lifeme2027/NKaspa.git cd NKaspa dotnet run --project samples/PsktMultisig 输出里会依次出现这些关键行:
step: create step: sign party-a-sigs: 1 party-b-sigs: 1 step: combine combined-sigs: 2 step: finalize step: extract broadcast: no verified: true ok 样例还会打印 PSKT 的 JSON、交易 id 和交易十六进制,这些都只是本机离线算出来的结果,没有提交到任何网络。
分步代码
如果想在自己的项目里写一遍,先装包:
dotnet add package NKaspa --version 0.1.11-alpha 下面的代码和 samples/PsktMultisig/Program.cs 的流程一致,用的是样例里的公开测试私钥,不是钱包,不能存放任何真实资金。
步骤 1:准备两把测试密钥和 2-of-2 赎回脚本
using NKaspa; using NKaspa.Crypto; using NKaspa.PSKT; using KaspaPskt = NKaspa.PSKT.PSKT; // 公开测试标量,仅用于演示 using var partyA = Key.Parse("0000000000000000000000000000000000000000000000000000000000000003"); using var partyB = Key.Parse("0000000000000000000000000000000000000000000000000000000000000005"); using var payeeKey = Key.Parse("0000000000000000000000000000000000000000000000000000000000000007"); // 公钥顺序要固定,各方必须用同一顺序 var redeem = KaspaPskt.MultisigRedeemScript(2, new[] { partyA.PubKey, partyB.PubKey }); var utxo = new UtxoEntry(100_000_000UL, ScriptPubKey.PayToScriptHash(redeem), 20, false); 关于 using KaspaPskt = … 这个别名:如果你的代码写在 namespace NKaspa 里面,裸名 PSKT 会被当成命名空间,样例因此用了别名。直接照抄这个写法最省事。
步骤 2:创建 PSKT,分发未签名的十六进制
var input = new PSKTInput(new OutPoint(new uint256(8), 0)) // 演示用的假 outpoint { Utxo = utxo, RedeemScript = redeem, SigOpCount = 2, }; var created = KaspaPskt.Create() .Constructor() .AddInput(input) .AddOutput(new PSKTOutput(90_000_000UL, payeeKey.PubKey.GetScriptPubKey())) .Updater() .Signer(); string unsignedHex = created.ToHex(); // 以 "PSKT" 开头,发给每个签名方 关于手续费:这个样例没有调用任何费率计算;手续费就是输入金额减输出金额,此处为 10,000,000 sompi(0.1 KAS,演示值,testnet)。上链前请按实际 mass 用 NKaspa 的手续费接口自行计算。
步骤 3:双方各自签名
每一方只拿到未签名的十六进制,签完再交回自己那一份:
var aux = new byte[32]; // 全 0 只为让签名可复现;正式使用时不要传固定值,让它随机 var signedA = KaspaPskt.FromHex(unsignedHex).Signer().Sign(partyA, aux); var signedB = KaspaPskt.FromHex(unsignedHex).Signer().Sign(partyB, aux); // 此时 signedA、signedB 各自只有 1 个部分签名 步骤 4:合并、终结、提取
var combined = KaspaPskt.FromHex(signedA.ToHex()) .Combiner() .Combine(KaspaPskt.FromHex(signedB.ToHex())); // 合并后有 2 个部分签名 var finalized = combined.Finalizer().Finalize(); Transaction tx = finalized.Extractor().ExtractTransaction(); // tx.GetId() 与 finalized.Global.Id 相同;这里不广播 步骤 5:在本地验签
var hash = tx.GetSignatureHash(0, utxo, SigHashType.All).ToBytes(); var ops = tx.Inputs[0].SignatureScript.Ops; // 两个签名 + 赎回脚本 bool okA = partyA.PubKey.Verify(new SchnorrSignature(ops[0].Data!.AsSpan(0, 64)), hash); bool okB = partyB.PubKey.Verify(new SchnorrSignature(ops[1].Data!.AsSpan(0, 64)), hash); bool redeemOk = ops[2].Data!.AsSpan().SequenceEqual(redeem.ToBytes()); 三个值都为 true,就说明提取出的交易里两份签名都对得上,赎回脚本也正确放在了最后。样例到这里结束,不会调用任何提交交易的接口。
步骤 6:签名不够时 Finalize 会拒绝
只拿一方的签名去终结,会抛出 PSKTException:
try { KaspaPskt.FromHex(signedA.ToHex()).Finalizer().Finalize(); } catch (PSKTException) { // 2-of-2 只有 1 个签名,不能终结 } 仓库测试 PsktTests.Finalize_fails_without_utxo_redeem_script_or_enough_signatures 还覆盖了缺 UTXO、缺赎回脚本这两种失败情况。
安全默认
- 本教程全程离线、不广播。
- 如果你要把交易提交到链上,请只用 testnet(
kaspatest:地址)和小额。多签脚本、公钥顺序或阈值写错,都可能让资金无法花费。 - 不要在正式使用中固定
aux,也不要把测试向量私钥用于任何真实资金。 - NKaspa 是 alpha、未审计、非官方的社区库。
常见问题(FAQ)
Kaspa 多签用 PSBT 吗?
不用。Kaspa 这边是 PSKT,NKaspa 里序列化后以 PSKT 开头,Bundle 以 PSKB 开头。不要把比特币 PSBT 字节当 PSKT 用。
最小的 Kaspa 多签例子在哪?
在 NKaspa 仓库的 samples/PsktMultisig。它离线走完 2-of-2 Schnorr 的创建、双方签名、合并、终结、提取,并打印 broadcast: no。
怎么在 C# 项目里安装 NKaspa?
运行 dotnet add package NKaspa --version 0.1.11-alpha,或者克隆仓库用项目引用。库目前是 alpha 版本。
这个样例能直接广播吗?
不能,样例不广播。如果你自己把交易提交上链,请只用 testnet 和小额,并自行承担脚本写错的风险。
MultisigRedeemScript 只能做 2-of-2 吗?
本教程只演示样例里已验证的 2-of-2 Schnorr;其他门限组合不在本文范围内。使用前请先在离线和 testnet 上自测。
NKaspa 的 PSKT 和 rusty-kaspa 的字节一致吗?
本文不做互通认证。PsktTests 的说明写明:rusty-kaspa 的 PSKT 测试和多签示例使用随机密钥,没有钉死的字节向量可直接对照;NKaspa 只把空 PSKT 文档按字段顺序钉死,其余文档做的是读写往返测试。
能直接在主网放大额资金吗?
不建议。请先离线跑通,再在 testnet 用小额自行验证。
下一步
- 用 C# 连接 Kaspa 节点并发交易(dev.to 教程):中文
- DAA score、确认数与时间锁 FAQ
先离线跑通:dotnet run --project samples/PsktMultisig(github.com/lifeme2027/NKaspa)
有用就给 NKaspa 点个 Star;遇到问题欢迎开 issue。
来源(访问日期 2026-11-09)
- 样例:https://github.com/lifeme2027/NKaspa/blob/v0.1.11-alpha/samples/PsktMultisig/Program.cs
- 测试:https://github.com/lifeme2027/NKaspa/blob/v0.1.11-alpha/tests/NKaspa.Tests/PsktTests.cs
- NuGet 版本列表:https://api.nuget.org/v3-flatcontainer/nkaspa/index.json
免责声明
NKaspa 目前是 alpha 版本(0.1.11-alpha),未经安全审计。它是社区项目,不是 Kaspa 官方项目。本教程基于离线样例samples/PsktMultisig;脚本或阈值写错可能导致资金无法花费。请先用小额在 Kaspa testnet 验证。不要把本文理解成对任何钱包、硬件或其他项目的安全/性能评价。

