This is a pending issue with both the polkadot JS extension and ink not lining up with how signatures should work and what's on offer from both packages.
Currently, ink offers ECDSA verification and polkadot JS extension offers sr25519 (schnorrkel) signing only. These are not compatible.
I have lodged an issue with polkadot JS extension and ink on their github repos. Please upvote/comment accordingly.
As of posting this answer, polkadot JS extension does not plan on supporting anything other than sr25519 (schnorrkel) signing from user accounts (i.e. not Alice/Bob, etc). However, ink is adding sr25519 verification to the contracts pallet for substrate which means sr25519 (schnorrkel) verification should be supported in the future!
In the meantime, I have managed to import the schnorrkel crate into ink and verify sr25519 signatures. Unfortunately this crate adds ~30KB to the contract size.
To produce the sr25519 (schnorrkel) signature using polkadot JS extension:
let message = 'hello';
const ext = account.extension;
const signer = ext?.signer;
const signRaw = signer?.signRaw;
if(signRaw) {
// create the message and sign it
const payload = {
address: account.account.address,
data: message,
type: 'bytes'
};
const signed = await signRaw(payload as SignerPayloadRaw);
console.log('signature', signed.signature);
}
Then to verify in ink, add the schnorrkel dependency to Cargo.toml:
[dependencies]
schnorrkel = { version = "0.9.1", default-features = false, features = ["u64_backend"] }
Then to verify a payload + signature in ink:
// We must make sure that this is the same as declared in the substrate source code.
// this is the signing context used by the schnorrkel library when signing messages. It has to be the same binary blob on both sides of the signing process (i.e. the signing and the verifying) as it is used in the encryption/decryption process.
const CTX: &[u8] = b"substrate";
#[ink(message)]
pub fn verify_sr25519(
&self,
signature: [u8; 64],
payload: [u8; 49],
) -> Result<bool, Error> {
let caller = self.env().caller();
let mut caller_bytes = [0u8; 32];
let caller_ref: &[u8] = caller.as_ref();
caller_bytes.copy_from_slice(&caller_ref[..32]);
let sig = Signature::from_bytes(&signature).map_err(|_| Error::InvalidSignature)?;
let pub_key =
PublicKey::from_bytes(&caller_bytes).map_err(|_| Error::InvalidPublicKey)?;
let res = pub_key.verify_simple(crate::CTX, &payload, &sig);
Ok(res.is_ok())
}
Note that your payload may be a different size to mine. Also note that the CTX variable has to be the same as that of substrate as it is involved in the sr25519 algorithm for computing signatures, so different CTX values lead to different signatures.
For a full example, see here and here