Let Pen.sign return full StdSignature

This commit is contained in:
Simon Warta 2020-02-11 19:34:46 +01:00
parent 923d9d0b87
commit 18740bc8b8
14 changed files with 152 additions and 111 deletions

View File

@ -1,5 +1,5 @@
import { CosmWasmClient } from "./cosmwasmclient";
import { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
import { makeSignBytes, marshalTx } from "./encoding";
import { findAttribute } from "./logs";
import { Secp256k1Pen } from "./pen";
import { RestClient } from "./restclient";
@ -95,7 +95,7 @@ describe("CosmWasmClient", () => {
const chainId = await client.chainId();
const { accountNumber, sequence } = await client.getNonce(faucet.address);
const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
const signature = await pen.sign(signBytes);
const signedTx = {
msg: [sendMsg],
fee: fee,
@ -113,9 +113,7 @@ describe("CosmWasmClient", () => {
it("works", async () => {
pendingWithoutCosmos();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, async signBytes => {
return encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
});
const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, signBytes => pen.sign(signBytes));
const codeId = await client.upload(getRandomizedHackatom());
expect(codeId).toBeGreaterThanOrEqual(1);
});
@ -125,9 +123,7 @@ describe("CosmWasmClient", () => {
it("works with transfer amount", async () => {
pendingWithoutCosmos();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, async signBytes => {
return encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
});
const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, signBytes => pen.sign(signBytes));
const codeId = await client.upload(getRandomizedHackatom());
const transferAmount: readonly Coin[] = [
@ -159,9 +155,7 @@ describe("CosmWasmClient", () => {
it("can instantiate one code multiple times", async () => {
pendingWithoutCosmos();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, async signBytes => {
return encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
});
const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, signBytes => pen.sign(signBytes));
const codeId = await client.upload(getRandomizedHackatom());
const contractAddress1 = await client.instantiate(codeId, {
@ -180,9 +174,7 @@ describe("CosmWasmClient", () => {
it("works", async () => {
pendingWithoutCosmos();
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, async signBytes => {
return encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
});
const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, signBytes => pen.sign(signBytes));
const codeId = await client.upload(getRandomizedHackatom());
// instantiate

View File

@ -1,59 +1 @@
import { Encoding } from "@iov/encoding";
import { encodeSecp256k1Signature } from "./encoding";
const { fromBase64 } = Encoding;
describe("encoding", () => {
describe("encodeSecp256k1Signature", () => {
it("encodes a full signature", () => {
const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP");
const signature = fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
);
expect(encodeSecp256k1Signature(pubkey, signature)).toEqual({
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
});
});
it("compresses uncompressed public keys", () => {
const pubkey = fromBase64(
"BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=",
);
const signature = fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
);
expect(encodeSecp256k1Signature(pubkey, signature)).toEqual({
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
});
});
it("removes recovery values from signature data", () => {
const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP");
const signature = Uint8Array.from([
...fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
),
99,
]);
expect(encodeSecp256k1Signature(pubkey, signature)).toEqual({
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
});
});
});
});
describe("encoding", () => {});

View File

@ -1,10 +1,8 @@
import { Secp256k1 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import { encodeSecp256k1Pubkey } from "./pubkey";
import { Msg, StdFee, StdSignature, StdTx } from "./types";
import { Msg, StdFee, StdTx } from "./types";
const { toBase64, toUtf8 } = Encoding;
const { toUtf8 } = Encoding;
function sortJson(json: any): any {
if (typeof json !== "object" || json === null) {
@ -59,12 +57,3 @@ export function makeSignBytes(
const signMsg = sortJson(signJson);
return toUtf8(JSON.stringify(signMsg));
}
export function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature {
return {
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: encodeSecp256k1Pubkey(pubkey),
// Recovery seems to be unused
signature: toBase64(Secp256k1.trimRecoveryByte(signature)),
};
}

View File

@ -4,8 +4,9 @@ export { logs, types };
export { CosmosAddressBech32Prefix, encodeAddress, isValidAddress } from "./address";
export { unmarshalTx } from "./decoding";
export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
export { makeSignBytes, marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";
export { encodeSecp256k1Signature } from "./signature";
export { CosmWasmClient, ExecuteResult, GetNonceResult, PostTxResult } from "./cosmwasmclient";
export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen";
export {

View File

@ -2,6 +2,7 @@ import { Secp256k1, Secp256k1Signature, Sha256 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import { Secp256k1Pen } from "./pen";
import { decodeSignature } from "./signature";
const { fromHex } = Encoding;
@ -33,12 +34,12 @@ describe("Sec256k1Pen", () => {
"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 { pubkey, signature } = decodeSignature(await pen.sign(data));
const valid = await Secp256k1.verifySignature(
new Secp256k1Signature(signature.slice(0, 32), signature.slice(32, 64)),
new Sha256(data).digest(),
pen.pubkey,
pubkey,
);
expect(valid).toEqual(true);
});

View File

@ -9,6 +9,9 @@ import {
Slip10RawIndex,
} from "@iov/crypto";
import { encodeSecp256k1Signature } from "./signature";
import { StdSignature } from "./types";
export type PrehashType = "sha256" | "sha512" | null;
/**
@ -23,7 +26,7 @@ export type PrehashType = "sha256" | "sha512" | null;
*/
export interface Pen {
readonly pubkey: Uint8Array;
readonly createSignature: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise<Uint8Array>;
readonly sign: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise<StdSignature>;
}
function prehash(bytes: Uint8Array, type: PrehashType): Uint8Array {
@ -73,14 +76,12 @@ export class Secp256k1Pen implements Pen {
}
/**
* Creates a fixed length encoding of the signature parameters r (32 bytes) and s (32 bytes).
* Creates and returns a signature
*/
public async createSignature(
signBytes: Uint8Array,
prehashType: PrehashType = "sha256",
): Promise<Uint8Array> {
public async sign(signBytes: Uint8Array, prehashType: PrehashType = "sha256"): Promise<StdSignature> {
const message = prehash(signBytes, prehashType);
const signature = await Secp256k1.createSignature(message, this.privkey);
return new Uint8Array([...signature.r(32), ...signature.s(32)]);
const fixedLengthSignature = new Uint8Array([...signature.r(32), ...signature.s(32)]);
return encodeSecp256k1Signature(this.pubkey, fixedLengthSignature);
}
}

View File

@ -3,7 +3,7 @@ import { Sha256 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import { assert } from "@iov/utils";
import { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
import { makeSignBytes, marshalTx } from "./encoding";
import { findAttribute, parseLogs } from "./logs";
import { Pen, Secp256k1Pen } from "./pen";
import { encodeBech32Pubkey } from "./pubkey";
@ -86,7 +86,7 @@ async function uploadCustomContract(
const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value;
const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account_number, sequence);
const signature = encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
const signature = await pen.sign(signBytes);
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
return client.postTx(marshalTx(signedTx));
}
@ -127,7 +127,7 @@ async function instantiateContract(
const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value;
const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account_number, sequence);
const signature = encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
const signature = await pen.sign(signBytes);
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
return client.postTx(marshalTx(signedTx));
}
@ -159,7 +159,7 @@ async function executeContract(
const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value;
const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account_number, sequence);
const signature = encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
const signature = await pen.sign(signBytes);
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
return client.postTx(marshalTx(signedTx));
}
@ -261,7 +261,7 @@ describe("RestClient", () => {
const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value;
const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account_number, sequence);
const signature = encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
const signature = await pen.sign(signBytes);
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
const result = await client.postTx(marshalTx(signedTx));
// console.log("Raw log:", result.raw_log);

View File

@ -0,0 +1,79 @@
import { Encoding } from "@iov/encoding";
import { decodeSignature, encodeSecp256k1Signature } from "./signature";
import { StdSignature } from "./types";
const { fromBase64 } = Encoding;
describe("signature", () => {
describe("encodeSecp256k1Signature", () => {
it("encodes a full signature", () => {
const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP");
const signature = fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
);
expect(encodeSecp256k1Signature(pubkey, signature)).toEqual({
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
});
});
it("compresses uncompressed public keys", () => {
const pubkey = fromBase64(
"BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=",
);
const signature = fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
);
expect(encodeSecp256k1Signature(pubkey, signature)).toEqual({
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
});
});
it("removes recovery values from signature data", () => {
const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP");
const signature = Uint8Array.from([
...fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
),
99,
]);
expect(encodeSecp256k1Signature(pubkey, signature)).toEqual({
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
});
});
});
describe("decodeSignature", () => {
it("works for secp256k1", () => {
const signature: StdSignature = {
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
};
expect(decodeSignature(signature)).toEqual({
pubkey: fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"),
signature: fromBase64(
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
),
});
});
});
});

View File

@ -0,0 +1,29 @@
import { Secp256k1 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
import { encodeSecp256k1Pubkey } from "./pubkey";
import { pubkeyType, StdSignature } from "./types";
export function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature {
return {
// eslint-disable-next-line @typescript-eslint/camelcase
pub_key: encodeSecp256k1Pubkey(pubkey),
// Recovery seems to be unused
signature: Encoding.toBase64(Secp256k1.trimRecoveryByte(signature)),
};
}
export function decodeSignature(
signature: StdSignature,
): { readonly pubkey: Uint8Array; readonly signature: Uint8Array } {
switch (signature.pub_key.type) {
// Note: please don't add cases here without writing additional unit tests
case pubkeyType.secp256k1:
return {
pubkey: Encoding.fromBase64(signature.pub_key.value),
signature: Encoding.fromBase64(signature.signature),
};
default:
throw new Error("Unsupported pubkey type");
}
}

View File

@ -1,4 +1,4 @@
import { Msg, StdFee, StdSignature, StdTx } from "./types";
import { Msg, StdFee, StdTx } from "./types";
export declare function marshalTx(tx: StdTx): Uint8Array;
export declare function makeSignBytes(
msgs: readonly Msg[],
@ -8,4 +8,3 @@ export declare function makeSignBytes(
accountNumber: number,
sequence: number,
): Uint8Array;
export declare function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature;

View File

@ -3,8 +3,9 @@ import * as types from "./types";
export { logs, types };
export { CosmosAddressBech32Prefix, encodeAddress, isValidAddress } from "./address";
export { unmarshalTx } from "./decoding";
export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
export { makeSignBytes, marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";
export { encodeSecp256k1Signature } from "./signature";
export { CosmWasmClient, ExecuteResult, GetNonceResult, PostTxResult } from "./cosmwasmclient";
export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen";
export {

View File

@ -1,4 +1,5 @@
import { Slip10RawIndex } from "@iov/crypto";
import { StdSignature } from "./types";
export declare type PrehashType = "sha256" | "sha512" | null;
/**
* A pen is the most basic tool you can think of for signing. It works
@ -12,7 +13,7 @@ export declare type PrehashType = "sha256" | "sha512" | null;
*/
export interface Pen {
readonly pubkey: Uint8Array;
readonly createSignature: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise<Uint8Array>;
readonly sign: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise<StdSignature>;
}
/**
* The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a`
@ -25,7 +26,7 @@ export declare class Secp256k1Pen implements Pen {
private readonly privkey;
private constructor();
/**
* Creates a fixed length encoding of the signature parameters r (32 bytes) and s (32 bytes).
* Creates and returns a signature
*/
createSignature(signBytes: Uint8Array, prehashType?: PrehashType): Promise<Uint8Array>;
sign(signBytes: Uint8Array, prehashType?: PrehashType): Promise<StdSignature>;
}

8
packages/sdk/types/signature.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
import { StdSignature } from "./types";
export declare function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature;
export declare function decodeSignature(
signature: StdSignature,
): {
readonly pubkey: Uint8Array;
readonly signature: Uint8Array;
};

View File

@ -1,7 +1,7 @@
#!/usr/bin/env node
/* eslint-disable @typescript-eslint/camelcase */
const { CosmWasmClient, encodeSecp256k1Signature, Secp256k1Pen } = require("@cosmwasm/sdk");
const { CosmWasmClient, Secp256k1Pen } = require("@cosmwasm/sdk");
const fs = require("fs");
const httpUrl = "http://localhost:1317";
@ -56,9 +56,7 @@ const initMsgCash = {
async function main() {
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, async signBytes => {
return encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
});
const client = CosmWasmClient.makeWritable(httpUrl, faucet.address, signBytes => pen.sign(signBytes));
const wasm = fs.readFileSync(__dirname + "/contracts/cw-erc20.wasm");
const codeId = await client.upload(wasm, "Upload ERC20 contract");