Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Save payload and registry info to check signed payload #12

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,6 @@ moonbeam-signer-win.exe

.DS_Store

test/build/
test/build/

payload-*.json
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ You will see the updated parachainBond in the apps (chain state) if connecting t

Some specific actions are provided as a preconfigured feature:

`npm run cli voteCouncil -- --network <network> --ws <ws> <address>` : creates a vote council payload, prompts for signature and sends it
`npm run cli voteCouncil -- --network <network> --ws <ws> --address <address>` : creates a vote council payload, prompts for signature and sends it

`npm run cli voteTechCommittee -- --network <network> --ws <ws> --address <address>` : creates a tech committee vote payload, prompts for signature and sends it

Expand Down
664 changes: 340 additions & 324 deletions package-lock.json

Large diffs are not rendered by default.

36 changes: 35 additions & 1 deletion src/commands/signCommand.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Argv } from "yargs";
import { SignArgs, SignPromptArgs } from "../methods/types";
import { SignAndVerifyArgs, SignArgs, SignPromptArgs } from "../methods/types";
import { sign } from "../methods/sign";
import { isNetworkType } from "../methods/utils";
import { verifyAndSign } from "../methods/verifyAndSign";

export const signOptions = {
type: {
Expand Down Expand Up @@ -44,6 +45,7 @@ export const signCommand = {
},
};

// TODO this should be an optional arg
export const signPromptCommand = {
command: "signPrompt <type> <privateKey|mnemonic> [derivePath]",
describe: "sign byteCode with a private key - using prompt",
Expand All @@ -54,3 +56,35 @@ export const signPromptCommand = {
await sign(isNetworkType(argv.type), argv.privateKey, true, argv.derivePath);
},
};

export const verifyAndSignCommand = {
command: "verifyAndSign",
describe: "sign byteCode with a private key and verify against payload file",
builder: (yargs: Argv) => {
return yargs.options({
...signOptions,
message: {
describe: "message to be signed",
type: "string" as "string",
default: "0x0",
demandOption: true,
},
filePath: {
describe: "file path of the saved extrinsic payload",
type: "string",
default: "payload.json",
demandOption: true,
},
});
},
handler: async (argv: SignAndVerifyArgs) => {
await verifyAndSign(
isNetworkType(argv.type),
argv.privateKey,
false,
argv.derivePath,
argv.filePath,
argv.message
);
},
};
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import yargs from "yargs";

import { signCommand, signPromptCommand } from "./commands/signCommand";
import { verifyAndSignCommand, signCommand, signPromptCommand } from "./commands/signCommand";
import { createAndSendTxCommand } from "./commands/createAndSendCommand";
import { verifyCommand } from "./commands/verifyCommand";
import { getTransactionDataCommand } from "./commands/getTransactionDataCommand";
Expand All @@ -12,6 +12,7 @@ const { hideBin } = require("yargs/helpers");
export const cli = yargs(hideBin(process.argv))
.command(signCommand)
.command(signPromptCommand)
.command(verifyAndSignCommand)
.command(voteCouncilCommand)
.command(voteTechCommitteeCommand)
.command(verifyCommand)
Expand Down
70 changes: 52 additions & 18 deletions src/methods/createAndSendTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,43 @@ import { u8aToHex } from "@polkadot/util";
import { typesBundlePre900 } from "moonbeam-types-bundle";
import { ISubmittableResult, SignerPayloadJSON } from "@polkadot/types/types";
import prompts from "prompts";
import { moonbeamChains } from "./utils";
import fs from "fs";
import { instantiateApi, moonbeamChains } from "./utils";
import { SignerResult, SubmittableExtrinsic } from "@polkadot/api/types";
import { NetworkArgs, TxArgs, TxParam } from "./types";
import {
NetworkArgs,
PayloadVerificationInfo,
RegistryPersistantInfo,
TxArgs,
TxParam,
} from "./types";

export const getRegistryInfo = async (api: ApiPromise): Promise<RegistryPersistantInfo> => {
const [runtimeVersion, chain, chainProps, chainMetadata] = await Promise.all([
api.rpc.state.getRuntimeVersion(),
api.rpc.system.chain(),
api.rpc.system.properties(),
api.rpc.state.getMetadata(),
]);
return {
runtimeVersion: {
specName: runtimeVersion.specName.toString(),
specVersion: Number(runtimeVersion.specVersion),
},
chainName: chain.toString(),
chainProps: {
ss58Format: chainProps.ss58Format.toString(),
tokenSymbol: chainProps.tokenSymbol.toString(),
tokenDecimals: chainProps.tokenDecimals.toString(),
},
metadataHex: chainMetadata.toHex(),
};
};

export async function createAndSendTx(
txArgs: TxArgs,
networkArgs: NetworkArgs,
signatureFunction: (payload: string) => Promise<`0x${string}`>
signatureFunction: (payload: `0x${string}`, filePath: string) => Promise<`0x${string}`>
) {
const { tx, params, address, sudo } = txArgs;
const { ws, network } = networkArgs;
Expand All @@ -19,33 +48,38 @@ export async function createAndSendTx(
? (params as TxParam[])
: (params as string).split(",");

let api: ApiPromise;
if (moonbeamChains.includes(network)) {
api = await ApiPromise.create({
provider: new WsProvider(ws),
typesBundle: typesBundlePre900 as any,
});
} else {
api = await ApiPromise.create({
provider: new WsProvider(ws),
});
}
const api=await instantiateApi(network,ws)

let txExtrinsic: SubmittableExtrinsic<"promise", ISubmittableResult>;
if (sudo) {
txExtrinsic = await api.tx.sudo.sudo(api.tx[section][method](...splitParams));
} else {
txExtrinsic = await api.tx[section][method](...splitParams);
}
console.log('txExtrinsic',txExtrinsic.toHex())
const signer = {
signPayload: (payload: SignerPayloadJSON) => {
signPayload: async (payload: SignerPayloadJSON) => {
console.log("(sign)", payload);

// create the actual payload we will be using
const xp = txExtrinsic.registry.createType("ExtrinsicPayload", payload);
console.log("Transaction data to be signed : ", u8aToHex(xp.toU8a(true)));
const payloadHex = u8aToHex(xp.toU8a(true));
console.log("Transaction data to be signed : ", payloadHex);

// save tx data in a file
const payloadData: PayloadVerificationInfo = {
payload,
registryInfo: await getRegistryInfo(api),
};
const data = JSON.stringify(payloadData, null, 2);
const filePath: string = `payload-${payloadHex.substring(0, 10)}...${payloadHex.substring(
payloadHex.length - 10,
payloadHex.length
)}.json`;
fs.writeFileSync(filePath, data);

return new Promise<SignerResult>(async (resolve) => {
const signature = await signatureFunction(u8aToHex(xp.toU8a(true)));
const signature = await signatureFunction(payloadHex, filePath);
resolve({ id: 1, signature });
});
},
Expand Down Expand Up @@ -74,7 +108,7 @@ export async function createAndSendTx(
});
}
export async function createAndSendTxPrompt(txArgs: TxArgs, networkArgs: NetworkArgs) {
return createAndSendTx(txArgs, networkArgs, async (payload: string) => {
return createAndSendTx(txArgs, networkArgs, async (payload: `0x${string}`) => {
const response = await prompts({
type: "text",
name: "signature",
Expand Down
23 changes: 13 additions & 10 deletions src/methods/getTransactionData.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { createAndSendTx } from "./createAndSendTx";
import { NetworkArgs, TxArgs } from "./types";

export async function getTransactionData(txArgs: TxArgs, networkArgs: NetworkArgs) {
return createAndSendTx(
txArgs,
networkArgs,
// Here we don't want to send the signature,
// just see the payload so we return empty signature
async (_: string) => {
return "0x";
}
);
export async function getTransactionData(txArgs: TxArgs, networkArgs: NetworkArgs):Promise<{payload:`0x${string}`, filePath:string}> {
return new Promise((res)=>{
createAndSendTx(
txArgs,
networkArgs,
// Here we don't want to send the signature,
// just see the payload so we return empty signature
async (payload: `0x${string}`, filePath:string) => {
res({payload, filePath})
return "0x";
}
);
})
}
5 changes: 2 additions & 3 deletions src/methods/sign.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { hexToU8a, stringToU8a, u8aToHex } from "@polkadot/util";
import { hexToU8a, u8aToHex } from "@polkadot/util";
import { Keyring } from "@polkadot/keyring";
import type { KeyringPair } from "@polkadot/keyring/types";
import type { KeypairType } from "@polkadot/util-crypto/types";
import { cryptoWaitReady } from "@polkadot/util-crypto";
import prompts from "prompts";
import { NetworkType } from "./types";

// TODO display payload content
export async function sign(
type: NetworkType,
privKeyOrMnemonic: string,
prompt: boolean,
derivePath: string,
message?: string
): Promise<string> {
): Promise<`0x${string}`> {
if (!["ethereum", "sr25519"].includes(type)) {
throw new Error("Type is not supported");
}
Expand Down
19 changes: 19 additions & 0 deletions src/methods/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { SignerPayloadJSON } from "@polkadot/types/types";

// Command Args
export interface SignArgs extends SignPromptArgs {
message: string;
}
export interface SignAndVerifyArgs extends SignArgs {
filePath: string;
}
export interface SignPromptArgs {
type: string;
privateKey: string;
Expand Down Expand Up @@ -48,3 +53,17 @@ export interface NetworkArgs {
}

export type NetworkType = "ethereum" | "sr25519";

//Registry

export interface RegistryPersistantInfo {
runtimeVersion: { specName: string; specVersion: number };
chainName: string;
chainProps: { ss58Format: string; tokenSymbol: string; tokenDecimals: string };
metadataHex: `0x${string}`;
}

export interface PayloadVerificationInfo {
payload: SignerPayloadJSON;
registryInfo: RegistryPersistantInfo;
}
18 changes: 18 additions & 0 deletions src/methods/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ApiPromise, WsProvider } from "@polkadot/api";
import { typesBundlePre900 } from "moonbeam-types-bundle";
import { NetworkType } from "./types";

export const moonbeamChains = ["moonbase", "moonbeam", "moonriver", "moonshadow"];
Expand All @@ -16,6 +18,22 @@ export function isNetworkType(type: string): NetworkType {
}
}

export async function instantiateApi(network:string,ws:string):Promise<ApiPromise> {

let api: ApiPromise;
if (moonbeamChains.includes(network)) {
api = await ApiPromise.create({
provider: new WsProvider(ws),
typesBundle: typesBundlePre900 as any,
});
} else {
api = await ApiPromise.create({
provider: new WsProvider(ws),
});
}
return api
}

export function exit() {
process.exit();
}
42 changes: 42 additions & 0 deletions src/methods/verifyAndSign.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
u8aToHex,
} from "@polkadot/util";
import fs from "fs";
import { Metadata, TypeRegistry } from "@polkadot/types";
import { NetworkType, PayloadVerificationInfo, RegistryPersistantInfo } from "./types";

import { sign } from "./sign";
import { initRegistry } from "./registryUtils";


export async function verifyAndSign(
type: NetworkType,
privKeyOrMnemonic: string,
prompt: boolean,
derivePath: string,
filePath: string,
message: string
): Promise<`0x${string}`> {
// get the payload data from the file
const rawdata = fs.readFileSync(filePath);
const payloadVerifInfoFromFile: PayloadVerificationInfo = JSON.parse(rawdata as any);

// Recreate registry
const registry = new TypeRegistry();
await initRegistry(registry, payloadVerifInfoFromFile.registryInfo);

// Check the payload against payload info
const hexFromSimpleRegistry = u8aToHex(
registry
.createType("ExtrinsicPayload", payloadVerifInfoFromFile.payload, {
version: payloadVerifInfoFromFile.payload.version,
})
.toU8a(true)
);

if (hexFromSimpleRegistry !== message) {
throw new Error("Payload is not matching payload in filepath");
}

return sign(type, privKeyOrMnemonic, prompt, derivePath, message);
}
Loading