I have signed a message using 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;
};
DKpHyR1WjWE23E3xizPUhefZKmpMrMXNBVfoxQ7WXCRR
hello
3EWDdtU1w8pWkr6fg8faJvKn1wBZmNjgf5kUx4Pn5gw4HeBPYVDm7cTHNqpRVMami6yX36jdaeZacv9GXR19Jzye
But I am not being able to verify the signed message using Python 3.9 with PyNaCl and Solana-py. I have tried the following:
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)
)
But verification returns: nacl.exceptions.BadSignatureError: Signature was forged or corrupt.
Somebody knows what is wrong? Could it be an encoding problem? Seems like JS uses the following byte types:
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]
And in 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'
Do I need to use some different encoding on Python?
Thanks for providing a concrete example on this, you're very close! The encoding is absolutely the issue here -- the pubkey is correctly encoded in Python as bytes. That first byte of \x90
, encoded as two hex values, is 144
in JS, and you can check that in Python with: int('90', 16) = 144
.
So to verify your key, you can instead use the base58
package https://github.com/keis/base58 and do:
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)
)
Note.- on smessage you don't need to use b58, because it was
encoded with new TextEncoder().encode("hello")
.
Second option: if you already have the UInt8Array from JS, you can do:
result = VerifyKey(bytes(PublicKey("HERE_THE_PUB_KEY"))
).verify(
smessage=bytes([byte1, byte2, byte3, ...])
signature=bytes([byte1, byte2, byte3, ...])
)