Add DirectSecp256k1Wallet

This commit is contained in:
Jake Hartnell 2020-11-11 11:59:37 -08:00
parent c453717dc7
commit 329cf17a0d
3 changed files with 151 additions and 0 deletions

View File

@ -0,0 +1,64 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Secp256k1, Secp256k1Signature, sha256 } from "@cosmjs/crypto";
import { fromBase64, fromHex } from "@cosmjs/encoding";
import { coins } from "@cosmjs/launchpad";
import { DirectSecp256k1Wallet } from "./directsecp256k1wallet";
import { makeAuthInfoBytes, makeSignBytes, makeSignDoc } from "./signing";
import { testVectors } from "./testutils.spec";
describe("DirectSecp256k1Wallet", () => {
const defaultPrivkey = fromHex("b8c462d2bb0c1a92edf44f735021f16c270f28ee2c3d1cb49943a5e70a3c763e");
const defaultAddress = "cosmos1kxt5x5q2l57ma2d434pqpafxdm0mgeg9c8cvtx";
const defaultPubkey = fromHex("03f146c27639179e5b67b8646108f48e1a78b146c74939e34afaa5414ad5c93f8a");
describe("fromKey", () => {
it("works", async () => {
const signer = await DirectSecp256k1Wallet.fromKey(defaultPrivkey);
expect(signer).toBeTruthy();
});
});
describe("getAccounts", () => {
it("resolves to a list of accounts", async () => {
const signer = await DirectSecp256k1Wallet.fromKey(defaultPrivkey);
const accounts = await signer.getAccounts();
expect(accounts.length).toEqual(1);
expect(accounts[0]).toEqual({
address: defaultAddress,
algo: "secp256k1",
pubkey: defaultPubkey,
});
});
});
describe("signDirect", () => {
it("resolves to valid signature", async () => {
const { sequence, bodyBytes } = testVectors[1];
const wallet = await DirectSecp256k1Wallet.fromKey(defaultPrivkey);
const accounts = await wallet.getAccounts();
const pubkey = {
typeUrl: "/cosmos.crypto.secp256k1.PubKey",
value: accounts[0].pubkey,
};
const fee = coins(2000, "ucosm");
const gasLimit = 200000;
const chainId = "simd-testing";
const accountNumber = 1;
const signDoc = makeSignDoc(
fromHex(bodyBytes),
makeAuthInfoBytes([pubkey], fee, gasLimit, sequence),
chainId,
accountNumber,
);
const signDocBytes = makeSignBytes(signDoc);
const { signature } = await wallet.signDirect(accounts[0].address, signDoc);
const valid = await Secp256k1.verifySignature(
Secp256k1Signature.fromFixedLength(fromBase64(signature.signature)),
sha256(signDocBytes),
pubkey.value,
);
expect(valid).toEqual(true);
});
});
});

View File

@ -0,0 +1,63 @@
import { Secp256k1, sha256 } from "@cosmjs/crypto";
import { AccountData, encodeSecp256k1Signature, rawSecp256k1PubkeyToAddress } from "@cosmjs/launchpad";
import { cosmos } from "./codec";
import { DirectSignResponse, OfflineDirectSigner } from "./signer";
import { makeSignBytes } from "./signing";
/**
* A wallet that holds a single secp256k1 keypair.
*
* If you want to work with BIP39 mnemonics and multiple accounts, use DirectSecp256k1HdWallet.
*/
export class DirectSecp256k1Wallet implements OfflineDirectSigner {
/**
* Creates a DirectSecp256k1Wallet 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 fromKey(privkey: Uint8Array, prefix = "cosmos"): Promise<DirectSecp256k1Wallet> {
const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey;
return new DirectSecp256k1Wallet(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<readonly AccountData[]> {
return [
{
algo: "secp256k1",
address: this.address,
pubkey: this.pubkey,
},
];
}
public async signDirect(address: string, signDoc: cosmos.tx.v1beta1.ISignDoc): Promise<DirectSignResponse> {
const signBytes = makeSignBytes(signDoc);
if (address !== this.address) {
throw new Error(`Address ${address} not found in wallet`);
}
const hashedMessage = sha256(signBytes);
const signature = await Secp256k1.createSignature(hashedMessage, this.privkey);
const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]);
const stdSignature = encodeSecp256k1Signature(this.pubkey, signatureBytes);
return {
signed: signDoc,
signature: stdSignature,
};
}
}

View File

@ -0,0 +1,24 @@
import { AccountData } from "@cosmjs/launchpad";
import { cosmos } from "./codec";
import { DirectSignResponse, OfflineDirectSigner } from "./signer";
/**
* A wallet that holds a single secp256k1 keypair.
*
* If you want to work with BIP39 mnemonics and multiple accounts, use DirectSecp256k1HdWallet.
*/
export declare class DirectSecp256k1Wallet implements OfflineDirectSigner {
/**
* Creates a DirectSecp256k1Wallet from the given private key
*
* @param privkey The private key.
* @param prefix The bech32 address prefix (human readable part). Defaults to "cosmos".
*/
static fromKey(privkey: Uint8Array, prefix?: string): Promise<DirectSecp256k1Wallet>;
private readonly pubkey;
private readonly privkey;
private readonly prefix;
private constructor();
private get address();
getAccounts(): Promise<readonly AccountData[]>;
signDirect(address: string, signDoc: cosmos.tx.v1beta1.ISignDoc): Promise<DirectSignResponse>;
}