如何用solana-web3.js实现Phantom钱包同款消息签名?
如何用solana-web3.js实现与Phantom钱包一致的消息签名
问题背景
想要在solana-web3.js中实现和Phantom钱包扩展signMessage函数一致的消息签名逻辑,但现有代码的输出结果和Phantom的签名结果完全不符:
Phantom签名输出示例:
{"signature":"5uLfqwr19GdFBUyMTPeeL3HJ9U5f9TjRzAnCkzAaCyxZgNeA3C1i6c37FjPTowZRg8WSYdwANdVVXMMkWx2Hzwoy","address":"2o6AxcaMZqcS3L8oyPhS66keuxA1FrRtrSRcRxRw4U9C"}
现有不符代码:
const signMessage = (message, mnemonic) => { // mnemonic to keypair const seed = bip39.mnemonicToSeedSync(mnemonic); const path = 'm/44\'/501\'/0\'/0\''; const derivedSeed = derivePath(path, seed.toString('hex')).key; const keyPair = Keypair.fromSeed(derivedSeed); if (!keyPair) throw new Error('Invalid secret key') // sign message const address = keyPair.publicKey.toBase58() const bufSecretKey = keyPair.secretKey const serializedData = Buffer.from(message) const bufSig = tweetnacl.sign(serializedData, bufSecretKey) const signature = Buffer.from(bufSig).toString('hex') return { signature, address} };
现有代码输出示例:
{"signature":"f53409d847b5eae5ef3bb892b998805d2ea04b699e39bb5c126e757d1f4023488945ba210609fb661f70c10a45fdd55db09741d5e6a8839446a91af39d4fe5044c6f67696e204368616c6c656e67653a2066613666363234342d663863352d343162642d383033372d356333396562653534396366","address":"2o6AxcaMZqcS3L8oyPhS66keuxA1FrRtrSRcRxRw4U9C"}
问题原因
现有代码不符合Phantom遵循的Solana消息签名规范,核心差异有两点:
- 缺少固定签名前缀:Phantom会自动为待签名消息添加
"Solana Signed Message:\n"前缀,这是Solana生态统一约定,用于区分交易签名和普通消息签名。 - 签名结果处理错误:
tweetnacl.sign()返回的是「签名+原始消息」的拼接字节数组,而Phantom只返回前64字节的纯签名;同时Phantom返回的签名是Base58编码,不是现有代码用的Hex编码。
修正后的代码
const signMessage = (message, mnemonic) => { // 助记词转密钥对(保留原有逻辑) const seed = bip39.mnemonicToSeedSync(mnemonic); const path = "m/44'/501'/0'/0'"; const derivedSeed = derivePath(path, seed.toString("hex")).key; const keyPair = Keypair.fromSeed(derivedSeed); if (!keyPair) throw new Error("Invalid secret key"); // 构造符合Solana规范的待签名消息:添加固定前缀 const encoder = new TextEncoder(); const prefix = encoder.encode("Solana Signed Message:\n"); const messageBytes = encoder.encode(message); const dataToSign = new Uint8Array([...prefix, ...messageBytes]); // 生成纯签名并转换为Base58格式(和Phantom一致) const signatureBytes = tweetnacl.sign.detached(dataToSign, keyPair.secretKey); const signature = bs58.encode(signatureBytes); const address = keyPair.publicKey.toBase58(); return { signature, address }; };
关键说明
- 必须添加前缀:这是Solana生态的硬性约定,所有合规钱包都会遵循,避免消息签名被恶意用作交易签名。
- 使用
detached签名:tweetnacl.sign.detached()直接返回纯签名字节数组,无需手动截取,更适配场景需求。 - 依赖
bs58包:需要先安装bs58包来完成Base58编码转换,和Phantom的输出格式对齐。
内容的提问来源于stack exchange,提问作者Yoga
相关产品推荐
相关产品推荐

