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

feat: polkadot signer #244

Merged
merged 1 commit into from
Sep 27, 2022
Merged
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
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);
}