Add DirectSecp256k1Wallet
This commit is contained in:
parent
c453717dc7
commit
329cf17a0d
64
packages/proto-signing/src/directsecp256k1wallet.spec.ts
Normal file
64
packages/proto-signing/src/directsecp256k1wallet.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
63
packages/proto-signing/src/directsecp256k1wallet.ts
Normal file
63
packages/proto-signing/src/directsecp256k1wallet.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
24
packages/proto-signing/types/directsecp256k1wallet.d.ts
vendored
Normal file
24
packages/proto-signing/types/directsecp256k1wallet.d.ts
vendored
Normal 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>;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user