From 04a74aac839d2d748df416f244c8c861d68afb94 Mon Sep 17 00:00:00 2001 From: Shane Vitarana Date: Wed, 7 Oct 2020 12:03:07 -0400 Subject: [PATCH] Add Secpk256k1 offline key signer Renamed Secp256k1Wallet -> Secp256k1HdWallet Renamed Secp256k1Key -> Secp256k1Wallet Renamed fromPrivateKey function Fixed linter issues Rename Secp256k1Wallet.fromMnemonic everywhere Revert "Rename Secp256k1Wallet.fromMnemonic everywhere" This reverts commit 18c52968bc1a234755c61d1d5c67850222239bd7. Revert "Fixed linter issues" This reverts commit 8e6fce93d809f160e8dafa9478f56f9effcf561a. Revert "Renamed fromPrivateKey function" This reverts commit d628f186bb126d4fc2b205dbc0b95b3198af02df. Revert "Renamed Secp256k1Key -> Secp256k1Wallet" This reverts commit 35a082701de9bd2c8cf89b25a168f12212d4ba8a. Revert "Renamed Secp256k1Wallet -> Secp256k1HdWallet" This reverts commit bd69f293e1ae7d96ad0afd295af360de70863e13. Ran linter Renamed fromPrivateKey Updated Typescript definitions --- packages/launchpad/src/secp256k1key.spec.ts | 54 ++++++++++++++++++++ packages/launchpad/src/secp256k1key.ts | 56 +++++++++++++++++++++ packages/launchpad/types/secp256k1key.d.ts | 18 +++++++ 3 files changed, 128 insertions(+) create mode 100644 packages/launchpad/src/secp256k1key.spec.ts create mode 100644 packages/launchpad/src/secp256k1key.ts create mode 100644 packages/launchpad/types/secp256k1key.d.ts diff --git a/packages/launchpad/src/secp256k1key.spec.ts b/packages/launchpad/src/secp256k1key.spec.ts new file mode 100644 index 00000000..643a4b21 --- /dev/null +++ b/packages/launchpad/src/secp256k1key.spec.ts @@ -0,0 +1,54 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto"; +import { fromBase64, fromHex } from "@cosmjs/encoding"; + +import { serializeSignDoc, StdSignDoc } from "./encoding"; +import { Secp256k1Key } from "./secp256k1key"; + +describe("Secp256k1Key", () => { + const defaultPrivkey = fromHex("b8c462d2bb0c1a92edf44f735021f16c270f28ee2c3d1cb49943a5e70a3c763e"); + const defaultAddress = "cosmos1kxt5x5q2l57ma2d434pqpafxdm0mgeg9c8cvtx"; + const defaultPubkey = fromHex("03f146c27639179e5b67b8646108f48e1a78b146c74939e34afaa5414ad5c93f8a"); + + describe("fromPrivkey", () => { + it("works", async () => { + const signer = await Secp256k1Key.fromPrivkey(defaultPrivkey); + expect(signer).toBeTruthy(); + }); + }); + + describe("getAccounts", () => { + it("resolves to a list of accounts", async () => { + const signer = await Secp256k1Key.fromPrivkey(defaultPrivkey); + const accounts = await signer.getAccounts(); + expect(accounts.length).toEqual(1); + expect(accounts[0]).toEqual({ + address: defaultAddress, + algo: "secp256k1", + pubkey: defaultPubkey, + }); + }); + }); + + describe("sign", () => { + it("resolves to valid signature if enabled", async () => { + const signer = await Secp256k1Key.fromPrivkey(defaultPrivkey); + const signDoc: StdSignDoc = { + msgs: [], + fee: { amount: [], gas: "23" }, + chain_id: "foochain", + memo: "hello, world", + account_number: "7", + sequence: "54", + }; + const { signed, signature } = await signer.sign(defaultAddress, signDoc); + expect(signed).toEqual(signDoc); + const valid = await Secp256k1.verifySignature( + Secp256k1Signature.fromFixedLength(fromBase64(signature.signature)), + new Sha256(serializeSignDoc(signed)).digest(), + defaultPubkey, + ); + expect(valid).toEqual(true); + }); + }); +}); diff --git a/packages/launchpad/src/secp256k1key.ts b/packages/launchpad/src/secp256k1key.ts new file mode 100644 index 00000000..33bde15f --- /dev/null +++ b/packages/launchpad/src/secp256k1key.ts @@ -0,0 +1,56 @@ +import { Secp256k1, Sha256 } from "@cosmjs/crypto"; + +import { rawSecp256k1PubkeyToAddress } from "./address"; +import { serializeSignDoc, StdSignDoc } from "./encoding"; +import { encodeSecp256k1Signature } from "./signature"; +import { AccountData, OfflineSigner, SignResponse } from "./signer"; + +export class Secp256k1Key implements OfflineSigner { + /** + * Creates a Secp256k1 key signer from the given private key + * + * @param privkey The private key. + * @param prefix The bech32 address prefix (human readable part). Defaults to "cosmos". + */ + public static async fromPrivkey(privkey: Uint8Array, prefix = "cosmos"): Promise { + const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; + return new Secp256k1Key(privkey, Secp256k1.compressPubkey(uncompressed), prefix); + } + + private readonly pubkey: Uint8Array; + private readonly privkey: Uint8Array; + private readonly prefix: string; + + private constructor(privkey: Uint8Array, pubkey: Uint8Array, prefix: string) { + this.privkey = privkey; + this.pubkey = pubkey; + this.prefix = prefix; + } + + private get address(): string { + return rawSecp256k1PubkeyToAddress(this.pubkey, this.prefix); + } + + public async getAccounts(): Promise { + return [ + { + algo: "secp256k1", + address: this.address, + pubkey: this.pubkey, + }, + ]; + } + + public async sign(signerAddress: string, signDoc: StdSignDoc): Promise { + if (signerAddress !== this.address) { + throw new Error(`Address ${signerAddress} not found in wallet`); + } + const message = new Sha256(serializeSignDoc(signDoc)).digest(); + const signature = await Secp256k1.createSignature(message, this.privkey); + const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]); + return { + signed: signDoc, + signature: encodeSecp256k1Signature(this.pubkey, signatureBytes), + }; + } +} diff --git a/packages/launchpad/types/secp256k1key.d.ts b/packages/launchpad/types/secp256k1key.d.ts new file mode 100644 index 00000000..1f5d6edc --- /dev/null +++ b/packages/launchpad/types/secp256k1key.d.ts @@ -0,0 +1,18 @@ +import { StdSignDoc } from "./encoding"; +import { AccountData, OfflineSigner, SignResponse } from "./signer"; +export declare class Secp256k1Key implements OfflineSigner { + /** + * Creates a Secp256k1 key signer from the given private key + * + * @param privkey The private key. + * @param prefix The bech32 address prefix (human readable part). Defaults to "cosmos". + */ + static fromPrivkey(privkey: Uint8Array, prefix?: string): Promise; + private readonly pubkey; + private readonly privkey; + private readonly prefix; + private constructor(); + private get address(); + getAccounts(): Promise; + sign(signerAddress: string, signDoc: StdSignDoc): Promise; +}