This commit is contained in:
Simon Warta 2020-02-07 00:02:08 +01:00
parent 903166478b
commit 9e4a79f670
5 changed files with 170 additions and 0 deletions

View File

@ -4,4 +4,5 @@ export { CosmosBech32Prefix, decodeBech32Pubkey, encodeAddress, isValidAddress }
export { unmarshalTx } from "./decoding";
export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";
export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen";
export { types };

View File

@ -0,0 +1,49 @@
import { Secp256k1, Secp256k1Signature, Sha256 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import { Secp256k1Pen } from "./pen";
const { fromHex } = Encoding;
describe("Sec256k1Pen", () => {
it("can be constructed", () => {
const pen = new Secp256k1Pen(
"zebra slush diet army arrest purpose hawk source west glimpse custom record",
);
expect(pen).toBeTruthy();
});
describe("getPubkey", () => {
it("returns compressed pubkey", async () => {
// special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling
// m/44'/118'/0'/0/0
// pubkey: 02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6
const pen = new Secp256k1Pen(
"special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling",
);
expect(await pen.getPubkey()).toEqual(
fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"),
);
});
});
describe("createSignature", () => {
it("creates correct signatures", async () => {
// special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling
// m/44'/118'/0'/0/0
// pubkey: 02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6
const pen = new Secp256k1Pen(
"special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling",
);
const data = Encoding.toAscii("foo bar");
const signature = await pen.createSignature(data);
const valid = await Secp256k1.verifySignature(
new Secp256k1Signature(signature.slice(0, 32), signature.slice(32, 64)),
new Sha256(data).digest(),
fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"),
);
expect(valid).toEqual(true);
});
});
});

87
packages/sdk/src/pen.ts Normal file
View File

@ -0,0 +1,87 @@
import {
Bip39,
EnglishMnemonic,
Secp256k1,
Sha256,
Sha512,
Slip10,
Slip10Curve,
Slip10RawIndex,
} from "@iov/crypto";
export type PrehashType = "sha256" | "sha512" | null;
/**
* A pen is the most basic tool you can think of for signing. It works
* everywhere and can be used intuitively by everyone. However, it does not
* come with a great amount of features. End of semi suitable metaphor.
*
* This wraps a single keypair and allows for signing.
*
* Non-goals of this types are: multi account support, persistency, data migrations,
* obfuscation of sensitive data.
*/
export interface Pen {
readonly getPubkey: () => Promise<Uint8Array>;
readonly createSignature: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise<Uint8Array>;
}
function prehash(bytes: Uint8Array, type: PrehashType): Uint8Array {
switch (type) {
case null:
return new Uint8Array([...bytes]);
case "sha256":
return new Sha256(bytes).digest();
case "sha512":
return new Sha512(bytes).digest();
default:
throw new Error("Unknown prehash type");
}
}
/**
* The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a`
* with 0-based account index `a`.
*/
export function makeCosmoshubPath(a: number): readonly Slip10RawIndex[] {
return [
Slip10RawIndex.hardened(44),
Slip10RawIndex.hardened(118),
Slip10RawIndex.hardened(0),
Slip10RawIndex.normal(0),
Slip10RawIndex.normal(a),
];
}
export class Secp256k1Pen implements Pen {
private readonly mnemonic: EnglishMnemonic;
private readonly hdPath: readonly Slip10RawIndex[];
public constructor(mnemonic: string, hdPath: readonly Slip10RawIndex[] = makeCosmoshubPath(0)) {
this.mnemonic = new EnglishMnemonic(mnemonic);
this.hdPath = hdPath;
}
public async getPubkey(): Promise<Uint8Array> {
const privkey = await this.getPrivkey();
const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey;
return Secp256k1.compressPubkey(uncompressed);
}
/**
* Creates a fixed length encoding of the signature parameters r (32 bytes) and s (32 bytes).
*/
public async createSignature(
signBytes: Uint8Array,
prehashType: PrehashType = "sha256",
): Promise<Uint8Array> {
const message = prehash(signBytes, prehashType);
const signature = await Secp256k1.createSignature(message, await this.getPrivkey());
return new Uint8Array([...signature.r(32), ...signature.s(32)]);
}
private async getPrivkey(): Promise<Uint8Array> {
const seed = await Bip39.mnemonicToSeed(this.mnemonic);
return Slip10.derivePath(Slip10Curve.Secp256k1, seed, this.hdPath).privkey;
}
}

View File

@ -3,4 +3,5 @@ export { CosmosBech32Prefix, decodeBech32Pubkey, encodeAddress, isValidAddress }
export { unmarshalTx } from "./decoding";
export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";
export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen";
export { types };

32
packages/sdk/types/pen.d.ts vendored Normal file
View File

@ -0,0 +1,32 @@
import { Slip10RawIndex } from "@iov/crypto";
export declare type PrehashType = "sha256" | "sha512" | null;
/**
* A pen is the most basic tool you can think of for signing. It works
* everywhere and can be used intuitively by everyone. However, it does not
* come with a great amount of features. End of semi suitable metaphor.
*
* This wraps a single keypair and allows for signing.
*
* Non-goals of this types are: multi account support, persistency, data migrations,
* obfuscation of sensitive data.
*/
export interface Pen {
readonly getPubkey: () => Promise<Uint8Array>;
readonly createSignature: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise<Uint8Array>;
}
/**
* The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a`
* with 0-based account index `a`.
*/
export declare function makeCosmoshubPath(a: number): readonly Slip10RawIndex[];
export declare class Secp256k1Pen implements Pen {
private readonly mnemonic;
private readonly hdPath;
constructor(mnemonic: string, hdPath?: readonly Slip10RawIndex[]);
getPubkey(): Promise<Uint8Array>;
/**
* Creates a fixed length encoding of the signature parameters r (32 bytes) and s (32 bytes).
*/
createSignature(signBytes: Uint8Array, prehashType?: PrehashType): Promise<Uint8Array>;
private getPrivkey;
}