Sitelet https://github.com/entropycode/w3f-m1-cli
Skip to content

About

CLI for creating and signing extrinsics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

Create Signed Transactions

This tutorial explains how to use a set of CLI tools to create a signed transaction in Substrate. There are many tutorials on building different DApps / DApp side chains with Substrate and tools such as polkadot.js to interact with a substrate node. However, as more developers build on Substrate, we want to dive into what is actually encoded in the extrinsics so that we can create them with new tools / manually, if required.

We will be using simple CLI tools to:

Setup

Please refer to SETUP for instructions on how to setup your environment for this tutorial.

Create Extrinsics

We start off by looking into what is being encoded in an unsigned extrinsic. To do this, we want a simple tool create-extrinsic.js that takes in some data required to create an unsigned extrinsic, outputs data required for for signing in the next stage. There are comprehensive materials on what an extrinsic is and different types of extrinsics in the substrate.dev documentations and we will not cover it here.

./create-extrinsic.js [input-file-path]

The example input file has the following fields which are neccessary for creating a signed transaction. In this example, we want to transfer some balance from one account 5G..QY to another account 5F..tys on a node running locally at port 9944.

{ 
    "wsEndpoint" : "ws://127.0.0.1:9944",
    "account" : "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
    "module" : "balances",
    "method" : "transfer",
    "args" : ["5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", 10],
    "tip" : 2
}

We shall use polkadot.js to create an unsigned extrinsic. To get familiar with polkadot.js, go here

const unsignedExtrinsic = api.tx[input.module][input.method](...input.args)

A hex representation of the extrinsic is obtained by unsignedExtrinsic.toHex().

0x94040400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4828

Let's break this down into what is being encoded:

Layout Bytes Field Description Encapsulation
0x Hex prefix
Length Length prefix of the rest of the extrinsic
94 Length prefix Compact encoded
Version Version and signing information
04 version 1 byte; 1 high bit signed flag, 7 low bit extrinsic version See implementation
Call Data Encoded call data
04 Module identifier The index of the module Balances within all runtime modules Big Endian (Hex)
00 Method identifier The index of the method transfer within the module Balances Big Endian (Hex)
ff8e...28 Method arguments The concatenation of the encoded arguments Compact encoded

Let's look at the encoded method arguments, where the arguements are specified in the balances module:

Arguments Field Bytes Description
Destination Account ff Account format indicator
Destination Account 8eaf...26a48 Public key from provided address in SS58 address format
Value 28 Value of transfer Compact encoded

What is happening behind the scenes?

An instance of polkadot.js API is initialised with the metadata from a node, provided or with a provider to a substrate node. Along with chain data and runtime data, a key role of the metadata is to allow the instance to have the information to index the modules, methods and the types required for encoding the arguments, that are the last 3 fields in the above unsignedExtrinsic.

Metadata

We require the metadata in our next step to create the signed transaction without connections to the node, for more details on metadata, see details here.

const metadata = await api.rpc.state.getMetadata()

Extension Data

In order to create a signed transaction, we will need to add a signature, the signer's address and some other information to the above unsignedExtrinsic.

There are additional information in the extrinsic payload that is required to be signed and is not explicitly in the extrinsic, such as genesisHash, blockHash (for mortal transactions), etc. Details can be found here.

In our tool, we will create the payload with data from the input file and the node.

const signaturePayloadValue = {
  address: input.account,
  blockHash: currentBlock.block.header.hash.toHex(), 
  genesisHash: genesisHash.toHex(),
  nonce: nonce.toHex(),
  method: '0x' + methodIndex + callData,
  era: era.toHex(), 
  tip: input.tip,
  version: extrinsicVersion,
  specVersion: runtimeVersion.specVersion,
}

// In polkadot.js the signed extension type is called ExtrinsicPayload
const toSignPayload = api.createType('ExtrinsicPayload',  signaturePayloadValue, {version: signaturePayloadValue.version})

Similar to the unsignedExtrinsic, we can get the hex code toSignPayload.toHex() and it is a concatenation of encoded fields.

0x900400ff8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a482832000008010000003ded6a14293f500687b357386c9fc9c3e93da3b1f3ed84cefcd8209519ca5a31049dcb30b4e117c26fd2e6dfea2c1b8221fd3b0bf912033bb1857ef1db3042b2
Layout Byte Field Description Encapsulation
0x Hex prefix
Length Length prefix of the rest of the extrinsic
90 Length Prefix Compact encoded
Call Data Encoded call data
04 Module identifier The index of the module Balances within all runtime modules Big Endian (Hex)
00 Method identifier The index of the method transfer within the module Balances Big Endian (Hex)
ff8e...28 Method arguments The concatenation of the encoded arguments Compact encoded
Extension Data Additional data to be signed
3200 Transaction Era The period defined for moratal transaction to be valid See implementation
08 Transaction Index Signer's acccount nonce Compact encoded
08 Tip Optional; higher tip increase priority of the transaction Compact encoded
01000000 Runtime Spec Version Current runtime spec version to ensure signing for the intended runtime logic 4 bytes in hex
3d.. 31 Genesis Blockhash Genesis blockhash of the current chain to signing for the intended chain See declared storage
04.. b2 Current Blockhash Current blockhash when signed, for mortal transactions. Same as Genesis blockhash for immortal transactions See declared storage

NOTE: Ensure you are using toSignPayload.toU8a(true) to create the payload without the length prefix to be signed.

Output

Now we have all the required information to be signed, we shall output it to the next stage.

const output = {
    metadata: metadata.toHex(),
    signaturePayload: toSignPayload.toHex(),
    toSign: u8aToHex(toSignPayload.toU8a(true)),
    unsignedExtrinsic: unsignedExtrinsic.toHex(),
}

Create Signed Transactions

We will sign the payload and create a signed transaction in this section with the following CLI tool: create-signed-transaction.js. It will be taking in the output from create-extrinsic .js. This tool will not be connecting to a node since we have prepared all the required data in our previous step.

./create-extrinsic.js [input-file-path] | ./create-signed-transaction.js [signature-type] [private-key]

There are multiple ways to sign the extrinsic payload, here we use the polkadot.js Keyring.

const keyring = new Keyring({ type: scheme })
const keypair = keyring.addFromUri(secret)
const signature = keypair.sign(input.toSign);

The signature is 64 bytes, and it will need to be prefixed by the type of signature as specified at the command line as multiple types are supported.

const signatureTypes = { 'ed25519' : '0x00', 'sr25519' : '0x01', '0x02' : 'edcsa'} 
const multiSignature = signatureTypes[scheme] + Buffer.from(signature).toString('hex')

With the signature ready, we can use the method addSignature to add it into the unsigned extrinsic to created a signed transaction. To create an extrinsic javascript object from the unsigned extrinsic hex input, we need to attach the metadata information to the type registry so that the hex can be decoded / encoded correctly.

const registry = new TypeRegistry()
new Metadata(registry, input.metadata)

const unsignedExtrinsic = createType(registry, 'Extrinsic', input.unsignedExtrinsic)
const signedExtrinsic = unsignedExtrinsic.addSignature(keypair.publicKey, multiSignature, input.signaturePayload)

The signedExtrinsic hex encodes the following information:

Layout Bytes Field Description Encapsulation
0x Hex prefix
Length Length prefix of the rest of the extrinsic
2d02 Length prefix Compact encoded
Version Version and signing information
84 version 1 byte; 1 high bit signed flag, 7 low bit extrinsic version See implementation
Signer Signer's Identifier
ff Account format See Account format indicator
d4..7d Public key SS58 address format
Signature Signature type prefix
01 Type prefix For identifying signature in MultiSignature MultiSignature Enum
3e..81 Signature 64 bytes signature from signing the Signed Extension See Substrate Cryptography
Extension Data
3200 Transaction Era The period defined for moratal transaction to be valid See implementation
08 Transaction Index Signer's acccount nonce Compact encoded
08 Tip Optional; higher tip increase priority of the transaction Compact encoded
Call Data Encoded call data
04 Module identifier The index of the module Balances within all runtime modules Big Endian (Hex)
00 Method identifier The index of the method transfer within the module Balances Big Endian (Hex)
ff8e...28 Method arguments The concatenation of the encoded arguments Compact encoded
Account format indicator

1/3/5/9/33 bytes: The signing account identity, in Address format:

  • 0...0xef: 1 byte Account Index, to be interpreted as the value of the byte.
  • 0xfc: 2 byte Account Index, value to follow.
  • 0xfd: 4 byte Account Index, value to follow.
  • 0xfe: 8 byte Account Index, value to follow.
  • 0xff: 32 byte Account ID, value to follow.

Some transaction specific fields (era, nonce, tips), the signer's account identifier and the signature are the fields added to an unsigned extrinsic to create a signed transaction, along with a new length prefix. All the transaction specific information neccessary to create a signed transaction is given in an extrinsic payload. Therefore, it is possible to manually create a signed transaction with the extrsinsic payload and a tool that returns the length prefix.

Submit signed extrinsics

Now that we have the signed transaction, we can submit it to the substrate node with the rpc author section. We can do this simply by piping the signed extrinsic into sumbit.js.

./create-extrinsic.js [input-file-path] | ./create-signed-transaction.js [signature-scheme] [private-key]  | ./submit.js [host] [port]

Copyright © 2019-2020 Entropy Labs

About

CLI for creating and signing extrinsics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages