如何使用 Python (PyNaCl) 验证由 Solana 钱包适配器 (javascript) 签名的消息

How to verify with Python (PyNaCl) a message signed by Solana wallet adapter (javascript)

我已经使用 Solana's wallet adapter example 签署了一条消息:

import { useWallet } from '@solana/wallet-adapter-react';
import bs58 from 'bs58';
import React, { FC, useCallback } from 'react';
import { sign } from 'tweetnacl';

export const SignMessageButton: FC = () => {
    const { publicKey, signMessage } = useWallet();

    const onClick = useCallback(async () => {
        try {
            // `publicKey` will be null if the wallet isn't connected
            if (!publicKey) throw new Error('Wallet not connected!');
            // `signMessage` will be undefined if the wallet doesn't support it
            if (!signMessage) throw new Error('Wallet does not support message signing!');

            // Encode anything as bytes
            const message = new TextEncoder().encode("hello");
            // Sign the bytes using the wallet
            const signature = await signMessage(message);
            // Verify that the bytes were signed using the private key that matches the known public key
            if (!sign.detached.verify(message, signature, publicKey.toBytes())) throw new Error('Invalid signature!');

            alert(`Message signature: ${bs58.encode(signature)}`);
        } catch (error: any) {
            alert(`Signing failed: ${error?.message}`);
        }
    }, [publicKey, signMessage]);

    return signMessage ? (<button onClick={onClick} disabled={!publicKey}>Sign Message</button>) : null;
};

但是我无法使用 Python 3.9 和 PyNaCl and Solana-py 来验证签名的消息。我尝试了以下方法:

from nacl.signing import VerifyKey
from solana.publickey import PublicKey
import base58

pubkey = bytes(PublicKey("DKpHyR1WjWE23E3xizPUhefZKmpMrMXNBVfoxQ7WXCRR"))
msg = bytes("hello", 'utf8')
signed = bytes("3EWDdtU1w8pWkr6fg8faJvKn1wBZmNjgf5kUx4Pn5gw4HeBPYVDm7cTHNqpRVMami6yX36jdaeZacv9GXR19Jzye", 'utf8')

result = VerifyKey(
    pubkey
).verify(
    smessage=base58.b58decode(msg),
    signature=base58.b58decode(signed)
)

但验证returns:nacl.exceptions.BadSignatureError: Signature was forged or corrupt.

有人知道哪里出了问题吗?会不会是编码问题?好像 JS 使用以下字节类型:

pubkey:  Uint8Array(32) [144, 188, 240, 167, 187, 75, 30, 17, 232, 175, 91, 222, 73, 68, 183, 218, 108, 56, 249, 64, 250, 61, 111, 168, 194, 233, 159, 2, 247, 5, 175, 124, buffer: ArrayBuffer(32), byteLength: 32, byteOffset: 0, length: 32]
message: Uint8Array(44) [57, 85, 65, 81, 76, 53, 81, 68, 67, 89, 122, 70, 112, 107, 119, 70, 88, 52, 88, 75, 53, 70, 119, 107, 66, 54, 67, 57, 116, 57, 116, 120, 65, 89, 52, 102, 102, 122, 69, 52, 114, 97, 113, 84, buffer: ArrayBuffer(44), byteLength: 44, byteOffset: 0, length: 44]
signed:  Uint8Array(64) [111, 173, 219, 10, 169, 113, 163, 35, 30, 162, 250, 243, 191, 106, 195, 99, 238, 34, 49, 192, 19, 92, 111, 142, 57, 31, 158, 235, 65, 219, 146, 176, 174, 48, 30, 255, 160, 90, 174, 179, 219, 197, 252, 189, 150, 225, 160, 133, 163, 109, 159, 80, 56, 191, 11, 1, 91, 111, 196, 214, 231, 84, 11, 1, buffer: ArrayBuffer(64), byteLength: 64, byteOffset: 0, length: 64]

在python中:

pubkey: b'\xb7\x1e+\xef\xe19#y}\xa4L\xf2K\rK\xc3\xbby\x93\x1c\x00L\xe1<\x19g`-\x9d\xd5\xee\x94'
msg:    b'Cn8eVZg'
signed: b'3EWDdtU1w8pWkr6fg8faJvKn1wBZmNjgf5kUx4Pn5gw4HeBPYVDm7cTHNqpRVMami6yX36jdaeZacv9GXR19Jzye'

我需要在 Python 上使用一些不同的编码吗?

感谢您提供具体示例,您已经非常接近了!编码绝对是这里的问题——pubkey 在 Python 中正确编码为字节。 \x90 的第一个字节,编码为两个十六进制值,在 JS 中是 144,您可以在 Python 中检查它:int('90', 16) = 144.

所以要验证您的密钥,您可以改用 base58https://github.com/keis/base58 并执行:

from nacl.signing import VerifyKey
from solana.publickey import PublicKey
import base58

pubkey = bytes(PublicKey("DKpHyR1WjWE23E3xizPUhefZKmpMrMXNBVfoxQ7WXCRR"))
msg = bytes("hello", 'utf8')
signed = bytes("3EWDdtU1w8pWkr6fg8faJvKn1wBZmNjgf5kUx4Pn5gw4HeBPYVDm7cTHNqpRVMami6yX36jdaeZacv9GXR19Jzye", 'utf8')

result = VerifyKey(
    pubkey
).verify(
    smessage=msg,  
    signature=base58.b58decode(signed)
)

注意。- 在 smessage 你不需要使用 b58,因为它是 用 new TextEncoder().encode("hello").

编码

第二个选项:如果你已经有 JS 的 UInt8Array,你可以这样做:

result = VerifyKey(bytes(PublicKey("HERE_THE_PUB_KEY"))
  ).verify(
    smessage=bytes([byte1, byte2, byte3, ...])
    signature=bytes([byte1, byte2, byte3, ...])
)