Skip to content
This repository has been archived by the owner on Sep 14, 2023. It is now read-only.

Commit

Permalink
feat: polkadot signer (#244)
Browse files Browse the repository at this point in the history
  • Loading branch information
tjjfvi authored Sep 27, 2022
1 parent 8be489f commit 406ca84
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 24 deletions.
2 changes: 2 additions & 0 deletions effect/atoms/$Extrinsic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ export const $extrinsic = atomFactory("ExtrinsicCodec", (
deriveCodec: M.DeriveCodec,
metadata: M.Metadata,
sign?: M.SignExtrinsic,
prefix?: number,
) => {
return M.$extrinsic({
deriveCodec,
metadata,
sign: sign!,
prefix: prefix!,
});
});
2 changes: 1 addition & 1 deletion effect/std/submitAndWatchExtrinsic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface SendAndWatchExtrinsicProps {
export function sendAndWatchExtrinsic<Props extends SendAndWatchExtrinsicProps>(props: Props) {
const metadata = a.metadata(props.config);
const deriveCodec = a.deriveCodec(metadata);
const $extrinsic = a.$extrinsic(deriveCodec, metadata, props.sign);
const $extrinsic = a.$extrinsic(deriveCodec, metadata, props.sign, props.config.addressPrefix);
const runtimeVersion = a.rpcCall(props.config, "state_getRuntimeVersion", []);
const senderSs58 = sys.anon([props.sender], (sender) => {
return ((): string => {
Expand Down
53 changes: 53 additions & 0 deletions examples/polkadot_signer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { TypeRegistry } from "https://deno.land/x/[email protected]/types/mod.ts";
import * as C from "../mod.ts";
import * as t from "../test-util/mod.ts";
import * as U from "../util/mod.ts";

const config = await t.config();

const root = C
.chain(config)
.pallet("Balances")
.extrinsic("transfer")
.call({
value: 12345n,
dest: {
type: "Id",
value: t.bob.publicKey,
},
})
.signed(
{
type: "Id",
value: t.alice.publicKey,
},
{
signPayload(payload: any) {
const tr = new TypeRegistry();
tr.setSignedExtensions(payload.signedExtensions);
return Promise.resolve(
tr
.createType("ExtrinsicPayload", payload, { version: payload.version })
.sign(t.alice),
);
},
},
)
.sendAndWatch((stop) => {
return (event) => {
if (typeof event.params.result === "string") {
console.log("Extrinsic", event.params.result);
} else {
if (event.params.result.inBlock) {
console.log("Extrinsic in block", event.params.result.inBlock);
} else if (event.params.result.finalized) {
console.log("Extrinsic finalized as of", event.params.result.finalized);
stop();
} else {
console.log("Misc", event.params.result);
}
}
};
});

U.throwIfError(await root.run());
116 changes: 94 additions & 22 deletions frame_metadata/Extrinsic.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as $ from "../deps/scale.ts";
import { assert } from "../deps/std/testing/asserts.ts";
import * as H from "../hashers/mod.ts";
import * as ss58 from "../ss58/mod.ts";
import { hex } from "../util/mod.ts";
import { $null, DeriveCodec } from "./Codec.ts";
import { Metadata } from "./Metadata.ts";

Expand All @@ -21,7 +23,13 @@ export interface Signature {
value: Uint8Array;
}

export type SignExtrinsic = (message: Uint8Array) => Signature | Promise<Signature>;
export type SignExtrinsic =
| ((message: Uint8Array) => Signature | Promise<Signature>)
| PolkadotSigner;

export interface PolkadotSigner {
signPayload(payload: any): Promise<{ signature: string }>;
}

export interface Extrinsic {
protocolVersion: number;
Expand All @@ -41,6 +49,7 @@ interface ExtrinsicCodecProps {
metadata: Metadata;
deriveCodec: DeriveCodec;
sign: SignExtrinsic;
prefix: number;
}

export function $extrinsic(props: ExtrinsicCodecProps): $.Codec<Extrinsic> {
Expand All @@ -53,8 +62,12 @@ export function $extrinsic(props: ExtrinsicCodecProps): $.Codec<Extrinsic> {
const callTy = props.metadata.tys[callTyI];
assert(callTy?.type === "Union");
const $call = deriveCodec(callTyI);
const $extra = getExtrasCodec(signedExtensions.map((x) => x.ty));
const $additional = getExtrasCodec(signedExtensions.map((x) => x.additionalSigned));
const [$extra, extraPjsInfo] = getExtensionInfo(pjsExtraKeyMap, "ty");
const [$additional, additionalPjsInfo] = getExtensionInfo(
pjsAdditionalKeyMap,
"additionalSigned",
);
const pjsInfo = [...extraPjsInfo, ...additionalPjsInfo];

const toSignSize = $call._staticSize + $extra._staticSize + $additional._staticSize;
const totalSize = 1 + $address._staticSize + $sig._staticSize + toSignSize;
Expand All @@ -74,30 +87,67 @@ export function $extrinsic(props: ExtrinsicCodecProps): $.Codec<Extrinsic> {
};
const { signature } = extrinsic;
if (signature) {
$address._encode(buffer, signature.address);
if ("additional" in signature) {
$address._encode(buffer, signature.address);
const toSignBuffer = new $.EncodeBuffer(buffer.stealAlloc(toSignSize));
$call._encode(toSignBuffer, call);
const callEnd = toSignBuffer.finishedSize + toSignBuffer.index;
$extra._encode(toSignBuffer, signature.extra);
const extraEnd = toSignBuffer.finishedSize + toSignBuffer.index;
$additional._encode(toSignBuffer, signature.additional);
const toSignEncoded = toSignBuffer.finish();
const callEncoded = toSignEncoded.subarray(0, callEnd);
const extraEncoded = toSignEncoded.subarray(callEnd, extraEnd);
const toSign = toSignEncoded.length > 256
? H.Blake2_256.hash(toSignEncoded)
: toSignEncoded;
const sig = props.sign(toSign);
if (sig instanceof Promise) {
$sigPromise._encode(buffer, sig);
if ("signPayload" in props.sign) {
const exts = [...signature.extra, ...signature.additional];
const extEnds = [];
for (let i = 0; i < pjsInfo.length; i++) {
pjsInfo[i]!.codec._encode(toSignBuffer, exts[i]);
extEnds.push(toSignBuffer.finishedSize + toSignBuffer.index);
}
const extraEnd = extEnds[extraPjsInfo.length - 1] ?? callEnd;
const toSignEncoded = toSignBuffer.finish();
const callEncoded = toSignEncoded.subarray(0, callEnd);
const extraEncoded = toSignEncoded.subarray(callEnd, extraEnd);
if (signature.address.type !== "Id") {
throw new Error("polkadot signer: address types other than Id are not supported");
}
const payload: Record<string, unknown> = {
address: ss58.encode(props.prefix, signature.address.value),
method: hex.encodePrefixed(callEncoded),
signedExtensions: signedExtensions.map((x) => x.ident),
version: extrinsic.protocolVersion,
};
let last = callEnd;
for (let i = 0; i < pjsInfo.length; i++) {
const { key } = pjsInfo[i]!;
if (!key) throw new Error("polkadot signer: unknown extension");
payload[key] = typeof exts[i] === "number"
? exts[i]
: hex.encodePrefixed(toSignEncoded.subarray(last, extEnds[i]!));
last = extEnds[i]!;
}
const signer = props.sign;
buffer.writeAsync(0, async (buffer) => {
const { signature } = await signer.signPayload(payload);
buffer.insertArray(hex.decode(signature));
});
buffer.insertArray(extraEncoded);
buffer.insertArray(callEncoded);
} else {
$sig._encode(buffer, sig);
$extra._encode(toSignBuffer, signature.extra);
const extraEnd = toSignBuffer.finishedSize + toSignBuffer.index;
$additional._encode(toSignBuffer, signature.additional);
const toSignEncoded = toSignBuffer.finish();
const callEncoded = toSignEncoded.subarray(0, callEnd);
const extraEncoded = toSignEncoded.subarray(callEnd, extraEnd);
const toSign = toSignEncoded.length > 256
? H.Blake2_256.hash(toSignEncoded)
: toSignEncoded;
const sig = props.sign(toSign);
if (sig instanceof Promise) {
$sigPromise._encode(buffer, sig);
} else {
$sig._encode(buffer, sig);
}
buffer.insertArray(extraEncoded);
buffer.insertArray(callEncoded);
}
buffer.insertArray(extraEncoded);
buffer.insertArray(callEncoded);
} else {
$address._encode(buffer, signature.address);
$sig._encode(buffer, signature.sig);
$extra._encode(buffer, signature.extra);
$call._encode(buffer, call);
Expand Down Expand Up @@ -146,7 +196,29 @@ export function $extrinsic(props: ExtrinsicCodecProps): $.Codec<Extrinsic> {
function findExtrinsicTypeParam(name: string) {
return metadata.tys[metadata.extrinsic.ty]?.params.find((x) => x.name === name)?.ty;
}
function getExtrasCodec(is: number[]) {
return $.tuple(...is.map((i) => deriveCodec(i)).filter((x) => x !== $null));
function getExtensionInfo(
keyMap: Record<string, string | undefined>,
key: "ty" | "additionalSigned",
): [codec: $.Codec<any>, pjsInfo: { key: string | undefined; codec: $.Codec<any> }[]] {
const pjsInfo = signedExtensions
.map((e) => ({ key: keyMap[e.ident], codec: deriveCodec(e[key]) }))
.filter((x) => x.codec !== $null);
return [$.tuple(...pjsInfo.map((x) => x.codec)), pjsInfo];
}
}

const pjsExtraKeyMap: Record<string, string> = {
CheckEra: "era",
CheckMortality: "era",
ChargeTransactionPayment: "tip",
CheckNonce: "nonce",
};

const pjsAdditionalKeyMap: Record<string, string> = {
CheckEra: "blockHash",
CheckMortality: "blockHash",
CheckSpecVersion: "specVersion",
CheckTxVersion: "transactionVersion",
CheckVersion: "specVersion",
CheckGenesis: "genesisHash",
};
2 changes: 1 addition & 1 deletion known/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ export const statemint = new Statemint();
export class Subsocial extends Config_("wss://para.subsocial.network", 28) {}
export const subsocial = new Subsocial();

export class Westend extends Config_("wss://westend-rpc.polkadot.io", 0) {}
export class Westend extends Config_("wss://westend-rpc.polkadot.io", 42) {}
export const westend = new Westend();
4 changes: 4 additions & 0 deletions util/hex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ export function encode(bytes: Uint8Array): string {
}
return str;
}

export function encodePrefixed(bytes: Uint8Array): string {
return "0x" + encode(bytes);
}

0 comments on commit 406ca84

Please sign in to comment.