Merge pull request #79 from confio/pen-sign
Let Pen.sign return full StdSignature
This commit is contained in:
commit
ff57e8d2a7
@ -67,8 +67,8 @@ describe("encode", () => {
|
||||
},
|
||||
];
|
||||
|
||||
describe("encodePubKey", () => {
|
||||
it("encodes a Secp256k1 pubkey", () => {
|
||||
describe("encodePubkey", () => {
|
||||
it("works for compressed public key", () => {
|
||||
expect(encodePubkey(defaultPubkey)).toEqual({
|
||||
type: "tendermint/PubKeySecp256k1",
|
||||
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { encodeSecp256k1Signature, types } from "@cosmwasm/sdk";
|
||||
import { encodeSecp256k1Pubkey, encodeSecp256k1Signature, types } from "@cosmwasm/sdk";
|
||||
import {
|
||||
Algorithm,
|
||||
Amount,
|
||||
@ -10,19 +10,18 @@ import {
|
||||
SignedTransaction,
|
||||
UnsignedTransaction,
|
||||
} from "@iov/bcp";
|
||||
import { Secp256k1 } from "@iov/crypto";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
|
||||
import { BankTokens, Erc20Token } from "./types";
|
||||
|
||||
const { toBase64 } = Encoding;
|
||||
|
||||
// TODO: This function seems to be unused and is not well tested (e.g. uncompressed secp256k1 or ed25519)
|
||||
export function encodePubkey(pubkey: PubkeyBundle): types.PubKey {
|
||||
switch (pubkey.algo) {
|
||||
case Algorithm.Secp256k1:
|
||||
return {
|
||||
type: types.pubkeyType.secp256k1,
|
||||
value: toBase64(pubkey.data),
|
||||
};
|
||||
return encodeSecp256k1Pubkey(pubkey.data);
|
||||
case Algorithm.Ed25519:
|
||||
return {
|
||||
type: types.pubkeyType.ed25519,
|
||||
@ -70,8 +69,11 @@ export function encodeFee(fee: Fee, tokens: BankTokens): types.StdFee {
|
||||
|
||||
export function encodeFullSignature(fullSignature: FullSignature): types.StdSignature {
|
||||
switch (fullSignature.pubkey.algo) {
|
||||
case Algorithm.Secp256k1:
|
||||
return encodeSecp256k1Signature(fullSignature.pubkey.data, fullSignature.signature);
|
||||
case Algorithm.Secp256k1: {
|
||||
const compressedPubkey = Secp256k1.compressPubkey(fullSignature.pubkey.data);
|
||||
const normalizedSignature = Secp256k1.trimRecoveryByte(fullSignature.signature);
|
||||
return encodeSecp256k1Signature(compressedPubkey, normalizedSignature);
|
||||
}
|
||||
default:
|
||||
throw new Error("Unsupported signing algorithm");
|
||||
}
|
||||
|
||||
@ -65,7 +65,7 @@ const sendTokensMsg: types.MsgSend = {
|
||||
};
|
||||
|
||||
const signBytes = makeSignBytes([sendTokensMsg], defaultFee, defaultNetworkId, memo, account_number, sequence);
|
||||
const signature = encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
|
||||
const signature = await pen.sign(signBytes);
|
||||
const signedTx: types.StdTx = {
|
||||
msg: [sendTokensMsg],
|
||||
fee: defaultFee,
|
||||
|
||||
@ -40,7 +40,7 @@ const instantiateContract = async (initClient: RestClient, initPen: Secp256k1Pen
|
||||
};
|
||||
const account = (await initClient.authAccounts(faucetAddress)).result.value;
|
||||
const signBytes = makeSignBytes([instantiateContractMsg], defaultFee, networkId, memo, account);
|
||||
const signature = encodeSecp256k1Signature(initPen.pubkey, await initPen.createSignature(signBytes));
|
||||
const signature = await initPen.sign(signBytes);
|
||||
const signedTx = {
|
||||
msg: [instantiateContractMsg],
|
||||
fee: defaultFee,
|
||||
@ -71,7 +71,7 @@ const executeContract = async (execClient: RestClient, execPen: Secp256k1Pen, co
|
||||
};
|
||||
const account = (await execClient.authAccounts(faucetAddress)).result.value;
|
||||
const signBytes = makeSignBytes([instantiateContractMsg], defaultFee, networkId, memo, account);
|
||||
const signature = encodeSecp256k1Signature(execPen.pubkey, await execPen.createSignature(signBytes));
|
||||
const signature = await execPen.sign(signBytes);
|
||||
const signedTx = {
|
||||
msg: [instantiateContractMsg],
|
||||
fee: defaultFee,
|
||||
|
||||
@ -136,7 +136,7 @@ export function main(originalArgs: readonly string[]): void {
|
||||
const pubkey = encodeSecp256k1Pubkey(pen.pubkey);
|
||||
const address = encodeAddress(pubkey, "cosmos");
|
||||
const data = Encoding.toAscii("foo bar");
|
||||
const signature = await pen.createSignature(data);
|
||||
const signature = await pen.sign(data);
|
||||
|
||||
console.info("Done testing, will exit now.");
|
||||
process.exit(0);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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", () => {});
|
||||
|
||||
@ -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)),
|
||||
};
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ const { fromBase64 } = Encoding;
|
||||
|
||||
describe("pubkey", () => {
|
||||
describe("encodeSecp256k1Pubkey", () => {
|
||||
it("encodes a full signature", () => {
|
||||
it("encodes a compresed pubkey", () => {
|
||||
const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP");
|
||||
expect(encodeSecp256k1Pubkey(pubkey)).toEqual({
|
||||
type: "tendermint/PubKeySecp256k1",
|
||||
@ -15,14 +15,11 @@ describe("pubkey", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("compresses uncompressed public keys", () => {
|
||||
it("throws for uncompressed public keys", () => {
|
||||
const pubkey = fromBase64(
|
||||
"BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=",
|
||||
);
|
||||
expect(encodeSecp256k1Pubkey(pubkey)).toEqual({
|
||||
type: "tendermint/PubKeySecp256k1",
|
||||
value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ",
|
||||
});
|
||||
expect(() => encodeSecp256k1Pubkey(pubkey)).toThrowError(/public key must be compressed secp256k1/i);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -1,13 +1,15 @@
|
||||
import { Secp256k1 } from "@iov/crypto";
|
||||
import { Bech32, Encoding } from "@iov/encoding";
|
||||
import equal from "fast-deep-equal";
|
||||
|
||||
import { Bech32PubKey, PubKey, pubkeyType } from "./types";
|
||||
|
||||
export function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey {
|
||||
if (pubkey.length !== 33 || (pubkey[0] !== 0x02 && pubkey[0] !== 0x03)) {
|
||||
throw new Error("Public key must be compressed secp256k1, i.e. 33 bytes starting with 0x02 or 0x03");
|
||||
}
|
||||
return {
|
||||
type: pubkeyType.secp256k1,
|
||||
value: Encoding.toBase64(Secp256k1.compressPubkey(pubkey)),
|
||||
value: Encoding.toBase64(pubkey),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
69
packages/sdk/src/signature.spec.ts
Normal file
69
packages/sdk/src/signature.spec.ts
Normal file
@ -0,0 +1,69 @@
|
||||
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("throws when getting uncompressed public keys", () => {
|
||||
const pubkey = fromBase64(
|
||||
"BE8EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQE7WHpoHoNswYeoFkuYpYSKK4mzFzMV/dB0DVAy4lnNU=",
|
||||
);
|
||||
const signature = fromBase64(
|
||||
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
|
||||
);
|
||||
expect(() => encodeSecp256k1Signature(pubkey, signature)).toThrowError(
|
||||
/public key must be compressed secp256k1/i,
|
||||
);
|
||||
});
|
||||
|
||||
it("throws if signature contains recovery byte", () => {
|
||||
const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP");
|
||||
const signature = Uint8Array.from([
|
||||
...fromBase64(
|
||||
"1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
|
||||
),
|
||||
99,
|
||||
]);
|
||||
expect(() => encodeSecp256k1Signature(pubkey, signature)).toThrowError(
|
||||
/signature must be 64 bytes long/i,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
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==",
|
||||
),
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
39
packages/sdk/src/signature.ts
Normal file
39
packages/sdk/src/signature.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { Encoding } from "@iov/encoding";
|
||||
|
||||
import { encodeSecp256k1Pubkey } from "./pubkey";
|
||||
import { pubkeyType, StdSignature } from "./types";
|
||||
|
||||
/**
|
||||
* Takes a binary pubkey and signature to create a signature object
|
||||
*
|
||||
* @param pubkey a compressed secp256k1 public key
|
||||
* @param signature a 64 byte fixed length representation of secp256k1 signature components r and s
|
||||
*/
|
||||
export function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature {
|
||||
if (signature.length !== 64) {
|
||||
throw new Error(
|
||||
"Signature must be 64 bytes long. Cosmos SDK uses a 2x32 byte fixed length encoding for the secp256k1 signature integers r and s.",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
pub_key: encodeSecp256k1Pubkey(pubkey),
|
||||
signature: Encoding.toBase64(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");
|
||||
}
|
||||
}
|
||||
3
packages/sdk/types/encoding.d.ts
vendored
3
packages/sdk/types/encoding.d.ts
vendored
@ -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;
|
||||
|
||||
3
packages/sdk/types/index.d.ts
vendored
3
packages/sdk/types/index.d.ts
vendored
@ -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 {
|
||||
|
||||
7
packages/sdk/types/pen.d.ts
vendored
7
packages/sdk/types/pen.d.ts
vendored
@ -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>;
|
||||
}
|
||||
|
||||
14
packages/sdk/types/signature.d.ts
vendored
Normal file
14
packages/sdk/types/signature.d.ts
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
import { StdSignature } from "./types";
|
||||
/**
|
||||
* Takes a binary pubkey and signature to create a signature object
|
||||
*
|
||||
* @param pubkey a compressed secp256k1 public key
|
||||
* @param signature a 64 byte fixed length representation of secp256k1 signature components r and s
|
||||
*/
|
||||
export declare function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature;
|
||||
export declare function decodeSignature(
|
||||
signature: StdSignature,
|
||||
): {
|
||||
readonly pubkey: Uint8Array;
|
||||
readonly signature: Uint8Array;
|
||||
};
|
||||
@ -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");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user