michaelhly / solana-py

Solana Python SDK
https://michaelhly.github.io/solana-py
MIT License
1.05k stars 271 forks source link

Validating message signature from browser wallet #118

Closed geoffwhittington closed 2 years ago

geoffwhittington commented 3 years ago

I am trying to use Python code to verify the signature signedMessage generated in a user's browser. The Python code says its corrupt, however. All the public keys match up in the following example.

Does anyone know what I'm doing wrong?

Javascript/React

import React from "react";
import Button from "@material-ui/core/Button";

import "./App.css";

function App() {
  const signMessage = () => {
    window.solana.connect().then(async () => {
      const message = `test`;
      const encodedMessage = new TextEncoder().encode(message);

      const signedMessage = await window.solana.request({
        method: "signMessage",
        params: {
          message: encodedMessage,
        },
      });
      console.log(signedMessage);
    });
  };

  return (
    <>
      {window.solana && (
        <Button
          variant="contained"
          style={{ width: "1em" }}
          onClick={async () => {
            signMessage();
          }}
        >
          Sign
        </Button>
      )}
    </>
  );
}

export default App;

Python validation:

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

pk = PublicKey("H7q8z...W6c")

verify_key = VerifyKey(bytes(pk))

verify_key.verify(
    bytes("test", "utf8"),
    bytes(
        "5U3sXNjmzc8WzYb1vjXP9B95GohmKDsz72yFebuhsS4pocmZxuwj75y6rZyD71tcxXCwbJxwbEDDkSLgyASkzZ1A",
        "utf8",
    ),
)
michaelhly commented 3 years ago

Could you try signing like this instead? https://github.com/solana-labs/solana-web3.js/blob/e313482e5243bc66008cf342389c7ba819759767/src/transaction.ts#L511

geoffwhittington commented 3 years ago

Thanks for your response. Can the above triggered from Phantom or sollet? I am preoccupied with the user experience in my use case

AnderUstarroz commented 2 years ago

Same problem here, @geoffwhittington were you able to make it work? I have used the following JS function from Solana labs to sign messages:

async function SignMsg (publicKey: PublicKey, signMessage: any, msg: string) {
  try {
    // `publicKey` will be null if the wallet isn't connected
    if (!publicKey) {
      console.error("Wallet not connected!")
      return false
    }
    // `signMessage` will be undefined if the wallet doesn't support it
    if (!signMessage) {
      console.error("Wallet does not support message signing!")
      return false
    }

    // Encode anything as bytes
    const message = new TextEncoder().encode(msg)
    // 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())) {
      console.error("Invalid signature!")
      return false
    }
    console.log(`Message signature: ${bs58.encode(signature)}`)
    return bs58.encode(signature)
  } catch (error: any) {
    console.log(`Signing failed: ${error?.message}`)
    return false
  }
}

And the following Python script to verify messages:

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

result = VerifyKey(bytes(PublicKey("HERE_THE_PUB_KEY"))).verify(
    smessage=bytes("HERE_THE_MESSAGE", "utf8"),
    signature=bytes("HERE_THE_SIGNATURE", "utf8"),
)

But verification returns: nacl.exceptions.BadSignatureError: Signature was forged or corrupt.

Any idea what could be wrong?

geoffwhittington commented 2 years ago

@AnderUstarroz I haven't solved this yet, I'll let you know if/when that happens!

peterschwarzdev commented 2 years ago

I am a bit confused about this when you use await signMessage(message) I would expect to see the signed messages somewhere in the blockchain using the generated signature to find the performed action in solana explorer, but that is not the case, is all this happening offline then?

geoffwhittington commented 2 years ago

@peterschwarzdev I'm trying to leverage the web wallet to produce a signature only - no transfer of tokens. The signature is useful for authenticating a user.

AnderUstarroz commented 2 years ago

@geoffwhittington I got it! try this:

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

pubkey = bytes(PublicKey("THE_PUBKEY"))
msg = bytes("THE_MESSAGE", 'utf8')
signed = bytes("THE_SIGNATURE", 'utf8')

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

Works for messages signed using the JS function I previously posted.

geoffwhittington commented 2 years ago

I'm going to name my second child after you if this works.

geoffwhittington commented 2 years ago

@AnderUstarroz You rock - works like a charm.