proto-signing: Update for new OfflineSigner types

This commit is contained in:
willclarktech 2020-10-21 16:09:31 +02:00
parent 5b5a173f5a
commit f1c081a7d0
No known key found for this signature in database
GPG Key ID: 551A86E2E398ADF7
9 changed files with 126 additions and 83 deletions

View File

@ -1,7 +1,11 @@
import { Secp256k1, Secp256k1Signature, sha256 } from "@cosmjs/crypto";
import { fromBase64, fromHex, toAscii } from "@cosmjs/encoding";
import { fromBase64, fromHex } from "@cosmjs/encoding";
import { coins } from "@cosmjs/launchpad";
import Long from "long";
import { DirectSecp256k1Wallet } from "./directsecp256k1wallet";
import { makeAuthInfoBytes, makeSignBytes } from "./signing";
import { faucet, testVectors } from "./testutils.spec";
describe("DirectSecp256k1Wallet", () => {
// m/44'/118'/0'/0/0
@ -54,15 +58,28 @@ describe("DirectSecp256k1Wallet", () => {
});
});
describe("sign", () => {
describe("signDirect", () => {
it("resolves to valid signature", async () => {
const wallet = await DirectSecp256k1Wallet.fromMnemonic(defaultMnemonic);
const message = toAscii("foo bar");
const signature = await wallet.sign(defaultAddress, message);
const { sequence, bodyBytes } = testVectors[1];
const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic);
const pubkey = {
typeUrl: "/cosmos.crypto.secp256k1.PubKey",
value: fromBase64(faucet.pubkey.value),
};
const fee = coins(2000, "ucosm");
const gasLimit = 200000;
const signDoc = {
bodyBytes: fromHex(bodyBytes),
authInfoBytes: makeAuthInfoBytes([pubkey], fee, gasLimit, sequence),
accountNumber: Long.fromNumber(1),
chainId: "simd-testing",
};
const signDocBytes = makeSignBytes(signDoc);
const signResponse = await wallet.signDirect(faucet.address, signDoc);
const valid = await Secp256k1.verifySignature(
Secp256k1Signature.fromFixedLength(fromBase64(signature.signature)),
sha256(message),
defaultPubkey,
Secp256k1Signature.fromFixedLength(fromBase64(signResponse.signature.signature)),
sha256(signDocBytes),
pubkey.value,
);
expect(valid).toEqual(true);
});

View File

@ -13,9 +13,12 @@ import {
encodeSecp256k1Signature,
makeCosmoshubPath,
rawSecp256k1PubkeyToAddress,
StdSignature,
} from "@cosmjs/launchpad";
import { cosmos } from "./codec";
import { DirectSignResponse, OfflineDirectSigner } from "./signer";
import { makeSignBytes } from "./signing";
/**
* Derivation information required to derive a keypair and an address from a mnemonic.
*/
@ -25,7 +28,7 @@ interface Secp256k1Derivation {
}
/** A wallet for protobuf based signing using SIGN_MODE_DIRECT */
export class DirectSecp256k1Wallet {
export class DirectSecp256k1Wallet implements OfflineDirectSigner {
/**
* Restores a wallet from the given BIP39 mnemonic.
*
@ -113,13 +116,18 @@ export class DirectSecp256k1Wallet {
];
}
public async sign(address: string, message: Uint8Array): Promise<StdSignature> {
public async signDirect(address: string, signDoc: cosmos.tx.v1beta1.ISignDoc): Promise<DirectSignResponse> {
const signBytes = makeSignBytes(signDoc);
if (address !== this.address) {
throw new Error(`Address ${address} not found in wallet`);
}
const hashedMessage = sha256(message);
const hashedMessage = sha256(signBytes);
const signature = await Secp256k1.createSignature(hashedMessage, this.privkey);
const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]);
return encodeSecp256k1Signature(this.pubkey, signatureBytes);
const stdSignature = encodeSecp256k1Signature(this.pubkey, signatureBytes);
return {
signed: signDoc,
signature: stdSignature,
};
}
}

View File

@ -3,4 +3,5 @@ export { cosmosField } from "./decorator";
export { Registry } from "./registry";
export { DirectSecp256k1Wallet } from "./directsecp256k1wallet";
export { decodePubkey, encodePubkey } from "./pubkey";
export { makeAuthInfo, makeSignBytes } from "./signing";
export { OfflineDirectSigner, OfflineSigner } from "./signer";
export { makeAuthInfoBytes, makeSignBytes } from "./signing";

View File

@ -1,56 +1,18 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { fromBase64, fromHex, toHex } from "@cosmjs/encoding";
import Long from "long";
import { cosmos, google } from "./codec";
import { DirectSecp256k1Wallet } from "./directsecp256k1wallet";
import { defaultRegistry } from "./msgs";
import { Registry, TxBodyValue } from "./registry";
import { makeAuthInfo, makeSignBytes } from "./signing";
import { makeAuthInfoBytes, makeSignBytes } from "./signing";
import { faucet, testVectors } from "./testutils.spec";
const { Tx, TxRaw } = cosmos.tx.v1beta1;
const { PubKey } = cosmos.crypto.secp256k1;
const { Any } = google.protobuf;
const faucet = {
mnemonic:
"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone",
pubkey: {
type: "tendermint/PubKeySecp256k1",
value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ",
},
address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
};
const testVectors = [
{
sequence: 0,
signedTxBytes:
"0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712650a4e0a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a02080112130a0d0a0575636f736d12043230303010c09a0c1a40c9dd20e07464d3a688ff4b710b1fbc027e495e797cfa0b4804da2ed117959227772de059808f765aa29b8f92edf30f4c2c5a438e30d3fe6897daa7141e3ce6f9",
signBytes:
"0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712650a4e0a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a02080112130a0d0a0575636f736d12043230303010c09a0c1a0c73696d642d74657374696e672001",
signature:
"c9dd20e07464d3a688ff4b710b1fbc027e495e797cfa0b4804da2ed117959227772de059808f765aa29b8f92edf30f4c2c5a438e30d3fe6897daa7141e3ce6f9",
},
{
sequence: 1,
signedTxBytes:
"0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712670a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a020801180112130a0d0a0575636f736d12043230303010c09a0c1a40525adc7e61565a509c60497b798c549fbf217bb5cd31b24cc9b419d098cc95330c99ecc4bc72448f85c365a4e3f91299a3d40412fb3751bab82f1940a83a0a4c",
signBytes:
"0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712670a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a020801180112130a0d0a0575636f736d12043230303010c09a0c1a0c73696d642d74657374696e672001",
signature:
"525adc7e61565a509c60497b798c549fbf217bb5cd31b24cc9b419d098cc95330c99ecc4bc72448f85c365a4e3f91299a3d40412fb3751bab82f1940a83a0a4c",
},
{
sequence: 2,
signedTxBytes:
"0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712670a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a020801180212130a0d0a0575636f736d12043230303010c09a0c1a40f3f2ca73806f2abbf6e0fe85f9b8af66f0e9f7f79051fdb8abe5bb8633b17da132e82d577b9d5f7a6dae57a144efc9ccc6eef15167b44b3b22a57240109762af",
signBytes:
"0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712670a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a020801180212130a0d0a0575636f736d12043230303010c09a0c1a0c73696d642d74657374696e672001",
signature:
"f3f2ca73806f2abbf6e0fe85f9b8af66f0e9f7f79051fdb8abe5bb8633b17da132e82d577b9d5f7a6dae57a144efc9ccc6eef15167b44b3b22a57240109762af",
},
];
describe("signing", () => {
const chainId = "simd-testing";
const toAddress = "cosmos1qypqxpq9qcrsszg2pvxq6rs0zqg3yyc5lzv7xu";
@ -134,15 +96,21 @@ describe("signing", () => {
await Promise.all(
testVectors.map(async ({ sequence, signBytes, signedTxBytes }) => {
const authInfoBytes = makeAuthInfo([publicKeyAny], feeAmount, gasLimit, sequence);
const signDocBytes = makeSignBytes(txBodyBytes, authInfoBytes, chainId, accountNumber);
const authInfoBytes = makeAuthInfoBytes([publicKeyAny], feeAmount, gasLimit, sequence);
const signDoc = {
bodyBytes: txBodyBytes,
authInfoBytes: authInfoBytes,
chainId: chainId,
accountNumber: Long.fromNumber(accountNumber),
};
const signDocBytes = makeSignBytes(signDoc);
expect(toHex(signDocBytes)).toEqual(signBytes);
const signature = await wallet.sign(address, signDocBytes);
const signResponse = await wallet.signDirect(address, signDoc);
const txRaw = TxRaw.create({
bodyBytes: txBodyBytes,
authInfoBytes: authInfoBytes,
signatures: [fromBase64(signature.signature)],
signatures: [fromBase64(signResponse.signature.signature)],
});
const txRawBytes = Uint8Array.from(TxRaw.encode(txRaw).finish());
const txBytesHex = toHex(txRawBytes);

View File

@ -9,9 +9,9 @@ const { SignDoc, AuthInfo } = cosmos.tx.v1beta1;
/**
* Creates and serializes an AuthInfo document using SIGN_MODE_DIRECT.
*/
export function makeAuthInfo(
export function makeAuthInfoBytes(
pubkeys: readonly google.protobuf.IAny[],
feeAmount: cosmos.base.v1beta1.Coin[],
feeAmount: readonly cosmos.base.v1beta1.Coin[],
gasLimit: number,
sequence: number,
): Uint8Array {
@ -25,23 +25,23 @@ export function makeAuthInfo(
sequence: sequence ? Long.fromNumber(sequence) : undefined,
}),
),
fee: { amount: feeAmount, gasLimit: Long.fromNumber(gasLimit) },
fee: { amount: [...feeAmount], gasLimit: Long.fromNumber(gasLimit) },
};
return Uint8Array.from(AuthInfo.encode(authInfo).finish());
}
export function makeSignBytes(
txBody: Uint8Array,
authInfo: Uint8Array,
chainId: string,
accountNumber: number,
): Uint8Array {
export function makeSignBytes({
accountNumber,
authInfoBytes,
bodyBytes,
chainId,
}: cosmos.tx.v1beta1.ISignDoc): Uint8Array {
const signDoc = SignDoc.create(
omitDefaults({
bodyBytes: txBody,
authInfoBytes: authInfo,
chainId: chainId,
accountNumber: accountNumber,
authInfoBytes: authInfoBytes,
bodyBytes: bodyBytes,
chainId: chainId,
}),
);
return Uint8Array.from(SignDoc.encode(signDoc).finish());

View File

@ -1,2 +1,48 @@
/** @see https://rgxdb.com/r/1NUN74O6 */
export const base64Matcher = /^(?:[a-zA-Z0-9+/]{4})*(?:|(?:[a-zA-Z0-9+/]{3}=)|(?:[a-zA-Z0-9+/]{2}==)|(?:[a-zA-Z0-9+/]{1}===))$/;
export const faucet = {
mnemonic:
"economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone",
pubkey: {
type: "tendermint/PubKeySecp256k1",
value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ",
},
address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
};
export const testVectors = [
{
sequence: 0,
signedTxBytes:
"0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712650a4e0a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a02080112130a0d0a0575636f736d12043230303010c09a0c1a40c9dd20e07464d3a688ff4b710b1fbc027e495e797cfa0b4804da2ed117959227772de059808f765aa29b8f92edf30f4c2c5a438e30d3fe6897daa7141e3ce6f9",
bodyBytes:
"0a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637",
signBytes:
"0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712650a4e0a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a02080112130a0d0a0575636f736d12043230303010c09a0c1a0c73696d642d74657374696e672001",
signature:
"c9dd20e07464d3a688ff4b710b1fbc027e495e797cfa0b4804da2ed117959227772de059808f765aa29b8f92edf30f4c2c5a438e30d3fe6897daa7141e3ce6f9",
},
{
sequence: 1,
signedTxBytes:
"0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712670a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a020801180112130a0d0a0575636f736d12043230303010c09a0c1a40525adc7e61565a509c60497b798c549fbf217bb5cd31b24cc9b419d098cc95330c99ecc4bc72448f85c365a4e3f91299a3d40412fb3751bab82f1940a83a0a4c",
signBytes:
"0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712670a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a020801180112130a0d0a0575636f736d12043230303010c09a0c1a0c73696d642d74657374696e672001",
bodyBytes:
"0a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637",
signature:
"525adc7e61565a509c60497b798c549fbf217bb5cd31b24cc9b419d098cc95330c99ecc4bc72448f85c365a4e3f91299a3d40412fb3751bab82f1940a83a0a4c",
},
{
sequence: 2,
signedTxBytes:
"0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712670a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a020801180212130a0d0a0575636f736d12043230303010c09a0c1a40f3f2ca73806f2abbf6e0fe85f9b8af66f0e9f7f79051fdb8abe5bb8633b17da132e82d577b9d5f7a6dae57a144efc9ccc6eef15167b44b3b22a57240109762af",
bodyBytes:
"0a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d120731323334353637",
signBytes:
"0a93010a90010a1c2f636f736d6f732e62616e6b2e763162657461312e4d736753656e6412700a2d636f736d6f7331706b707472653766646b6c366766727a6c65736a6a766878686c63337234676d6d6b38727336122d636f736d6f7331717970717870713971637273737a673270767871367273307a716733797963356c7a763778751a100a0575636f736d12073132333435363712670a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21034f04181eeba35391b858633a765c4a0c189697b40d216354d50890d350c7029012040a020801180212130a0d0a0575636f736d12043230303010c09a0c1a0c73696d642d74657374696e672001",
signature:
"f3f2ca73806f2abbf6e0fe85f9b8af66f0e9f7f79051fdb8abe5bb8633b17da132e82d577b9d5f7a6dae57a144efc9ccc6eef15167b44b3b22a57240109762af",
},
];

View File

@ -1,7 +1,9 @@
import { HdPath } from "@cosmjs/crypto";
import { AccountData, StdSignature } from "@cosmjs/launchpad";
import { AccountData } from "@cosmjs/launchpad";
import { cosmos } from "./codec";
import { DirectSignResponse, OfflineDirectSigner } from "./signer";
/** A wallet for protobuf based signing using SIGN_MODE_DIRECT */
export declare class DirectSecp256k1Wallet {
export declare class DirectSecp256k1Wallet implements OfflineDirectSigner {
/**
* Restores a wallet from the given BIP39 mnemonic.
*
@ -33,5 +35,5 @@ export declare class DirectSecp256k1Wallet {
get mnemonic(): string;
private get address();
getAccounts(): Promise<readonly AccountData[]>;
sign(address: string, message: Uint8Array): Promise<StdSignature>;
signDirect(address: string, signDoc: cosmos.tx.v1beta1.ISignDoc): Promise<DirectSignResponse>;
}

View File

@ -3,4 +3,5 @@ export { cosmosField } from "./decorator";
export { Registry } from "./registry";
export { DirectSecp256k1Wallet } from "./directsecp256k1wallet";
export { decodePubkey, encodePubkey } from "./pubkey";
export { makeAuthInfo, makeSignBytes } from "./signing";
export { OfflineDirectSigner, OfflineSigner } from "./signer";
export { makeAuthInfoBytes, makeSignBytes } from "./signing";

View File

@ -2,15 +2,15 @@ import { cosmos, google } from "./codec";
/**
* Creates and serializes an AuthInfo document using SIGN_MODE_DIRECT.
*/
export declare function makeAuthInfo(
export declare function makeAuthInfoBytes(
pubkeys: readonly google.protobuf.IAny[],
feeAmount: cosmos.base.v1beta1.Coin[],
feeAmount: readonly cosmos.base.v1beta1.Coin[],
gasLimit: number,
sequence: number,
): Uint8Array;
export declare function makeSignBytes(
txBody: Uint8Array,
authInfo: Uint8Array,
chainId: string,
accountNumber: number,
): Uint8Array;
export declare function makeSignBytes({
accountNumber,
authInfoBytes,
bodyBytes,
chainId,
}: cosmos.tx.v1beta1.ISignDoc): Uint8Array;