Hyperledger Fabric Node.js客户端规范签名问题求助
解决Fabric离线签名的规范Low-S签名问题
核心原因
Fabric要求ECDSA签名必须是规范形式(Low-S),即签名中的S值必须≤曲线阶的一半。Node.js原生crypto.sign方法默认不强制生成Low-S签名,导致peer验证时抛出S值过高的错误。
可行解决方案
方案1:使用@noble/curves包(推荐)
@noble/curves的p256(对应prime256v1曲线)模块原生支持生成Low-S签名,完全适配Fabric的签名要求。
代码示例:
import { p256 } from '@noble/curves/p256'; import { hexToBytes, bytesToHex } from '@noble/hashes/utils'; // 将PEM格式私钥转换为裸私钥字节数组 function pemToPrivateKeyBytes(pem) { const pemHeader = '-----BEGIN PRIVATE KEY-----'; const pemFooter = '-----END PRIVATE KEY-----'; const pemContent = pem.replace(pemHeader, '').replace(pemFooter, '').replace(/\s/g, ''); return Uint8Array.from(Buffer.from(pemContent, 'base64')); } // 对交易摘要生成规范Low-S签名 function signProposalDigest(proposalDigest, privateKeyPem) { const privateKeyBytes = pemToPrivateKeyBytes(privateKeyPem); // 默认生成符合要求的Low-S签名 const signature = p256.sign(proposalDigest, privateKeyBytes); // 转换为Fabric兼容的DER格式 return signature.toDER(); }
方案2:手动调整原生crypto生成的签名
如果坚持使用Node.js原生crypto模块,需要手动解析签名的R、S值,将超出阈值的S值替换为曲线阶 - S,再重新编码为DER格式。
代码示例:
const crypto = require('crypto'); const asn1 = require('asn1.js'); // 需先安装asn1.js包 // 定义ECDSA签名的ASN.1结构,用于解析/编码签名 const EcdsaSig = asn1.define('EcdsaSig', function() { this.seq().obj( this.key('r').int(), this.key('s').int() ); }); function normalizeSignature(signature, curve) { const ecdh = crypto.createECDH(curve); const n = ecdh.getPrivateKey().asn1Curve.n; // 获取曲线阶 // 解析DER格式签名为R、S值 const decoded = EcdsaSig.decode(signature, 'der'); // 如果S大于曲线阶的一半,替换为n - S if (decoded.s.compare(n.divn(2)) === 1) { decoded.s = n.sub(decoded.s); } // 重新编码为DER格式 return EcdsaSig.encode(decoded, 'der'); } // 原生签名后执行规范化处理 function signWithNormalization(proposalDigest, privateKeyPem) { const privateKey = crypto.createPrivateKey(privateKeyPem); // 生成原始签名 const rawSignature = crypto.sign('sha256', proposalDigest, privateKey); // 转换为规范Low-S签名 return normalizeSignature(rawSignature, 'prime256v1'); }
集成到fabric-gateway离线签名流程
在fabric-gateway的签名回调中使用上述生成的规范签名即可:
const { Gateway, Wallets } = require('fabric-network'); async function submitOfflineSignedTransaction() { const gateway = new Gateway(); const wallet = await Wallets.newFileSystemWallet('./wallet'); await gateway.connect(ccp, { wallet, identity: 'your-identity', signer: async (digest) => { const identity = await wallet.get('your-identity'); const normalizedSignature = signProposalDigest(digest, identity.privateKey); return { signature: normalizedSignature }; } }); // 后续交易提交流程... }
注意事项
- 确保使用
prime256v1曲线(Fabric默认曲线),不要混淆为secp256k1。 - 手动调整S值时必须使用大数运算,避免直接操作字节流导致的格式错误。
内容的提问来源于stack exchange,提问作者Eduardo Valente
相关产品推荐
相关产品推荐

