project-serum / sol-wallet-adapter

Other
185 stars 93 forks source link

Unable to verify signed message #41

Closed ezy closed 3 years ago

ezy commented 3 years ago

Having created a signed message I'm unsure how to use the resulting signature to verify the message using the publicKey.

My use case is: I'm wanting to use a Solana Wallet to login to an API server with a pattern like:

  1. GET message: String (from API server)
  2. sign message with privateKey
  3. POST signature (to API server)
  4. verify signature with stored publicKey

I've attempted to use crypto.verify to decode the signed message on the API side but am a bit out of my depth digging into Buffers and elliptic curves:

// Front-end code
const toHexString = (buffer: Buffer) =>
  buffer.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");

const data = new TextEncoder().encode('message to verify');
const signed = await wallet.sign(data, "hex");
await setLogin({ // sends API post call to backend
  variables: {
    publicAddress: walletPublicKey,
    signature: toHexString(signed.signature),
  },
});

// Current WIP for backend code
const ALGORITHM = "ed25519";
const fromHexString = (hexString) =>
  new Uint8Array(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
const signature = fromHexString(args.signature);
const nonceUint8 = new TextEncoder().encode('message to verify');
const verified = crypto.verify(
  ALGORITHM,
  nonceUint8,
  `-----BEGIN PUBLIC KEY-----\n${user.publicAddress}\n-----END PUBLIC KEY-----`,
  signature
);
console.log("isVerified: ", verified);

I'm pretty sure I'm going about this the wrong way and there must be an obvious method I'm missing.

Ideally it seems like there should be a verify function to consume the output of const signed = await wallet.sign(data, "hex");

Something like:

import { VerifyMessage } from '@solana/web3.js';

const verified = VerifyMessage(message, publicKey, signature, 'hex');

But after 3 days of pushing hard I'm starting to hit my limits and my brain is failing. Any help or direction where to look much appreciated 🙏

ezy commented 3 years ago

Solved with input from the fantastic Project Serum discord devs. High level solution is to use libs that are also used in the sol-wallet-adapter repo, namely tweetnacl and bs58:

const signatureUint8 = base58.decode(args.signature);
const nonceUint8 = new TextEncoder().encode(user?.nonce);
const pubKeyUint8 = base58.decode(user?.publicAddress);

nacl.sign.detached.verify(nonceUint8, signatureUint8, pubKeyUint8)
// true

For any devs who read this that might be implementing this sort of functionality, it may be preferable to use https://www.npmjs.com/package/elliptic as the uint8Array format introduced by tweetnacl means that to send signatures via JSON requires encoding/decoding using the bs58 lib, when elliptic handles hex strings by default.