4

Could Polkadot experts execute the code below and advise me on the questions, please?

You just need to replace the code by your own Westend accounts and nonce.

  import { construct, getRegistry, methods, deriveAddress } from "@substrate/txwrapper-polkadot";
  import { signatureVerify,  cryptoWaitReady } from '@polkadot/util-crypto';
  import { Keyring } from '@polkadot/api';
  import { EXTRINSIC_VERSION } from '@polkadot/types/extrinsic/v4/Extrinsic'

await cryptoWaitReady();

console.debug('build Keyring')
const drhan_keyring = new Keyring({ type: "sr25519"}); 

console.debug('Some mnemonic phrase, Add an account, straight mnemonic')
const drhan_substrateKeypair = drhan_keyring.addFromUri('world dutch flash motor outdoor major axis gloom rice pledge true suit'); 

console.debug(`publicKey: ${drhan_substrateKeypair.publicKey}`);

console.debug('derive account address');
const drhan_address = deriveAddress(drhan_substrateKeypair.publicKey, 42);
console.debug(`address: ${drhan_address}`);

const dest = "5Da4PC8yrDnr2RcKFLFWyuZ9XNei5mPYdAvyn5aUpBGV5Ydk";
const value = 1000000000;
const nonce = "34";

console.debug('Get the meta information of the target substrate node');
const { block } = await rpcToNode('chain_getBlock');
const blockHash = await rpcToNode('chain_getBlockHash');
const genesisHash = await rpcToNode('chain_getBlockHash', [0]);
const metadataRpc = await rpcToNode('state_getMetadata');
const { specVersion, transactionVersion, specName } = await rpcToNode('state_getRuntimeVersion');

console.debug('build the registry of the target substrate node');
const registry = getRegistry({
    chainName: 'Polkadot',
    specName,
    specVersion,
    metadataRpc
});

console.debug("create our `balances.transferKeepAlive` unsigned tx");
const unsigned = methods.balances.transferKeepAlive(
    {
        value,
        dest,
    },
    {
        address: drhan_address,
        blockHash,
        blockNumber: parseInt(block.header.number, 16),
        eraPeriod: 50,
        genesisHash,
        metadataRpc,
        nonce: parseInt(nonce),
        specVersion,
        tip: 0,
        transactionVersion,
    },
    {
        metadataRpc,
        registry,
    }
);

console.debug("Construct the signing payload from an unsigned transaction");
const signingPayload = construct.signingPayload(unsigned, { registry });

console.debug(`signingPayload: ${signingPayload}`);

console.debug("Construct signature");
const { signature } = registry.createType('ExtrinsicPayload', signingPayload, {
    version: EXTRINSIC_VERSION
}).sign(drhan_substrateKeypair);

console.debug(`signature: ${signature}`);

console.debug("Construct the signed transaction");
const signedTransaction = construct.signedTx(unsigned, signature, {
    metadataRpc,
    registry
});

console.debug("Derive the transaction hash of the signed transaction");
const hashSignedTx = construct.txHash(signedTransaction);

console.debug('check validity of signature');
const validity = signatureVerify(signingPayload, signature, drhan_address).isValid;

const response = {
    "signed_tx_payload": signedTransaction,
    "signed_tx_hash": hashSignedTx,
    "signatureVerify - valid?" : validity
};
export function rpcToNode(
     method: string,
     params: any[] = []
 ): Promise<any> {
     return fetch('https://westend-rpc.polkadot.io:443', {
         body: JSON.stringify({
             id: 1,
             jsonrpc: '2.0',
             method,
             params,
         }),
         headers: {
             'Content-Type': 'application/json',
             connection: 'keep-alive',
         },
         method: 'POST',
     })
         .then((response) => response.json())
         .then(({ error, result }) => {
             if (error) {
                 throw new Error(
                     `${error.code} ${error.message}: ${JSON.stringify(error.data)}`
                 );
             }
 
             return result;
         });
 }

Then, I have got the result as below, for example

{
  "signed_tx_payload": "0x39028400d6a55ba606625132346da20a84918fd03fc063e78beb65a708763ce212aba51e012ace240b88ccfda390bbef5547bf7bd353d77235c1055e0e58905f944b84343b63fe35ca27170fbf69596ddc4f941f6b4be25ea4cf07932591ef3512bc10828b65018800040300429f72a2498b5abaa44433210c5a5dc2d7b29946d0db1884101817871571f16302286bee",
  "signed_tx_hash": "0xfbf53347d8a80dc3444bcc5527c5e0795426ac5bf26978c236a41f920143bf4c",
  "signatureVerify - valid?": false
}

Then, I have submitted signed_tx_payload to Westend tesetnet as below

    const wsProvider = new WsProvider('wss://westend-rpc.polkadot.io');
    const api = await ApiPromise.create({ provider: wsProvider });
    
    const signed_tx_payload = "0x39028400d6a55ba606625132346da20a84918fd03fc063e78beb65a708763ce212aba51e012ace240b88ccfda390bbef5547bf7bd353d77235c1055e0e58905f944b84343b63fe35ca27170fbf69596ddc4f941f6b4be25ea4cf07932591ef3512bc10828b65018800040300429f72a2498b5abaa44433210c5a5dc2d7b29946d0db1884101817871571f16302286bee";
    
    const parsedTransaction = api.tx(signed_tx_payload);
    const TxHash = await api.rpc.author.submitExtrinsic(parsedTransaction);
    console.log(`tx hash: ${TxHash.toString()}`);

Then the message has been returned from Westend

2023-05-10 11:29:29        API/INIT: RPC methods not decorated: mmr_root, mmr_verifyProof, mmr_verifyProofStateless
2023-05-10 11:29:29        API/INIT: westend/9420: Not decorating runtime apis without matching versions: ParachainHost/4 (1/2/3 known), TransactionPaymentApi/4 (1/2/3 known)
tx hash: 0xfbf53347d8a80dc3444bcc5527c5e0795426ac5bf26978c236a41f920143bf4c

I thought that the signed transaction offline was successfully accepted to Westend testnet because I got the same hash of the transantion from Westend. But I have several questions below:

  1. Although this extrinsic was accepted to Westend testnet, it have not been commited to a block as I can't confirm it on westend.subscan.io and the nonce has not been increased. Why this extrinsic has not been commited although I got the same hash value from Westend ?

  2. When creating a raw unsigned extrinsic (transaction) object above, I used rpcToNode function which is introduced in here. If there exists a better way, could you advise me, please?

  3. As you see above, the function signatureVerify returned false strangely. Really I do not make sense of this result. Could you advise me whether I passed the wrong arguments, please?

  4. I have also tested verification with subkey as below

subkey verify --hex --message "signingPayload" "signature" "address(ss58 format)"

where signingPayload and signature can be obtained from the code above

$subkey verify --hex --message 0x9c040300429f72a2498b5abaa44433210c5a5dc2d7b29946d0db1884101817871571f16302286bee65018800cc24000015000000e143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423edfdac6269bf6f42a3dfde7683ca419c866b1d79b0449210345cbcea5b538f252 \
0x012ace240b88ccfda390bbef5547bf7bd353d77235c1055e0e58905f944b84343b63fe35ca27170fbf69596ddc4f941f6b4be25ea4cf07932591ef3512bc10828b \
5Gv9E4mSjXJsicL4QAuRe8zq3MfGeTGp1cVmB24aRhSgrKqs

then, I got an error

Error: SignatureFormatInvalid

I would be very grateful if anyone could advise me on the questions above.

Thanks

1 Answer 1

6

For context all the information to answer this question is in this github issue: https://github.com/paritytech/txwrapper-core/issues/296

  1. Given the address you are using to conduct the transaction, the transfers are going through, and the nonce is being increased. This can be confirmed on westend here: https://westend.subscan.io/account/5Gv9E4mSjXJsicL4QAuRe8zq3MfGeTGp1cVmB24aRhSgrKqs

  2. This is just preference. You may use whatever suits your systems design. Depending on the output you can create a subscription as well using polkadot-js api, api.rpc.author.submitAndWatchExtrinsic. If you choose to use rpcToNode you would need to scrape the chains newly finalized blocks and search for your transaction in the extrinsics to confirm that the transfer was either successful or not.

  3. Here is a code snippet that fixes your signatureVerify functionality:

    // Sign a payload. This operation should be performed on an offline device.
    const s = registry
        .createType('ExtrinsicPayload', signingPayload, {
            version: EXTRINSIC_VERSION,
        })
    const { signature } = s.sign(sender);
    console.log(`\nSignature: ${signature}`);

    const v = signatureVerify(s.toU8a({ method: true }), signature, sender.publicKey)

    console.log(v)

It is important to note that what is happening when you create an ExtrinsicPayload that the method bytes are being remove which can be seen in the code here. This means that the signingPayload you are passing in is not the same as the ExtrinsicPayload.

I hope this satisfied your question.

2
  • Tarik Gul, thanks for your great explanation, in particular the difference between signingPayload and ExtrinsicPaylaod. May 10 at 16:34
  • No worries at all happy to help in any way I can. If you found the response correct and helpful, feel free to mark it as the correct/top answer so it can help others in the same boat.
    – Tarik Gul
    May 10 at 16:56

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.