diff --git a/packages/bcp/src/encode.ts b/packages/bcp/src/encode.ts index 94288c48..164fb867 100644 --- a/packages/bcp/src/encode.ts +++ b/packages/bcp/src/encode.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { types } from "@cosmwasm/sdk"; +import { encodeSecp256k1Signature, types } from "@cosmwasm/sdk"; import { Algorithm, Amount, @@ -10,7 +10,6 @@ import { SignedTransaction, UnsignedTransaction, } from "@iov/bcp"; -import { Secp256k1 } from "@iov/crypto"; import { Decimal, Encoding } from "@iov/encoding"; import { TokenInfos } from "./types"; @@ -72,14 +71,12 @@ export function encodeFee(fee: Fee, tokens: TokenInfos): types.StdFee { } export function encodeFullSignature(fullSignature: FullSignature): types.StdSignature { - return { - pub_key: { - type: "tendermint/PubKeySecp256k1", - value: toBase64(Secp256k1.compressPubkey(fullSignature.pubkey.data)), - }, - // Recovery seems to be unused - signature: toBase64(Secp256k1.trimRecoveryByte(fullSignature.signature)), - }; + switch (fullSignature.pubkey.algo) { + case Algorithm.Secp256k1: + return encodeSecp256k1Signature(fullSignature.pubkey.data, fullSignature.signature); + default: + throw new Error("Unsupported signing algorithm"); + } } export function buildUnsignedTx(tx: UnsignedTransaction, tokens: TokenInfos): types.AminoTx { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 12243e6f..3c5016b9 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -38,6 +38,7 @@ "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" }, "dependencies": { + "@iov/crypto": "^2.0.0-alpha.7", "@iov/encoding": "^2.0.0-alpha.7", "axios": "^0.19.0" }, diff --git a/packages/sdk/src/encoding.spec.ts b/packages/sdk/src/encoding.spec.ts new file mode 100644 index 00000000..6aeb8f61 --- /dev/null +++ b/packages/sdk/src/encoding.spec.ts @@ -0,0 +1,59 @@ +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==", + }); + }); + }); +}); diff --git a/packages/sdk/src/encoding.ts b/packages/sdk/src/encoding.ts index 3cee29af..8f10a78d 100644 --- a/packages/sdk/src/encoding.ts +++ b/packages/sdk/src/encoding.ts @@ -1,6 +1,9 @@ +import { Secp256k1 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; -import { StdTx } from "./types"; +import { StdSignature, StdTx } from "./types"; + +const { toBase64 } = Encoding; export function sortJson(json: any): any { if (typeof json !== "object" || json === null) { @@ -24,3 +27,15 @@ export function marshalTx(tx: StdTx): Uint8Array { const json = JSON.stringify(tx); return Encoding.toUtf8(json); } + +export function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature { + return { + // eslint-disable-next-line @typescript-eslint/camelcase + pub_key: { + type: "tendermint/PubKeySecp256k1", + value: toBase64(Secp256k1.compressPubkey(pubkey)), + }, + // Recovery seems to be unused + signature: toBase64(Secp256k1.trimRecoveryByte(signature)), + }; +} diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 23e94da1..1a2e024c 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,6 +1,6 @@ import * as types from "./types"; export { unmarshalTx } from "./decoding"; -export { marshalTx, sortJson } from "./encoding"; +export { encodeSecp256k1Signature, marshalTx, sortJson } from "./encoding"; export { RestClient, TxsResponse } from "./restclient"; export { types }; diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index ed843e87..f6febb93 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -1,16 +1,15 @@ /* eslint-disable @typescript-eslint/camelcase */ import { ChainId, PrehashType, SignableBytes } from "@iov/bcp"; -import { Secp256k1 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import { HdPaths, Secp256k1HdWallet } from "@iov/keycontrol"; -import { marshalTx, sortJson } from "./encoding"; +import { encodeSecp256k1Signature, marshalTx, sortJson } from "./encoding"; import { RestClient } from "./restclient"; import contract from "./testdata/contract.json"; import data from "./testdata/cosmoshub.json"; -import { MsgStoreCodeWrapped, StdSignature, StdTx } from "./types"; +import { MsgStoreCodeWrapped, StdTx } from "./types"; -const { fromBase64, toBase64, toUtf8 } = Encoding; +const { fromBase64, toUtf8 } = Encoding; const httpUrl = "http://localhost:1317"; const defaultNetworkId = "testing"; @@ -105,21 +104,14 @@ describe("RestClient", () => { }); const signBytes = toUtf8(JSON.stringify(signMsg)) as SignableBytes; - const signature = await wallet.createTransactionSignature(signer, signBytes, PrehashType.Sha256); - const fullSignature: StdSignature = { - pub_key: { - type: "tendermint/PubKeySecp256k1", - value: toBase64(Secp256k1.compressPubkey(signer.pubkey.data)), - }, - // Recovery seems to be unused - signature: toBase64(Secp256k1.trimRecoveryByte(signature)), - }; + const rawSignature = await wallet.createTransactionSignature(signer, signBytes, PrehashType.Sha256); + const signature = encodeSecp256k1Signature(signer.pubkey.data, rawSignature); const tx: StdTx = { msg: unsigned.msg, fee: unsigned.fee, memo: memo, - signatures: [fullSignature], + signatures: [signature], }; const postableBytes = marshalTx(tx); diff --git a/packages/sdk/types/encoding.d.ts b/packages/sdk/types/encoding.d.ts index 591bdbb4..22972faf 100644 --- a/packages/sdk/types/encoding.d.ts +++ b/packages/sdk/types/encoding.d.ts @@ -1,3 +1,4 @@ -import { StdTx } from "./types"; +import { StdSignature, StdTx } from "./types"; export declare function sortJson(json: any): any; export declare function marshalTx(tx: StdTx): Uint8Array; +export declare function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature; diff --git a/packages/sdk/types/index.d.ts b/packages/sdk/types/index.d.ts index 0c6dcc71..cddb9250 100644 --- a/packages/sdk/types/index.d.ts +++ b/packages/sdk/types/index.d.ts @@ -1,5 +1,5 @@ import * as types from "./types"; export { unmarshalTx } from "./decoding"; -export { marshalTx, sortJson } from "./encoding"; +export { encodeSecp256k1Signature, marshalTx, sortJson } from "./encoding"; export { RestClient, TxsResponse } from "./restclient"; export { types };