Merge pull request #463 from CosmWasm/459-hash-functions

Add convenience hash functions
This commit is contained in:
mergify[bot] 2020-10-13 11:45:14 +00:00 committed by GitHub
commit 7d4809c13d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 103 additions and 46 deletions

View File

@ -1,5 +1,10 @@
# CHANGELOG
## 0.23.1 (unreleased)
- @cosmjs/crypto: Export new convenience functions `keccak256`, `ripemd160`,
`sha1`, `sha256` and `sha512`.
## 0.23.0 (2020-10-09)
- @cosmjs/cli: Expose `HdPath` type.

View File

@ -75,7 +75,9 @@ export async function main(originalArgs: readonly string[]): Promise<void> {
"Random",
"Secp256k1",
"Sha256",
"sha256",
"Sha512",
"sha512",
"Slip10",
"Slip10Curve",
"Slip10RawIndex",
@ -148,7 +150,7 @@ export async function main(originalArgs: readonly string[]): Promise<void> {
const readmeContent = fs.readFileSync(process.cwd() + "/README.md");
fs.writeFileSync(process.cwd() + "/README.md", readmeContent);
const hash = new Sha512(new Uint8Array([])).digest();
const hash = sha512(new Uint8Array([]));
const hexHash = toHex(hash);
export class NewDummyClass {};

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Sha256 } from "@cosmjs/crypto";
import { sha256 } from "@cosmjs/crypto";
import { Bech32, fromHex, fromUtf8, toAscii, toBase64 } from "@cosmjs/encoding";
import {
assertIsBroadcastTxSuccess,
@ -285,7 +285,7 @@ describe("CosmWasmClient", () => {
// check info
expect(result).toEqual(jasmine.objectContaining(expectedInfo));
// check data
expect(new Sha256(result.data).digest()).toEqual(fromHex(expectedInfo.checksum));
expect(sha256(result.data)).toEqual(fromHex(expectedInfo.checksum));
});
it("caches downloads", async () => {

View File

@ -1,4 +1,4 @@
import { Sha256 } from "@cosmjs/crypto";
import { sha256 } from "@cosmjs/crypto";
import { fromBase64, fromHex, toHex } from "@cosmjs/encoding";
import {
AuthExtension,
@ -202,7 +202,7 @@ export class CosmWasmClient {
public async getIdentifier(tx: WrappedStdTx): Promise<string> {
// We consult the REST API because we don't have a local amino encoder
const response = await this.lcdClient.encodeTx(tx);
const hash = new Sha256(fromBase64(response.tx)).digest();
const hash = sha256(fromBase64(response.tx));
return toHex(hash).toUpperCase();
}

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Sha256 } from "@cosmjs/crypto";
import { sha256 } from "@cosmjs/crypto";
import { Bech32, fromAscii, fromHex, fromUtf8, toAscii, toBase64, toHex } from "@cosmjs/encoding";
import {
assertIsBroadcastTxSuccess,
@ -165,7 +165,7 @@ describe("WasmExtension", () => {
expect(lastCode.creator).toEqual(alice.address0);
expect(lastCode.source).toEqual(hackatom.source);
expect(lastCode.builder).toEqual(hackatom.builder);
expect(lastCode.data_hash.toLowerCase()).toEqual(toHex(new Sha256(hackatom.data).digest()));
expect(lastCode.data_hash.toLowerCase()).toEqual(toHex(sha256(hackatom.data)));
});
});
@ -179,7 +179,7 @@ describe("WasmExtension", () => {
expect(code.creator).toEqual(alice.address0);
expect(code.source).toEqual(hackatom.source);
expect(code.builder).toEqual(hackatom.builder);
expect(code.data_hash.toLowerCase()).toEqual(toHex(new Sha256(hackatom.data).digest()));
expect(code.data_hash.toLowerCase()).toEqual(toHex(sha256(hackatom.data)));
expect(code.data).toEqual(toBase64(hackatom.data));
});
});

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Sha256 } from "@cosmjs/crypto";
import { sha256 } from "@cosmjs/crypto";
import { toHex } from "@cosmjs/encoding";
import {
assertIsBroadcastTxSuccess,
@ -259,7 +259,7 @@ describe("SigningCosmWasmClient", () => {
compressedChecksum,
compressedSize,
} = await client.upload(wasm);
expect(originalChecksum).toEqual(toHex(new Sha256(wasm).digest()));
expect(originalChecksum).toEqual(toHex(sha256(wasm)));
expect(originalSize).toEqual(wasm.length);
expect(compressedChecksum).toMatch(/^[0-9a-f]{64}$/);
expect(compressedSize).toBeLessThan(wasm.length * 0.5);

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Sha256 } from "@cosmjs/crypto";
import { sha256 } from "@cosmjs/crypto";
import { toBase64, toHex } from "@cosmjs/encoding";
import {
BroadcastMode,
@ -212,9 +212,9 @@ export class SigningCosmWasmClient extends CosmWasmClient {
const codeIdAttr = findAttribute(result.logs, "message", "code_id");
return {
originalSize: wasmCode.length,
originalChecksum: toHex(new Sha256(wasmCode).digest()),
originalChecksum: toHex(sha256(wasmCode)),
compressedSize: compressed.length,
compressedChecksum: toHex(new Sha256(compressed).digest()),
compressedChecksum: toHex(sha256(compressed)),
codeId: Number.parseInt(codeIdAttr.value, 10),
logs: result.logs,
transactionHash: result.transactionHash,

View File

@ -1,7 +1,7 @@
import { fromAscii, fromBase64, fromHex } from "@cosmjs/encoding";
import { EnglishMnemonic } from "./englishmnemonic";
import { Sha256 } from "./sha";
import { sha256 } from "./sha";
import wordlists from "./testdata/bip39_wordlists.json";
describe("EnglishMnemonic", () => {
@ -11,7 +11,7 @@ describe("EnglishMnemonic", () => {
const bip39EnglishTxt = fromBase64(wordlists.english);
// Ensure we loaded the correct english.txt from https://github.com/bitcoin/bips/tree/master/bip-0039
const checksum = new Sha256(bip39EnglishTxt).digest();
const checksum = sha256(bip39EnglishTxt);
expect(checksum).toEqual(fromHex("2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda"));
const wordsFromSpec: string[] = [];

View File

@ -2,7 +2,7 @@ export { Bip39 } from "./bip39";
export { EnglishMnemonic } from "./englishmnemonic";
export { HashFunction } from "./hash";
export { Hmac } from "./hmac";
export { Keccak256 } from "./keccak";
export { Keccak256, keccak256 } from "./keccak";
export {
Xchacha20poly1305Ietf,
xchacha20NonceLength,
@ -13,10 +13,10 @@ export {
Ed25519Keypair,
} from "./libsodium";
export { Random } from "./random";
export { Ripemd160 } from "./ripemd";
export { Ripemd160, ripemd160 } from "./ripemd";
export { Secp256k1, Secp256k1Keypair } from "./secp256k1";
export { ExtendedSecp256k1Signature, Secp256k1Signature } from "./secp256k1signature";
export { Sha1, Sha256, Sha512 } from "./sha";
export { Sha1, sha1, Sha256, sha256, Sha512, sha512 } from "./sha";
export {
HdPath,
pathToString,

View File

@ -1,6 +1,6 @@
import { fromHex, toHex } from "@cosmjs/encoding";
import { Keccak256 } from "./keccak";
import { Keccak256, keccak256 } from "./keccak";
import keccakVectors from "./testdata/keccak.json";
describe("Keccak256", () => {
@ -25,4 +25,9 @@ describe("Keccak256", () => {
expect(new Keccak256(fromHex(input)).digest()).toEqual(fromHex(output));
}
});
it("exposes a convenience function", () => {
const { in: input, out: output } = keccakVectors.keccak256[0];
expect(keccak256(fromHex(input))).toEqual(fromHex(output));
});
});

View File

@ -24,3 +24,8 @@ export class Keccak256 implements HashFunction {
return new Uint8Array(this.impl.digest());
}
}
/** Convenience function equivalent to `new Keccak256(data).digest()` */
export function keccak256(data: Uint8Array): Uint8Array {
return new Keccak256(data).digest();
}

View File

@ -1,6 +1,6 @@
import { fromHex } from "@cosmjs/encoding";
import { Ripemd160 } from "./ripemd";
import { Ripemd160, ripemd160 } from "./ripemd";
import ripemdVectors from "./testdata/ripemd.json";
describe("Ripemd160", () => {
@ -25,4 +25,9 @@ describe("Ripemd160", () => {
expect(new Ripemd160(fromHex(input)).digest()).toEqual(fromHex(output));
}
});
it("exposes a convenience function", () => {
const { in: input, out: output } = ripemdVectors.ripemd160[0];
expect(ripemd160(fromHex(input))).toEqual(fromHex(output));
});
});

View File

@ -22,3 +22,8 @@ export class Ripemd160 implements HashFunction {
return Uint8Array.from(this.impl.digest());
}
}
/** Convenience function equivalent to `new Ripemd160(data).digest()` */
export function ripemd160(data: Uint8Array): Uint8Array {
return new Ripemd160(data).digest();
}

View File

@ -3,7 +3,7 @@ import { fromHex } from "@cosmjs/encoding";
import { Secp256k1 } from "./secp256k1";
import { ExtendedSecp256k1Signature, Secp256k1Signature } from "./secp256k1signature";
import { Sha256 } from "./sha";
import { sha256 } from "./sha";
describe("Secp256k1", () => {
// How to generate Secp256k1 test vectors:
@ -383,7 +383,7 @@ describe("Secp256k1", () => {
for (const [index, row] of data.entries()) {
const pubkey = (await Secp256k1.makeKeypair(row.privkey)).pubkey;
const messageHash = new Sha256(row.message).digest();
const messageHash = sha256(row.message);
const isValid = await Secp256k1.verifySignature(
Secp256k1Signature.fromDer(row.signature),
messageHash,
@ -494,7 +494,7 @@ describe("Secp256k1", () => {
for (const [index, row] of data.entries()) {
const keypair = await Secp256k1.makeKeypair(row.privkey);
const messageHash = new Sha256(row.message).digest();
const messageHash = sha256(row.message);
// create signature
const calculatedSignature = await Secp256k1.createSignature(messageHash, row.privkey);

View File

@ -1,6 +1,6 @@
import { fromHex, toHex } from "@cosmjs/encoding";
import { Sha256 } from "./sha";
import { Sha256, sha256 } from "./sha";
import shaVectors from "./testdata/sha.json";
describe("Sha256", () => {
@ -25,4 +25,9 @@ describe("Sha256", () => {
expect(new Sha256(fromHex(input)).digest()).toEqual(fromHex(output));
}
});
it("exposes a convenience function", () => {
const { in: input, out: output } = shaVectors.sha256[0];
expect(sha256(fromHex(input))).toEqual(fromHex(output));
});
});

View File

@ -26,6 +26,11 @@ export class Sha1 implements HashFunction {
}
}
/** Convenience function equivalent to `new Sha1(data).digest()` */
export function sha1(data: Uint8Array): Uint8Array {
return new Sha1(data).digest();
}
export class Sha256 implements HashFunction {
public readonly blockSize = 512 / 8;
@ -49,6 +54,11 @@ export class Sha256 implements HashFunction {
}
}
/** Convenience function equivalent to `new Sha256(data).digest()` */
export function sha256(data: Uint8Array): Uint8Array {
return new Sha256(data).digest();
}
export class Sha512 implements HashFunction {
public readonly blockSize = 1024 / 8;
@ -71,3 +81,8 @@ export class Sha512 implements HashFunction {
return new Uint8Array(this.impl.digest());
}
}
/** Convenience function equivalent to `new Sha512(data).digest()` */
export function sha512(data: Uint8Array): Uint8Array {
return new Sha512(data).digest();
}

View File

@ -2,7 +2,7 @@ export { Bip39 } from "./bip39";
export { EnglishMnemonic } from "./englishmnemonic";
export { HashFunction } from "./hash";
export { Hmac } from "./hmac";
export { Keccak256 } from "./keccak";
export { Keccak256, keccak256 } from "./keccak";
export {
Xchacha20poly1305Ietf,
xchacha20NonceLength,
@ -13,10 +13,10 @@ export {
Ed25519Keypair,
} from "./libsodium";
export { Random } from "./random";
export { Ripemd160 } from "./ripemd";
export { Ripemd160, ripemd160 } from "./ripemd";
export { Secp256k1, Secp256k1Keypair } from "./secp256k1";
export { ExtendedSecp256k1Signature, Secp256k1Signature } from "./secp256k1signature";
export { Sha1, Sha256, Sha512 } from "./sha";
export { Sha1, sha1, Sha256, sha256, Sha512, sha512 } from "./sha";
export {
HdPath,
pathToString,

View File

@ -6,3 +6,5 @@ export declare class Keccak256 implements HashFunction {
update(data: Uint8Array): Keccak256;
digest(): Uint8Array;
}
/** Convenience function equivalent to `new Keccak256(data).digest()` */
export declare function keccak256(data: Uint8Array): Uint8Array;

View File

@ -6,3 +6,5 @@ export declare class Ripemd160 implements HashFunction {
update(data: Uint8Array): Ripemd160;
digest(): Uint8Array;
}
/** Convenience function equivalent to `new Ripemd160(data).digest()` */
export declare function ripemd160(data: Uint8Array): Uint8Array;

View File

@ -6,6 +6,8 @@ export declare class Sha1 implements HashFunction {
update(data: Uint8Array): Sha1;
digest(): Uint8Array;
}
/** Convenience function equivalent to `new Sha1(data).digest()` */
export declare function sha1(data: Uint8Array): Uint8Array;
export declare class Sha256 implements HashFunction {
readonly blockSize: number;
private readonly impl;
@ -13,6 +15,8 @@ export declare class Sha256 implements HashFunction {
update(data: Uint8Array): Sha256;
digest(): Uint8Array;
}
/** Convenience function equivalent to `new Sha256(data).digest()` */
export declare function sha256(data: Uint8Array): Uint8Array;
export declare class Sha512 implements HashFunction {
readonly blockSize: number;
private readonly impl;
@ -20,3 +24,5 @@ export declare class Sha512 implements HashFunction {
update(data: Uint8Array): Sha512;
digest(): Uint8Array;
}
/** Convenience function equivalent to `new Sha512(data).digest()` */
export declare function sha512(data: Uint8Array): Uint8Array;

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto";
import { Secp256k1, Secp256k1Signature, sha256 } from "@cosmjs/crypto";
import { fromBase64 } from "@cosmjs/encoding";
import {
coins,
@ -124,7 +124,7 @@ describe("LedgerSigner", () => {
expect(signed).toEqual(signDoc);
const valid = await Secp256k1.verifySignature(
Secp256k1Signature.fromFixedLength(fromBase64(signature.signature)),
new Sha256(serializeSignDoc(signed)).digest(),
sha256(serializeSignDoc(signed)),
fistAccount.pubkey,
);
expect(valid).toEqual(true);

View File

@ -1,4 +1,4 @@
import { Ripemd160, Sha256 } from "@cosmjs/crypto";
import { ripemd160, sha256 } from "@cosmjs/crypto";
import { Bech32, fromBase64 } from "@cosmjs/encoding";
import { PubKey, pubkeyType } from "./types";
@ -7,8 +7,8 @@ export function rawSecp256k1PubkeyToAddress(pubkeyRaw: Uint8Array, prefix: strin
if (pubkeyRaw.length !== 33) {
throw new Error(`Invalid Secp256k1 pubkey length (compressed): ${pubkeyRaw.length}`);
}
const hash1 = new Sha256(pubkeyRaw).digest();
const hash2 = new Ripemd160(hash1).digest();
const hash1 = sha256(pubkeyRaw);
const hash2 = ripemd160(hash1);
return Bech32.encode(prefix, hash2);
}
@ -24,14 +24,14 @@ export function pubkeyToAddress(pubkey: PubKey, prefix: string): string {
if (pubkeyBytes.length !== 32) {
throw new Error(`Invalid Ed25519 pubkey length: ${pubkeyBytes.length}`);
}
const hash = new Sha256(pubkeyBytes).digest();
const hash = sha256(pubkeyBytes);
return Bech32.encode(prefix, hash.slice(0, 20));
}
case pubkeyType.sr25519: {
if (pubkeyBytes.length !== 32) {
throw new Error(`Invalid Sr25519 pubkey length: ${pubkeyBytes.length}`);
}
const hash = new Sha256(pubkeyBytes).digest();
const hash = sha256(pubkeyBytes);
return Bech32.encode(prefix, hash.slice(0, 20));
}
default:

View File

@ -1,4 +1,4 @@
import { Sha256 } from "@cosmjs/crypto";
import { sha256 } from "@cosmjs/crypto";
import { fromBase64, fromHex, toHex } from "@cosmjs/encoding";
import { Uint53 } from "@cosmjs/math";
@ -207,7 +207,7 @@ export class CosmosClient {
public async getIdentifier(tx: WrappedStdTx): Promise<string> {
// We consult the REST API because we don't have a local amino encoder
const response = await this.lcdClient.encodeTx(tx);
const hash = new Sha256(fromBase64(response.tx)).digest();
const hash = sha256(fromBase64(response.tx));
return toHex(hash).toUpperCase();
}

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto";
import { Secp256k1, Secp256k1Signature, sha256 } from "@cosmjs/crypto";
import { fromBase64, fromHex } from "@cosmjs/encoding";
import { serializeSignDoc, StdSignDoc } from "./encoding";
@ -124,7 +124,7 @@ describe("Secp256k1HdWallet", () => {
expect(signed).toEqual(signDoc);
const valid = await Secp256k1.verifySignature(
Secp256k1Signature.fromFixedLength(fromBase64(signature.signature)),
new Sha256(serializeSignDoc(signed)).digest(),
sha256(serializeSignDoc(signed)),
defaultPubkey,
);
expect(valid).toEqual(true);

View File

@ -5,7 +5,7 @@ import {
pathToString,
Random,
Secp256k1,
Sha256,
sha256,
Slip10,
Slip10Curve,
stringToPath,
@ -263,7 +263,7 @@ export class Secp256k1HdWallet implements OfflineSigner {
if (signerAddress !== this.address) {
throw new Error(`Address ${signerAddress} not found in wallet`);
}
const message = new Sha256(serializeSignDoc(signDoc)).digest();
const message = sha256(serializeSignDoc(signDoc));
const signature = await Secp256k1.createSignature(message, this.privkey);
const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]);
return {

View File

@ -1,4 +1,4 @@
import { Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto";
import { Secp256k1, Secp256k1Signature, sha256 } from "@cosmjs/crypto";
import { makeSignDoc, serializeSignDoc } from "./encoding";
import { decodeSignature } from "./signature";
@ -33,7 +33,7 @@ export async function findSequenceForSignedTx(
const signBytes = serializeSignDoc(
makeSignDoc(tx.value.msg, tx.value.fee, chainId, tx.value.memo || "", accountNumber, s),
);
const prehashed = new Sha256(signBytes).digest();
const prehashed = sha256(signBytes);
const valid = await Secp256k1.verifySignature(secp256keSignature, prehashed, pubkey);
if (valid) return s;
}

View File

@ -1,4 +1,4 @@
import { Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto";
import { Secp256k1, Secp256k1Signature, sha256 } from "@cosmjs/crypto";
import { fromBase64, fromHex, toAscii } from "@cosmjs/encoding";
import { DirectSecp256k1Wallet } from "./directsecp256k1wallet";
@ -61,7 +61,7 @@ describe("DirectSecp256k1Wallet", () => {
const signature = await wallet.sign(defaultAddress, message);
const valid = await Secp256k1.verifySignature(
Secp256k1Signature.fromFixedLength(fromBase64(signature.signature)),
new Sha256(message).digest(),
sha256(message),
defaultPubkey,
);
expect(valid).toEqual(true);

View File

@ -4,7 +4,7 @@ import {
HdPath,
Random,
Secp256k1,
Sha256,
sha256,
Slip10,
Slip10Curve,
} from "@cosmjs/crypto";
@ -117,7 +117,7 @@ export class DirectSecp256k1Wallet {
if (address !== this.address) {
throw new Error(`Address ${address} not found in wallet`);
}
const hashedMessage = new Sha256(message).digest();
const hashedMessage = sha256(message);
const signature = await Secp256k1.createSignature(hashedMessage, this.privkey);
const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]);
return encodeSecp256k1Signature(this.pubkey, signatureBytes);