@borislav.itskov/schnorrkel.js

中文文档
3.1.2发布于 2025/06/26ISCTypeScriptCommonJS近 30 天下载 3842

一个javaScript库,用于签名和验证施诺尔签名. 它可用于单签名和多签名. 通过ecrecover进行区块链验证也得到了支持.

中文使用文档
文档目录10 节
  1. 2.0 版本数 中断更改
  2. 版本 3.0 中断更改
  3. 所需经费:
  4. 安装
  5. 测试
  6. 使用量
  7. 单一签名
  8. 多边形
  9. 多图像
  10. 隐形

Schnor 签名

一个javaScript库,用于签名和验证施诺尔签名.
它可用于单签名和多签名.
通过ecrecover进行区块链验证也得到了支持.

字体支持

从2.0版本开始,我们完全转到了Typescript.

2.0 版本数 中断更改

  • sign() 财务报告和审定财务报表 multiSigSign() 返回实例 SignatureOutput中每个元素都有缓冲属性
    • 改为 e 我们回来了 challenge 为施诺尔挑战赛。 要达到它的价值,使用 challenge.buffer
    • 改为 s 我们回来了 signature 为施诺尔签名。 要达到它的价值,使用 signature.buffer
    • 改为 R 我们回来了 publicNonce 为非斯. 要达到它的价值,使用 publicNonce.buffer
  • getCombinedPublicKey() 返回 a Key 课。 要得到真正的钥匙,使用 key.buffer
  • 许多方法变得静止 因为他们没有保持任何状态:
    • verify
    • sign
    • sumSigs
    • getCombinedPublicKey
    • getCombinedAddress

版本 3.0 中断更改

  • finalPublicNonce, (中文(简体) ), FinalPublicNonce 在所有地方替换为 publicNonce, (中文(简体) ), PublicNonce旧名只是没有道理.
  • sign() 是第一个 signHash()。接受纯文本消息作为参数的符号函数已不存在.
  • multiSigSign() 是第一个 multiSigSignHash()。接受纯文本消息作为参数的符号函数已不存在.
  • verify() 是第一个 verifyHash()。接受纯文本消息作为参数的校验功能已不复存在.

在第二版中,我们有许多方法可以签署一个信息。 对于在各种情况下使用何种职能是恰当的,这种广泛的混淆。 这使我们相信,使事情简单化并迫使散列成为方法是前进的道路.

所需经费:

  • 节点:QQ16.0.0, <20.0.0
  • npm (Node.js软件包管理器) v9.x.x

安装

git clone https://github.com/borislav-itskov/schnorrkel.js
cd schnorrkel.js
npm i

测试

npm run test

使用量

单一签名

我们称单一签字为单一签字.

签名 :

import { SchnorrSigner } from "@borislav.itskov/schnorrkel.js";
import { hexlify, randomBytes, hashMessage } from "ethers/lib/utils";

const privateKey = hexlify(randomBytes(32));
const signer = new SchnorrSigner(pk1);
const msg = "test message";
const commitment = hashMessage(msg);
const signature = signer.sign(commitment);

离链核查: 我们拿 signature 财务报告和审定财务报表 hash 从上文的例子来看

const result = signer.verify(hash, signature);

链上核查:

首先,你需要一个合同 核实Schnorr。 我们把它藏在仓库里 叫做 SchnorrAccountAbstraction。 。 。 .
但总之,你需要这个链子:

function verifySchnorr(bytes32 hash, bytes memory sig) internal pure returns (bool) {
    // px := public key x-coord
    // e := schnorr signature challenge
    // s := schnorr signature
    // parity := public key y-coord parity (27 or 28)
    (bytes32 px, bytes32 e, bytes32 s, uint8 parity) = abi.decode(sig, (bytes32, bytes32, bytes32, uint8));
    // ecrecover = (m, v, r, s);
    bytes32 sp = bytes32(Q - mulmod(uint256(s), uint256(px), Q));
    bytes32 ep = bytes32(Q - mulmod(uint256(e), uint256(px), Q));

    require(sp != Q);
    // the ecrecover precompile implementation checks that the `r` and `s`
    // inputs are non-zero (in this case, `px` and `ep`), thus we don't need to
    // check if they're zero.
    address R = ecrecover(sp, parity, px, ep);
    require(R != address(0), "ecrecover failed");
    return e == keccak256(abi.encodePacked(R, uint8(parity), px, hash));
}

我们来解释神秘感是如何起作用的 为什么以后需要它 在本文档中。 。 。 .
让我们向当地的硬帽节点发出一个请求. 终点站首跑:
npx 硬帽子节点
然后,这里是代码的一部分:

import { SchnorrSigner } from "@borislav.itskov/schnorrkel.js";
import { hexlify, randomBytes, hashMessage } from "ethers/lib/utils";
import { ContractFactory } from "ethers";

const privateKey = hexlify(randomBytes(32));
const signer = new SchnorrSigner(privateKey);
const factory = new ContractFactory(
  SchnorrAccountAbstraction.abi,
  SchnorrAccountAbstraction.bytecode,
  wallet
);
const contract: any = await factory.deploy([signer.getSchnorrAddress()]);
const msg = "just a test message";
const commitment = hashMessage(msg);
const sig = signer.sign(commitment);
const result = await contract.isValidSignature(
  commitment,
  signer.getEcrecoverSignature(sig)
);

您可以在 tests/schnorrkel/onchainSingleSign.test.ts 在此仓库中.

多边形

Schnorr 的多签名基于 n/n - 所有签名者都需要签名才能使签名有效.
下面是制定成功的多步骤.

多图像

为了让多西格更容易,a SchnorrMultisigProvider 类被创建。 它期待所有 SchnorrProvider 参加多西格的对象。 这个 SchnorrProvider 可以自己通过 或者一个人可以使用 SchnorrSigner。最刻薄的一点是,你不需要拥有私钥即可使用 SchnorrMultisigProvider。目标是为获取正确的链上 schnor 地址提供辅助功能,即所有签名者的综合公钥,并为获取预期的链上结构进行验证提供方便的方法

import {
  SchnorrSigner,
  SchnorrMultisigProvider,
} from "@borislav.itskov/schnorrkel.js";

const signerOne = new SchnorrSigner(pk1);
const signerTwo = new SchnorrSigner(pk2);
const multisigProvider = new SchnorrMultisigProvider([signerOne, signerTwo]);

公众非

公开的非ce需要在签字人之间进行交换后签字. 你可以用两种方式做到这一点.
使用多相图

const publicNonces = multisigProvider.getPublicNonces();

或手动给每个签名人单独打电话:

const publicNonces = [signerOne.getPublicNonces(), signerOne.getPublicNonces()];

在签署开始之前,非斯需要被交换. 还有.., getPublicNonces 在所有签署人完成签署程序之前,不应再次打电话。 或者至少一个人应该注意不要将公共非ces与新产生的非ces相混. 对于混血儿,签名不会奏效.

签名

以下是签字过程的例子。 公用密钥和公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用非公用.

import { solidityKeccak256 } from "ethers/lib/utils";

const msg = "just a test message";
const msgHash = solidityKeccak256(["string"], [msg]);
const publicKeys = multisigProvider.getPublicKeys();
const publicNonces = multisigProvider.getPublicNonces();
const signature = signerOne.sign(msgHash, publicKeys, publicNonces);
const signatureTwo = signerTwo.sign(msgHash, publicKeys, publicNonces);

校验链上

用于链上核查的编码数据的生成有些复杂,因此,它隐藏在 multisigProvider。 。 。 .
以下是如何使用提供者进行链上核查的例子:

const ecRecoverSchnorr = multisigProvider.getEcrecoverSignature([
  signature,
  signatureTwo,
]);
const result = await contract.isValidSignature(msgHash, ecRecoverSchnorr);

这里也有一个例子,说明没有多相传输器,你如何做到这一点:

import Schnorrkel from "@borislav.itskov/schnorrkel.js";
import { defaultAbiCoder } from "ethers/lib/utils";

const publicKeys = [signerOne.publicKey, signerTwo.publicKey];
const publicKey = arrayify(Schnorrkel.getCombinedPublicKey(publicKeys).buffer);
const sigOutputs = [signature, signatureTwo];
const sSummed = Schnorrkel.sumSigs(
  sigOutputs.map((output) => output.signature)
);
const challenge = sigOutputs[0].challenge;
const px = publicKey.slice(1, 33);
const parity = publicKey[0] - 2 + 27;
const ecRecoverSchnorr = defaultAbiCoder.encode(
  ["bytes32", "bytes32", "bytes32", "uint8"],
  [px, challenge.buffer, sSummed.buffer, parity]
);
const result = await contract.isValidSignature(msgHash, ecRecoverSchnorr);

校验下链

与多西格提供商:

const result = multisigProvider.verify(msgHash, [signature, signatureTwo]);

没有它:

import Schnorrkel from "@borislav.itskov/schnorrkel.js";

const publicKeys = [signerOne.publicKey, signerTwo.publicKey];
const sigOutputs = [signature, signatureTwo];
const sSummed = Schnorrkel.sumSigs(
  sigOutputs.map((output) => output.signature)
);

return _verify(
  sSummed.buffer,
  msgHash,
  sigOutputs[0].publicNonce.buffer,
  Schnorrkel.getCombinedPublicKey(publicKeys).buffer
);

您可以在 tests/schnorrkel/onchainMultiSign.test.ts 在此仓库中.

隐形

对于链上核查工作而言,我们受到以下工作的启发: 点数没有他的工作,RND需要更多的时间才能到达这个点. 你可以看看他的仓库 这里

我们利用Ethereum的隐秘 来验证签名。 这就是它的工作方式:
Ethereum ecrecover 返回一个地址( 公共密钥的屏蔽) 给定了 ECDSA 签名 . 给定 m 和 ECDSA 签名 (v, r, s), 其中 v 表示 x 坐标点的 Y 坐标的等同 r

ecrecover(m, v, r, s):
R = point derived from r and v
a = -G*m
b = R*s
Qr = a + b
Q = Qr * (1/r)
Q = (1/r) * (R*s - G*m) //recovered pubkey

Ethereum的ecrecover返回了64字节公用钥匙的keccak256散列的最后20字节. 鉴于签名(R, s),消息m和公钥P,我们可以将值输入密钥,这样返回的地址就可以与挑战作一比较.

calculate e = H(address(R) || m) and P_x = x-coordinate of P

过:

m = -s*P_x
v = parity of P
r = x-coordinate of P
s = -e*P_x

然后:

ecrecover(m=-s*P_x, v=0/1, r=P_x, s=-e*P_x):
P = point derived from r and v (public key)
a = -G*(-s*P_x) = G*s*P_x
b = P*(-m*P_x) = -P*e*P_x
Q = (1/P_x) (a+b)
Q = (1/P_x)(G*s*P_x - P*e*P_x)
Q = G*s - P*e  // same as schnorr verify above

返回的值是地址(Q).

  • 计算 e' = h( 地址( Q) ++ m)
  • 检查 e' == 以验证签名.

版本与发布记录

@borislav.itskov/schnorrkel.js 共发布 23 个版本,首次发布于 2023/03/31;过去 12 个月发布了 0 个版本。

版本发布日期距今
3.1.2latest2025/06/261.4 年前
3.1.12024/10/242 年前
3.1.02024/10/242 年前
3.0.02024/10/152 年前
2.0.852023/09/113 年前
2.0.842023/09/053 年前

关于 @borislav.itskov/schnorrkel.js 的常见问题

@borislav.itskov/schnorrkel.js 是什么?

一个javaScript库,用于签名和验证施诺尔签名. 它可用于单签名和多签名. 通过ecrecover进行区块链验证也得到了支持.

如何安装 @borislav.itskov/schnorrkel.js?

在项目目录运行 npm install @borislav.itskov/schnorrkel.js,或使用 pnpm add / yarn add / bun add。

@borislav.itskov/schnorrkel.js 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@borislav.itskov/schnorrkel.js 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@borislav.itskov/schnorrkel.js 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=16.0.0, <=22.12.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@borislav.itskov/schnorrkel.js 可以商用吗?

@borislav.itskov/schnorrkel.js 使用 ISC 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@borislav.itskov/schnorrkel.js 的下载量有多少?

根据 npm 官方下载统计,@borislav.itskov/schnorrkel.js 在 2026/10/09 – 2026/11/07 这 30 天内被下载 3,842 次,日均约 128 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@borislav.itskov/schnorrkel.js 还在维护吗?

最新版本 3.1.2 发布于 2025/06/26(1.4 年前);至今共发布 23 个版本,其中过去 12 个月发布了 0 个;首次发布于 2023/03/31。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。