diff --git a/packages/bcp/src/encode.ts b/packages/bcp/src/encode.ts index ca606156..23f7385d 100644 --- a/packages/bcp/src/encode.ts +++ b/packages/bcp/src/encode.ts @@ -10,6 +10,7 @@ import { SignedTransaction, UnsignedTransaction, } from "@iov/bcp"; +import { Secp256k1 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import { BankTokens, Erc20Token } from "./types"; @@ -70,8 +71,10 @@ 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 normalizedSignature = Secp256k1.trimRecoveryByte(fullSignature.signature); + return encodeSecp256k1Signature(fullSignature.pubkey.data, normalizedSignature); + } default: throw new Error("Unsupported signing algorithm"); } diff --git a/packages/sdk/src/signature.spec.ts b/packages/sdk/src/signature.spec.ts index 23dc8cda..b95612d1 100644 --- a/packages/sdk/src/signature.spec.ts +++ b/packages/sdk/src/signature.spec.ts @@ -39,7 +39,7 @@ describe("signature", () => { }); }); - it("removes recovery values from signature data", () => { + it("throws if signature contains recovery byte", () => { const pubkey = fromBase64("AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP"); const signature = Uint8Array.from([ ...fromBase64( @@ -47,14 +47,9 @@ describe("signature", () => { ), 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==", - }); + expect(() => encodeSecp256k1Signature(pubkey, signature)).toThrowError( + /signature must be 64 bytes long/i, + ); }); }); diff --git a/packages/sdk/src/signature.ts b/packages/sdk/src/signature.ts index 56eb6af6..a71f03eb 100644 --- a/packages/sdk/src/signature.ts +++ b/packages/sdk/src/signature.ts @@ -1,15 +1,25 @@ -import { Secp256k1 } from "@iov/crypto"; 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 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), - // Recovery seems to be unused - signature: Encoding.toBase64(Secp256k1.trimRecoveryByte(signature)), + signature: Encoding.toBase64(signature), }; } diff --git a/packages/sdk/types/signature.d.ts b/packages/sdk/types/signature.d.ts index c6908ddc..a2c9586c 100644 --- a/packages/sdk/types/signature.d.ts +++ b/packages/sdk/types/signature.d.ts @@ -1,4 +1,10 @@ import { StdSignature } from "./types"; +/** + * Takes a binary pubkey and signature to create a signature object + * + * @param pubkey a 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,