3

I've followed the cookbook on signing a payload, however this uses sr25519.

I would like to use ECDSA to sign the payload, how can this be done?

I have looked into using the keyring and managed to get the Alice account to do the signing:

        const keyring = new Keyring({ type: 'ecdsa' });
        const pair = keyring.addFromUri('//Alice');
        const sig = pair.sign(message);
        console.log('sig', sig);
        console.log('sig hex', u8aToHex(sig));

This works. However, I cannot find a way to add the account from an extension to the keyring to reproduce this using something other than the Alice account.

Edit 1: The alternative option number 1 is to ask the polkadot JS extension to sign the payload with an alternative method, i.e. ecdsa instead of sr25519. However I cannot find how to do this. Alternative option number 2 is to verify a sr25519 signature in ink, but I cannot find how to do that either.

2 Answers 2

1

To add account other than Alice one can use derivation phrase like so:

const pair = keyring.addFromUri('some derivation words you have from key generation...');

or add from seed, json or pair - details in code here

For the extension import you have to use @polkadot/extension-dapp

yarn add @polkadot/extension-dapp

and call it like so:

import { stringToHex } from "@polkadot/util";
import { web3Accounts, web3Enable, web3FromSource } from '@polkadot/extension-dapp';

// this call fires up the authorization popup
const extensions = await web3Enable('my cool dapp');

if (extensions.length === 0) {
    // no extension installed, or the user did not accept the authorization
    // in this case we should inform the use and give a link to the extension
    return;
}

// we are now informed that the user has at least one extension and that we
// will be able to show and use accounts
const allAccounts = await web3Accounts();

// `account` is of type InjectedAccountWithMeta 
// We arbitrarily select the first account returned from the above snippet
const account = allAccounts[0];

// to be able to retrieve the signer interface from this account
// we can use web3FromSource which will return an InjectedExtension type
const injector = await web3FromSource(account.meta.source);


// this injector object has a signer and a signRaw method
// to be able to sign raw bytes
const signRaw = injector?.signer?.signRaw;

if (!!signRaw) {
    // after making sure that signRaw is defined
    // we can use it to sign our message
    const { signature } = await signRaw({
        address: account.address,
        data: stringToHex('message to sign'),
        type: 'bytes'
    });
}

Example is compiled from cookbook. Check it for more details.

3
  • 1
    a) I cannot add accounts to the extension from seed phrases as those are private to the users of my system. b) this does sr25519 signing using signRaw. I cannot verify that as I only have access to ECDSA
    – goastler
    Mar 20 at 10:23
  • so you want JUST verify ECDSA? Mar 20 at 11:37
  • I want to verify sr25519 in ink
    – goastler
    Mar 22 at 11:32
1

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.