diff --git a/packages/cli/examples/delegate.ts b/packages/cli/examples/delegate.ts index 9d6fc327..88a4a47e 100644 --- a/packages/cli/examples/delegate.ts +++ b/packages/cli/examples/delegate.ts @@ -1,5 +1,7 @@ -const pen = await Secp256k1Pen.fromMnemonic("enlist hip relief stomach skate base shallow young switch frequent cry park"); -const senderAddress = pen.address("cosmos"); +const wallet = await Secp256k1Wallet.fromMnemonic( + "enlist hip relief stomach skate base shallow young switch frequent cry park", +); +const [{ address: senderAddress }] = await wallet.getAccounts(); const client = new CosmosClient("http://localhost:1317"); @@ -11,8 +13,8 @@ const msg: MsgDelegate = { // curl http://localhost:1317/staking/validators | jq '.result[0].operator_address' validator_address: "cosmosvaloper1gjvanqxc774u6ed9thj4gpn9gj5zus5u32enqn", amount: coin(300000, "ustake"), - } -} + }, +}; const fee = { amount: coins(2000, "ucosm"), gas: "120000", // 120k @@ -26,7 +28,7 @@ const { accountNumber, sequence } = await client.getNonce(senderAddress); console.log("Account/sequence:", accountNumber, sequence); const signBytes = makeSignBytes([msg], fee, chainId, memo, accountNumber, sequence); -const signature = await pen.sign(signBytes); +const signature = await wallet.sign(senderAddress, signBytes); const signedTx: StdTx = { msg: [msg], fee: fee, diff --git a/packages/cli/examples/faucet_addresses.ts b/packages/cli/examples/faucet_addresses.ts index a7be953b..211ff61f 100644 --- a/packages/cli/examples/faucet_addresses.ts +++ b/packages/cli/examples/faucet_addresses.ts @@ -1,9 +1,9 @@ -const 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"; +const 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"; for (let i of [0, 1, 2, 3, 4]) { - const pen = await Secp256k1Pen.fromMnemonic(mnemonic, makeCosmoshubPath(i)); - const pubkey = toBase64(pen.pubkey); - const address = pubkeyToAddress(encodeSecp256k1Pubkey(pen.pubkey), "cosmos"); + const wallet = await Secp256k1Wallet.fromMnemonic(mnemonic, makeCosmoshubPath(i), "cosmos"); + const [{ address, pubkey }] = await wallet.getAccounts(); console.info(`Address ${i}: ${address}`); console.info(`Pubkey ${i}: ${pubkey}`); } diff --git a/packages/cli/examples/generate_address.ts b/packages/cli/examples/generate_address.ts index e0ccfb0e..cbf7ae95 100644 --- a/packages/cli/examples/generate_address.ts +++ b/packages/cli/examples/generate_address.ts @@ -1,7 +1,6 @@ const mnemonic = Bip39.encode(Random.getBytes(16)).toString(); -const pen = await Secp256k1Pen.fromMnemonic(mnemonic); -const pubkey = encodeSecp256k1Pubkey(pen.pubkey); -const address = pubkeyToAddress(pubkey, "cosmos"); +const wallet = await Secp256k1Wallet.fromMnemonic(mnemonic); +const [{ address, pubkey }] = await wallet.getAccounts(); console.info("mnemonic:", mnemonic); console.info("pubkey:", pubkey); diff --git a/packages/cli/examples/helpers.ts b/packages/cli/examples/helpers.ts index c52ddc40..3e39b1af 100644 --- a/packages/cli/examples/helpers.ts +++ b/packages/cli/examples/helpers.ts @@ -4,7 +4,7 @@ interface Options { feeToken: string; gasPrice: number; bech32prefix: string; -}; +} const defaultOptions: Options = { httpUrl: "https://lcd.demo-09.cosmwasm.com", @@ -12,7 +12,7 @@ const defaultOptions: Options = { feeToken: "ucosm", gasPrice: 0.025, bech32prefix: "cosmos", -} +}; const defaultFaucetUrl = "https://faucet.demo-09.cosmwasm.com/credit"; @@ -22,7 +22,7 @@ const buildFeeTable = (feeToken: string, gasPrice: number): FeeTable => { return { amount: [{ amount: amount.toString(), denom: denom }], gas: gas.toString(), - } + }; }; return { @@ -32,7 +32,7 @@ const buildFeeTable = (feeToken: string, gasPrice: number): FeeTable => { exec: stdFee(200000, feeToken, gasPrice), send: stdFee(80000, feeToken, gasPrice), changeAdmin: stdFee(80000, feeToken, gasPrice), - } + }; }; // TODO: hit faucet @@ -43,22 +43,20 @@ const buildFeeTable = (feeToken: string, gasPrice: number): FeeTable => { // } // } -const penAddress = (pen: Secp256k1Pen, prefix: string): string => { - const pubkey = encodeSecp256k1Pubkey(pen.pubkey); - return pubkeyToAddress(pubkey, prefix); -} - -const connect = async (mnemonic: string, opts: Partial): Promise<{ - client: SigningCosmWasmClient, - address: string, +const connect = async ( + mnemonic: string, + opts: Partial, +): Promise<{ + client: SigningCosmWasmClient; + address: string; }> => { - const options: Options = {...defaultOptions, ...opts}; + const options: Options = { ...defaultOptions, ...opts }; const feeTable = buildFeeTable(options.feeToken, options.gasPrice); - const pen = await Secp256k1Pen.fromMnemonic(mnemonic); - const address = penAddress(pen, options.bech32prefix); + const wallet = await Secp256k1Wallet.fromMnemonic(mnemonic); + const [{ address }] = await wallet.getAccounts(); - const client = new SigningCosmWasmClient(options.httpUrl, address, signBytes => pen.sign(signBytes), feeTable); - return {client, address}; + const client = new SigningCosmWasmClient(options.httpUrl, address, wallet, feeTable); + return { client, address }; }; // smartQuery assumes the content is proper JSON data and parses before returning it @@ -80,32 +78,32 @@ const loadOrCreateMnemonic = (filename: string): string => { fs.writeFileSync(filename, mnemonic, "utf8"); return mnemonic; } -} +}; const hitFaucet = async (faucetUrl: string, address: string, ticker: string): Promise => { const r = await axios.post(defaultFaucetUrl, { ticker, address }); console.log(r.status); console.log(r.data); -} +}; const randomAddress = async (prefix: string): Promise => { const mnemonic = Bip39.encode(Random.getBytes(16)).toString(); return mnemonicToAddress(prefix, mnemonic); -} +}; const mnemonicToAddress = async (prefix: string, mnemonic: string): Promise => { - const pen = await Secp256k1Pen.fromMnemonic(mnemonic); - const pubkey = encodeSecp256k1Pubkey(pen.pubkey); - return pubkeyToAddress(pubkey, prefix); -} + const wallet = await Secp256k1Wallet.fromMnemonic(mnemonic); + const [{ address }] = await wallet.getAccounts(); + return address; +}; const downloadWasm = async (url: string): Promise => { - const r = await axios.get(url, { responseType: "arraybuffer"}); + const r = await axios.get(url, { responseType: "arraybuffer" }); if (r.status !== 200) { throw new Error(`Download error: ${r.status}`); } return r.data; -} +}; -const getAttibute = (logs: readonly logs.Log[], key: string): string|undefined => - logs[0].events[0].attributes.find(x => x.key == key)?.value +const getAttibute = (logs: readonly logs.Log[], key: string): string | undefined => + logs[0].events[0].attributes.find((x) => x.key == key)?.value; diff --git a/packages/cli/examples/local_faucet.ts b/packages/cli/examples/local_faucet.ts index 687e3512..ce72447f 100644 --- a/packages/cli/examples/local_faucet.ts +++ b/packages/cli/examples/local_faucet.ts @@ -14,5 +14,5 @@ const faucetMnemonic = "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone"; const faucetAddress = "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6"; -const pen = await Secp256k1Pen.fromMnemonic(faucetMnemonic); +const wallet = await Secp256k1Wallet.fromMnemonic(faucetMnemonic); const client = new LcdClient(defaultHttpUrl); diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index d5ead030..41c4474a 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -97,10 +97,10 @@ export function main(originalArgs: readonly string[]): void { "MsgDelegate", "MsgSend", "LcdClient", - "Pen", + "OfflineSigner", "PubKey", "pubkeyToAddress", - "Secp256k1Pen", + "Secp256k1Wallet", "SigningCosmosClient", "StdFee", "StdTx", @@ -145,11 +145,10 @@ export function main(originalArgs: readonly string[]): void { assert(Decimal.fromAtomics("12870000", 6).toString() === "12.87"); const mnemonic = Bip39.encode(Random.getBytes(16)).toString(); - const pen = await Secp256k1Pen.fromMnemonic(mnemonic, makeCosmoshubPath(0)); - const pubkey = encodeSecp256k1Pubkey(pen.pubkey); - const address = pubkeyToAddress(pubkey, "cosmos"); + const wallet = await Secp256k1Wallet.fromMnemonic(mnemonic, makeCosmoshubPath(0)); + const [{ address }] = await wallet.getAccounts(); const data = toAscii("foo bar"); - const signature = await pen.sign(data); + const signature = await wallet.sign(address, data); console.info("Done testing, will exit now."); process.exit(0); diff --git a/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts b/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts index e114e219..19979b42 100644 --- a/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts +++ b/packages/cosmwasm/src/cosmwasmclient.searchtx.spec.ts @@ -7,7 +7,7 @@ import { LcdClient, makeSignBytes, MsgSend, - Secp256k1Pen, + Secp256k1Wallet, } from "@cosmjs/sdk38"; import { assert, sleep } from "@cosmjs/utils"; @@ -48,10 +48,8 @@ describe("CosmWasmClient.searchTx", () => { beforeAll(async () => { if (wasmdEnabled()) { - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(wasmd.endpoint, alice.address0, (signBytes) => - pen.sign(signBytes), - ); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(wasmd.endpoint, alice.address0, wallet); { const recipient = makeRandomAddress(); @@ -107,7 +105,7 @@ describe("CosmWasmClient.searchTx", () => { const { accountNumber, sequence } = await client.getNonce(); const chainId = await client.getChainId(); const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await pen.sign(signBytes); + const signature = await wallet.sign(alice.address0, signBytes); const tx: CosmosSdkTx = { type: "cosmos-sdk/StdTx", value: { diff --git a/packages/cosmwasm/src/cosmwasmclient.spec.ts b/packages/cosmwasm/src/cosmwasmclient.spec.ts index 3928a075..555e113c 100644 --- a/packages/cosmwasm/src/cosmwasmclient.spec.ts +++ b/packages/cosmwasm/src/cosmwasmclient.spec.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/camelcase */ import { Sha256 } from "@cosmjs/crypto"; import { Bech32, fromHex, fromUtf8, toAscii, toBase64 } from "@cosmjs/encoding"; -import { makeSignBytes, MsgSend, Secp256k1Pen, StdFee } from "@cosmjs/sdk38"; +import { makeSignBytes, MsgSend, Secp256k1Wallet, StdFee } from "@cosmjs/sdk38"; import { assert, sleep } from "@cosmjs/utils"; import { ReadonlyDate } from "readonly-date"; @@ -204,7 +204,7 @@ describe("CosmWasmClient", () => { describe("postTx", () => { it("works", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); const client = new CosmWasmClient(wasmd.endpoint); const memo = "My first contract on chain"; @@ -235,7 +235,7 @@ describe("CosmWasmClient", () => { const chainId = await client.getChainId(); const { accountNumber, sequence } = await client.getNonce(alice.address0); const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await pen.sign(signBytes); + const signature = await wallet.sign(alice.address0, signBytes); const signedTx = { msg: [sendMsg], fee: fee, @@ -388,10 +388,8 @@ describe("CosmWasmClient", () => { beforeAll(async () => { if (wasmdEnabled()) { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(wasmd.endpoint, alice.address0, (signBytes) => - pen.sign(signBytes), - ); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(wasmd.endpoint, alice.address0, wallet); const { codeId } = await client.upload(getHackatom().data); const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() }; const { contractAddress } = await client.instantiate(codeId, initMsg, "random hackatom"); @@ -441,10 +439,8 @@ describe("CosmWasmClient", () => { beforeAll(async () => { if (wasmdEnabled()) { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(wasmd.endpoint, alice.address0, (signBytes) => - pen.sign(signBytes), - ); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(wasmd.endpoint, alice.address0, wallet); const { codeId } = await client.upload(getHackatom().data); const initMsg = { verifier: makeRandomAddress(), beneficiary: makeRandomAddress() }; const { contractAddress } = await client.instantiate(codeId, initMsg, "a different hackatom"); diff --git a/packages/cosmwasm/src/lcdapi/wasm.spec.ts b/packages/cosmwasm/src/lcdapi/wasm.spec.ts index 08dac3cf..0d3aa836 100644 --- a/packages/cosmwasm/src/lcdapi/wasm.spec.ts +++ b/packages/cosmwasm/src/lcdapi/wasm.spec.ts @@ -8,9 +8,9 @@ import { coins, LcdClient, makeSignBytes, - Pen, + OfflineSigner, PostTxsResponse, - Secp256k1Pen, + Secp256k1Wallet, setupAuthExtension, StdFee, } from "@cosmjs/sdk38"; @@ -47,7 +47,7 @@ function makeWasmClient(apiUrl: string): WasmClient { async function uploadContract( client: WasmClient, - pen: Pen, + signer: OfflineSigner, contract: ContractUploadInstructions, ): Promise { const memo = "My first contract on chain"; @@ -72,14 +72,14 @@ async function uploadContract( const { account_number, sequence } = (await client.auth.account(alice.address0)).result.value; const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); - const signature = await pen.sign(signBytes); + const signature = await signer.sign(alice.address0, signBytes); const signedTx = makeSignedTx(theMsg, fee, memo, signature); return client.postTx(signedTx); } async function instantiateContract( client: WasmClient, - pen: Pen, + signer: OfflineSigner, codeId: number, beneficiaryAddress: string, transferAmount?: readonly Coin[], @@ -110,14 +110,14 @@ async function instantiateContract( const { account_number, sequence } = (await client.auth.account(alice.address0)).result.value; const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); - const signature = await pen.sign(signBytes); + const signature = await signer.sign(alice.address0, signBytes); const signedTx = makeSignedTx(theMsg, fee, memo, signature); return client.postTx(signedTx); } async function executeContract( client: WasmClient, - pen: Pen, + signer: OfflineSigner, contractAddress: string, msg: object, ): Promise { @@ -138,7 +138,7 @@ async function executeContract( const { account_number, sequence } = (await client.auth.account(alice.address0)).result.value; const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); - const signature = await pen.sign(signBytes); + const signature = await signer.sign(alice.address0, signBytes); const signedTx = makeSignedTx(theMsg, fee, memo, signature); return client.postTx(signedTx); } @@ -256,7 +256,7 @@ describe("wasm", () => { describe("postTx", () => { it("can upload, instantiate and execute wasm", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); const client = makeWasmClient(wasmd.endpoint); const transferAmount = [coin(1234, "ucosm"), coin(321, "ustake")]; @@ -267,7 +267,7 @@ describe("wasm", () => { // upload { // console.log("Raw log:", result.raw_log); - const result = await uploadContract(client, pen, getHackatom()); + const result = await uploadContract(client, wallet, getHackatom()); expect(result.code).toBeFalsy(); const logs = parseLogs(result.logs); const codeIdAttr = findAttribute(logs, "message", "code_id"); @@ -281,7 +281,7 @@ describe("wasm", () => { // instantiate { - const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount); + const result = await instantiateContract(client, wallet, codeId, beneficiaryAddress, transferAmount); expect(result.code).toBeFalsy(); // console.log("Raw log:", result.raw_log); const logs = parseLogs(result.logs); @@ -297,7 +297,7 @@ describe("wasm", () => { // execute { - const result = await executeContract(client, pen, contractAddress, { release: {} }); + const result = await executeContract(client, wallet, contractAddress, { release: {} }); expect(result.data).toEqual("F00BAA"); expect(result.code).toBeFalsy(); // console.log("Raw log:", result.logs); @@ -324,7 +324,7 @@ describe("wasm", () => { describe("query", () => { it("can list upload code", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); const client = makeWasmClient(wasmd.endpoint); // check with contracts were here first to compare @@ -334,7 +334,7 @@ describe("wasm", () => { // upload data const hackatom = getHackatom(); - const result = await uploadContract(client, pen, hackatom); + const result = await uploadContract(client, wallet, hackatom); expect(result.code).toBeFalsy(); const logs = parseLogs(result.logs); const codeIdAttr = findAttribute(logs, "message", "code_id"); @@ -362,7 +362,7 @@ describe("wasm", () => { it("can list contracts and get info", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); const client = makeWasmClient(wasmd.endpoint); const beneficiaryAddress = makeRandomAddress(); const transferAmount: readonly Coin[] = [ @@ -378,7 +378,7 @@ describe("wasm", () => { if (existingInfos.length > 0) { codeId = existingInfos[existingInfos.length - 1].id; } else { - const uploadResult = await uploadContract(client, pen, getHackatom()); + const uploadResult = await uploadContract(client, wallet, getHackatom()); expect(uploadResult.code).toBeFalsy(); const uploadLogs = parseLogs(uploadResult.logs); const codeIdAttr = findAttribute(uploadLogs, "message", "code_id"); @@ -394,7 +394,7 @@ describe("wasm", () => { expect(contract.label).toMatch(/^.+$/); } - const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount); + const result = await instantiateContract(client, wallet, codeId, beneficiaryAddress, transferAmount); expect(result.code).toBeFalsy(); const logs = parseLogs(result.logs); const contractAddressAttr = findAttribute(logs, "message", "contract_address"); @@ -438,12 +438,12 @@ describe("wasm", () => { beforeAll(async () => { if (wasmdEnabled()) { - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const uploadResult = await uploadContract(client, pen, getHackatom()); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const uploadResult = await uploadContract(client, wallet, getHackatom()); assert(!uploadResult.code); const uploadLogs = parseLogs(uploadResult.logs); const codeId = Number.parseInt(findAttribute(uploadLogs, "message", "code_id").value, 10); - const instantiateResult = await instantiateContract(client, pen, codeId, makeRandomAddress()); + const instantiateResult = await instantiateContract(client, wallet, codeId, makeRandomAddress()); assert(!instantiateResult.code); const instantiateLogs = parseLogs(instantiateResult.logs); const contractAddressAttr = findAttribute(instantiateLogs, "message", "contract_address"); diff --git a/packages/cosmwasm/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm/src/signingcosmwasmclient.spec.ts index b1e71f07..927458a7 100644 --- a/packages/cosmwasm/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm/src/signingcosmwasmclient.spec.ts @@ -1,6 +1,6 @@ import { Sha256 } from "@cosmjs/crypto"; import { toHex } from "@cosmjs/encoding"; -import { AuthExtension, coin, coins, LcdClient, Secp256k1Pen, setupAuthExtension } from "@cosmjs/sdk38"; +import { AuthExtension, coin, coins, LcdClient, Secp256k1Wallet, setupAuthExtension } from "@cosmjs/sdk38"; import { assert } from "@cosmjs/utils"; import { isPostTxFailure, PrivateCosmWasmClient } from "./cosmwasmclient"; @@ -17,8 +17,8 @@ function makeWasmClient(apiUrl: string): LcdClient & AuthExtension & WasmExtensi describe("SigningCosmWasmClient", () => { describe("makeReadOnly", () => { it("can be constructed", async () => { - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); expect(client).toBeTruthy(); }); }); @@ -26,8 +26,8 @@ describe("SigningCosmWasmClient", () => { describe("getHeight", () => { it("always uses authAccount implementation", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); const openedClient = (client as unknown) as PrivateCosmWasmClient; const blockLatestSpy = spyOn(openedClient.lcdClient, "blocksLatest").and.callThrough(); @@ -44,8 +44,8 @@ describe("SigningCosmWasmClient", () => { describe("upload", () => { it("works", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); const wasm = getHackatom().data; const { codeId, @@ -63,8 +63,8 @@ describe("SigningCosmWasmClient", () => { it("can set builder and source", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); const hackatom = getHackatom(); const meta: UploadMeta = { @@ -82,8 +82,8 @@ describe("SigningCosmWasmClient", () => { describe("instantiate", () => { it("works with transfer amount", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); const { codeId } = await client.upload(getHackatom().data); const transferAmount = [coin(1234, "ucosm"), coin(321, "ustake")]; @@ -108,8 +108,8 @@ describe("SigningCosmWasmClient", () => { it("works with admin", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); const { codeId } = await client.upload(getHackatom().data); const beneficiaryAddress = makeRandomAddress(); @@ -131,8 +131,8 @@ describe("SigningCosmWasmClient", () => { it("can instantiate one code multiple times", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); const { codeId } = await client.upload(getHackatom().data); const contractAddress1 = await client.instantiate( @@ -158,8 +158,8 @@ describe("SigningCosmWasmClient", () => { describe("updateAdmin", () => { it("can update an admin", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); const { codeId } = await client.upload(getHackatom().data); const beneficiaryAddress = makeRandomAddress(); @@ -191,8 +191,8 @@ describe("SigningCosmWasmClient", () => { describe("clearAdmin", () => { it("can clear an admin", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); const { codeId } = await client.upload(getHackatom().data); const beneficiaryAddress = makeRandomAddress(); @@ -224,8 +224,8 @@ describe("SigningCosmWasmClient", () => { describe("migrate", () => { it("can can migrate from one code ID to another", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); const { codeId: codeId1 } = await client.upload(getHackatom().data); const { codeId: codeId2 } = await client.upload(getHackatom().data); @@ -263,8 +263,8 @@ describe("SigningCosmWasmClient", () => { describe("execute", () => { it("works", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); const { codeId } = await client.upload(getHackatom().data); // instantiate @@ -304,8 +304,8 @@ describe("SigningCosmWasmClient", () => { describe("sendTokens", () => { it("works", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); // instantiate const transferAmount = coins(7890, "ucosm"); diff --git a/packages/cosmwasm/src/signingcosmwasmclient.ts b/packages/cosmwasm/src/signingcosmwasmclient.ts index e713e82f..4a4b1df9 100644 --- a/packages/cosmwasm/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm/src/signingcosmwasmclient.ts @@ -7,6 +7,7 @@ import { coins, makeSignBytes, MsgSend, + OfflineSigner, StdFee, StdSignature, StdTx, @@ -164,7 +165,7 @@ function createPostTxErrorMessage(result: PostTxFailure): string { export class SigningCosmWasmClient extends CosmWasmClient { public readonly senderAddress: string; - private readonly signCallback: SigningCallback; + private readonly signer: OfflineSigner; private readonly fees: FeeTable; /** @@ -175,14 +176,14 @@ export class SigningCosmWasmClient extends CosmWasmClient { * * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) * @param senderAddress The address that will sign and send transactions using this instance - * @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction. + * @param signer An implementation of OfflineSigner which can provide signatures for transactions, potentially requiring user input. * @param customFees The fees that are paid for transactions * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns */ public constructor( apiUrl: string, senderAddress: string, - signCallback: SigningCallback, + signer: OfflineSigner, customFees?: Partial, broadcastMode = BroadcastMode.Block, ) { @@ -190,7 +191,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { this.anyValidAddress = senderAddress; this.senderAddress = senderAddress; - this.signCallback = signCallback; + this.signer = signer; this.fees = { ...defaultFees, ...(customFees || {}) }; } @@ -222,7 +223,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.getChainId(); const signBytes = makeSignBytes([storeCodeMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await this.signCallback(signBytes); + const signature = await this.signer.sign(this.senderAddress, signBytes); const signedTx: StdTx = { msg: [storeCodeMsg], fee: fee, @@ -272,7 +273,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { const chainId = await this.getChainId(); const signBytes = makeSignBytes([instantiateMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await this.signCallback(signBytes); + const signature = await this.signer.sign(this.senderAddress, signBytes); const signedTx: StdTx = { msg: [instantiateMsg], fee: fee, @@ -306,7 +307,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.getChainId(); const signBytes = makeSignBytes([updateAdminMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await this.signCallback(signBytes); + const signature = await this.signer.sign(this.senderAddress, signBytes); const signedTx: StdTx = { msg: [updateAdminMsg], fee: fee, @@ -336,7 +337,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.getChainId(); const signBytes = makeSignBytes([clearAdminMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await this.signCallback(signBytes); + const signature = await this.signer.sign(this.senderAddress, signBytes); const signedTx: StdTx = { msg: [clearAdminMsg], fee: fee, @@ -374,7 +375,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.getChainId(); const signBytes = makeSignBytes([msg], fee, chainId, memo, accountNumber, sequence); - const signature = await this.signCallback(signBytes); + const signature = await this.signer.sign(this.senderAddress, signBytes); const signedTx: StdTx = { msg: [msg], fee: fee, @@ -412,7 +413,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.getChainId(); const signBytes = makeSignBytes([executeMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await this.signCallback(signBytes); + const signature = await this.signer.sign(this.senderAddress, signBytes); const signedTx: StdTx = { msg: [executeMsg], fee: fee, @@ -449,7 +450,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.getChainId(); const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await this.signCallback(signBytes); + const signature = await this.signer.sign(this.senderAddress, signBytes); const signedTx: StdTx = { msg: [sendMsg], fee: fee, diff --git a/packages/cosmwasm/types/signingcosmwasmclient.d.ts b/packages/cosmwasm/types/signingcosmwasmclient.d.ts index 2a3e17ea..18949878 100644 --- a/packages/cosmwasm/types/signingcosmwasmclient.d.ts +++ b/packages/cosmwasm/types/signingcosmwasmclient.d.ts @@ -1,4 +1,4 @@ -import { BroadcastMode, Coin, StdFee, StdSignature } from "@cosmjs/sdk38"; +import { BroadcastMode, Coin, OfflineSigner, StdFee, StdSignature } from "@cosmjs/sdk38"; import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient"; import { Log } from "./logs"; export interface SigningCallback { @@ -83,7 +83,7 @@ export interface ExecuteResult { } export declare class SigningCosmWasmClient extends CosmWasmClient { readonly senderAddress: string; - private readonly signCallback; + private readonly signer; private readonly fees; /** * Creates a new client with signing capability to interact with a CosmWasm blockchain. This is the bigger brother of CosmWasmClient. @@ -93,14 +93,14 @@ export declare class SigningCosmWasmClient extends CosmWasmClient { * * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) * @param senderAddress The address that will sign and send transactions using this instance - * @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction. + * @param signer An implementation of OfflineSigner which can provide signatures for transactions, potentially requiring user input. * @param customFees The fees that are paid for transactions * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns */ constructor( apiUrl: string, senderAddress: string, - signCallback: SigningCallback, + signer: OfflineSigner, customFees?: Partial, broadcastMode?: BroadcastMode, ); diff --git a/packages/demo-staking/src/index.spec.ts b/packages/demo-staking/src/index.spec.ts index 49bd7877..6c6fa869 100644 --- a/packages/demo-staking/src/index.spec.ts +++ b/packages/demo-staking/src/index.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/camelcase */ import { SigningCosmWasmClient } from "@cosmjs/cosmwasm"; -import { Coin, coins, makeCosmoshubPath, Secp256k1Pen } from "@cosmjs/sdk38"; +import { Coin, coins, makeCosmoshubPath, Secp256k1Wallet } from "@cosmjs/sdk38"; import { BalanceResponse, @@ -40,26 +40,19 @@ describe("Staking demo", () => { it("works", async () => { pendingWithoutWasmd(); // The owner of the contract that will collect the tax - const ownerPen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const ownerAddress = ownerPen.address("cosmos"); - const ownerClient = new SigningCosmWasmClient(httpUrl, ownerAddress, (signBytes) => - ownerPen.sign(signBytes), - ); + const ownerWallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const [{ address: ownerAddress }] = await ownerWallet.getAccounts(); + const ownerClient = new SigningCosmWasmClient(httpUrl, ownerAddress, ownerWallet); // a user of the contract - const userPen = await Secp256k1Pen.fromMnemonic(alice.mnemonic, makeCosmoshubPath(1)); - const userAddress = userPen.address("cosmos"); - const userClient = new SigningCosmWasmClient( - httpUrl, - userAddress, - (signBytes) => userPen.sign(signBytes), - { - exec: { - amount: coins(5000, "ucosm"), - gas: "300000", // 300k, needed for unbonding - }, + const userWallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic, makeCosmoshubPath(1)); + const [{ address: userAddress }] = await userWallet.getAccounts(); + const userClient = new SigningCosmWasmClient(httpUrl, userAddress, userWallet, { + exec: { + amount: coins(5000, "ucosm"), + gas: "300000", // 300k, needed for unbonding }, - ); + }); const initMsg: InitMsg = { name: params.name, diff --git a/packages/faucet/src/actions/generate.ts b/packages/faucet/src/actions/generate.ts index 73ed15c1..ad876dae 100644 --- a/packages/faucet/src/actions/generate.ts +++ b/packages/faucet/src/actions/generate.ts @@ -1,7 +1,7 @@ import { Bip39, Random } from "@cosmjs/crypto"; import * as constants from "../constants"; -import { createPens } from "../profile"; +import { createWallets } from "../profile"; export async function generate(args: readonly string[]): Promise { if (args.length < 1) { @@ -16,5 +16,5 @@ export async function generate(args: readonly string[]): Promise { console.info(`FAUCET_MNEMONIC="${mnemonic}"`); // Log the addresses - await createPens(mnemonic, chainId, constants.concurrency, true); + await createWallets(mnemonic, chainId, constants.concurrency, true); } diff --git a/packages/faucet/src/faucet.ts b/packages/faucet/src/faucet.ts index b6450956..d1b0ee6f 100644 --- a/packages/faucet/src/faucet.ts +++ b/packages/faucet/src/faucet.ts @@ -1,8 +1,8 @@ -import { CosmosClient, Pen, SigningCosmosClient } from "@cosmjs/sdk38"; +import { CosmosClient, OfflineSigner, SigningCosmosClient } from "@cosmjs/sdk38"; import { sleep } from "@cosmjs/utils"; import { debugAccount, logAccountsState, logSendJob } from "./debugging"; -import { createPens } from "./profile"; +import { createWallets } from "./profile"; import { TokenManager } from "./tokenmanager"; import { MinimalAccount, SendJob, TokenConfiguration } from "./types"; @@ -19,8 +19,8 @@ export class Faucet { numberOfDistributors: number, logging = false, ): Promise { - const pens = await createPens(mnemonic, addressPrefix, numberOfDistributors, logging); - return new Faucet(apiUrl, addressPrefix, config, pens, logging); + const wallets = await createWallets(mnemonic, addressPrefix, numberOfDistributors, logging); + return new Faucet(apiUrl, addressPrefix, config, wallets, logging); } public readonly addressPrefix: string; @@ -38,7 +38,7 @@ export class Faucet { apiUrl: string, addressPrefix: string, config: TokenConfiguration, - pens: ReadonlyArray, + wallets: ReadonlyArray, logging = false, ) { this.addressPrefix = addressPrefix; @@ -47,15 +47,13 @@ export class Faucet { this.readOnlyClient = new CosmosClient(apiUrl); - this.holderAddress = pens[0][0]; - this.distributorAddresses = pens.slice(1).map((pair) => pair[0]); + this.holderAddress = wallets[0][0]; + this.distributorAddresses = wallets.slice(1).map((pair) => pair[0]); // we need one client per sender const clients: { [senderAddress: string]: SigningCosmosClient } = {}; - for (const [senderAddress, pen] of pens) { - clients[senderAddress] = new SigningCosmosClient(apiUrl, senderAddress, (signBytes) => - pen.sign(signBytes), - ); + for (const [senderAddress, wallet] of wallets) { + clients[senderAddress] = new SigningCosmosClient(apiUrl, senderAddress, wallet); } this.clients = clients; this.logging = logging; diff --git a/packages/faucet/src/profile.ts b/packages/faucet/src/profile.ts index 0abc05a9..1ef5cce2 100644 --- a/packages/faucet/src/profile.ts +++ b/packages/faucet/src/profile.ts @@ -1,26 +1,26 @@ import { pathToString } from "@cosmjs/crypto"; -import { makeCosmoshubPath, Pen, Secp256k1Pen } from "@cosmjs/sdk38"; +import { makeCosmoshubPath, OfflineSigner, Secp256k1Wallet } from "@cosmjs/sdk38"; -export async function createPens( +export async function createWallets( mnemonic: string, addressPrefix: string, numberOfDistributors: number, logging = false, -): Promise> { - const pens = new Array(); +): Promise> { + const wallets = new Array(); // first account is the token holder const numberOfIdentities = 1 + numberOfDistributors; for (let i = 0; i < numberOfIdentities; i++) { const path = makeCosmoshubPath(i); - const pen = await Secp256k1Pen.fromMnemonic(mnemonic, path); - const address = pen.address(addressPrefix); + const wallet = await Secp256k1Wallet.fromMnemonic(mnemonic, path, addressPrefix); + const [{ address }] = await wallet.getAccounts(); if (logging) { const role = i === 0 ? "token holder " : `distributor ${i}`; console.info(`Created ${role} (${pathToString(path)}): ${address}`); } - pens.push([address, pen]); + wallets.push([address, wallet]); } - return pens; + return wallets; } diff --git a/packages/sdk38/src/cosmosclient.searchtx.spec.ts b/packages/sdk38/src/cosmosclient.searchtx.spec.ts index cb6ca4de..3f244466 100644 --- a/packages/sdk38/src/cosmosclient.searchtx.spec.ts +++ b/packages/sdk38/src/cosmosclient.searchtx.spec.ts @@ -6,7 +6,6 @@ import { CosmosClient, isPostTxFailure } from "./cosmosclient"; import { makeSignBytes } from "./encoding"; import { LcdClient } from "./lcdapi"; import { isMsgSend, MsgSend } from "./msgs"; -import { Secp256k1Pen } from "./pen"; import { SigningCosmosClient } from "./signingcosmosclient"; import { faucet, @@ -17,6 +16,7 @@ import { wasmdEnabled, } from "./testutils.spec"; import { CosmosSdkTx } from "./types"; +import { Secp256k1Wallet } from "./wallet"; interface TestTxSend { readonly sender: string; @@ -32,10 +32,10 @@ describe("CosmosClient.searchTx", () => { beforeAll(async () => { if (wasmdEnabled()) { - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, (signBytes) => - pen.sign(signBytes), - ); + const wallet = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic); + const accounts = await wallet.getAccounts(); + const [{ address: walletAddress }] = accounts; + const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, wallet); { const memo = "Sending more than I can afford"; @@ -58,7 +58,7 @@ describe("CosmosClient.searchTx", () => { const { accountNumber, sequence } = await client.getNonce(); const chainId = await client.getChainId(); const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await pen.sign(signBytes); + const signature = await wallet.sign(walletAddress, signBytes); const tx: CosmosSdkTx = { type: "cosmos-sdk/StdTx", value: { diff --git a/packages/sdk38/src/cosmosclient.spec.ts b/packages/sdk38/src/cosmosclient.spec.ts index 64d9806d..29e2d991 100644 --- a/packages/sdk38/src/cosmosclient.spec.ts +++ b/packages/sdk38/src/cosmosclient.spec.ts @@ -6,7 +6,6 @@ import { CosmosClient, isPostTxFailure, PrivateCosmWasmClient } from "./cosmoscl import { makeSignBytes } from "./encoding"; import { findAttribute } from "./logs"; import { MsgSend } from "./msgs"; -import { Secp256k1Pen } from "./pen"; import cosmoshub from "./testdata/cosmoshub.json"; import { faucet, @@ -17,6 +16,7 @@ import { wasmd, } from "./testutils.spec"; import { StdFee } from "./types"; +import { Secp256k1Wallet } from "./wallet"; const blockTime = 1_000; // ms @@ -193,7 +193,9 @@ describe("CosmosClient", () => { describe("postTx", () => { it("works", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const wallet = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic); + const accounts = await wallet.getAccounts(); + const [{ address: walletAddress }] = accounts; const client = new CosmosClient(wasmd.endpoint); const memo = "My first contract on chain"; @@ -224,7 +226,7 @@ describe("CosmosClient", () => { const chainId = await client.getChainId(); const { accountNumber, sequence } = await client.getNonce(faucet.address); const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await pen.sign(signBytes); + const signature = await wallet.sign(walletAddress, signBytes); const signedTx = { msg: [sendMsg], fee: fee, diff --git a/packages/sdk38/src/index.ts b/packages/sdk38/src/index.ts index f7d21dc6..4233a729 100644 --- a/packages/sdk38/src/index.ts +++ b/packages/sdk38/src/index.ts @@ -50,9 +50,9 @@ export { TxsResponse, } from "./lcdapi"; export { isMsgDelegate, isMsgSend, Msg, MsgDelegate, MsgSend } from "./msgs"; -export { Pen, Secp256k1Pen, makeCosmoshubPath } from "./pen"; export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; export { findSequenceForSignedTx } from "./sequence"; export { encodeSecp256k1Signature, decodeSignature } from "./signature"; -export { FeeTable, SigningCallback, SigningCosmosClient } from "./signingcosmosclient"; +export { FeeTable, SigningCosmosClient } from "./signingcosmosclient"; export { isStdTx, pubkeyType, CosmosSdkTx, PubKey, StdFee, StdSignature, StdTx } from "./types"; +export { OfflineSigner, Secp256k1Wallet, makeCosmoshubPath } from "./wallet"; diff --git a/packages/sdk38/src/lcdapi/lcdclient.spec.ts b/packages/sdk38/src/lcdapi/lcdclient.spec.ts index 627075e1..b928af0c 100644 --- a/packages/sdk38/src/lcdapi/lcdclient.spec.ts +++ b/packages/sdk38/src/lcdapi/lcdclient.spec.ts @@ -1,13 +1,11 @@ /* eslint-disable @typescript-eslint/camelcase */ import { assert, sleep } from "@cosmjs/utils"; -import { rawSecp256k1PubkeyToAddress } from "../address"; import { Coin } from "../coins"; import { isPostTxFailure } from "../cosmosclient"; import { makeSignBytes } from "../encoding"; import { parseLogs } from "../logs"; import { MsgSend } from "../msgs"; -import { makeCosmoshubPath, Secp256k1Pen } from "../pen"; import { SigningCosmosClient } from "../signingcosmosclient"; import cosmoshub from "../testdata/cosmoshub.json"; import { @@ -21,6 +19,7 @@ import { wasmdEnabled, } from "../testutils.spec"; import { StdFee } from "../types"; +import { makeCosmoshubPath, Secp256k1Wallet } from "../wallet"; import { setupAuthExtension } from "./auth"; import { TxsResponse } from "./base"; import { LcdApiArray, LcdClient, normalizeLcdApiArray } from "./lcdclient"; @@ -217,10 +216,10 @@ describe("LcdClient", () => { beforeAll(async () => { if (wasmdEnabled()) { - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, (signBytes) => - pen.sign(signBytes), - ); + const wallet = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic); + const accounts = await wallet.getAccounts(); + const [{ address: walletAddress }] = accounts; + const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, wallet); { const recipient = makeRandomAddress(); @@ -267,7 +266,7 @@ describe("LcdClient", () => { const { accountNumber, sequence } = await client.getNonce(); const chainId = await client.getChainId(); const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await pen.sign(signBytes); + const signature = await wallet.sign(walletAddress, signBytes); const signedTx = { msg: [sendMsg], fee: fee, @@ -351,10 +350,8 @@ describe("LcdClient", () => { beforeAll(async () => { if (wasmdEnabled()) { - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, (signBytes) => - pen.sign(signBytes), - ); + const wallet = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, wallet); const recipient = makeRandomAddress(); const transferAmount = [ @@ -534,7 +531,9 @@ describe("LcdClient", () => { describe("postTx", () => { it("can send tokens", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const wallet = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic); + const accounts = await wallet.getAccounts(); + const [{ address: walletAddress }] = accounts; const memo = "My first contract on chain"; const theMsg: MsgSend = { @@ -565,7 +564,7 @@ describe("LcdClient", () => { const { account_number, sequence } = (await client.auth.account(faucet.address)).result.value; const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); - const signature = await pen.sign(signBytes); + const signature = await wallet.sign(walletAddress, signBytes); const signedTx = makeSignedTx(theMsg, fee, memo, signature); const result = await client.postTx(signedTx); expect(result.code).toBeUndefined(); @@ -582,12 +581,14 @@ describe("LcdClient", () => { it("can't send transaction with additional signatures", async () => { pendingWithoutWasmd(); - const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); - const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); - const account3 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(2)); - const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); - const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); - const address3 = rawSecp256k1PubkeyToAddress(account3.pubkey, "cosmos"); + const account1 = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const account2 = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const account3 = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(2)); + const [address1, address2, address3] = await Promise.all( + [account1, account2, account3].map(async (wallet) => { + return (await wallet.getAccounts())[0].address; + }), + ); const memo = "My first contract on chain"; const theMsg: MsgSend = { @@ -622,9 +623,9 @@ describe("LcdClient", () => { const signBytes1 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an1, sequence1); const signBytes2 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an2, sequence2); const signBytes3 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an3, sequence3); - const signature1 = await account1.sign(signBytes1); - const signature2 = await account2.sign(signBytes2); - const signature3 = await account3.sign(signBytes3); + const signature1 = await account1.sign(address1, signBytes1); + const signature2 = await account2.sign(address2, signBytes2); + const signature3 = await account3.sign(address3, signBytes3); const signedTx = { msg: [theMsg], fee: fee, @@ -638,14 +639,15 @@ describe("LcdClient", () => { it("can send multiple messages with one signature", async () => { pendingWithoutWasmd(); - const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); - const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); + const wallet = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const accounts = await wallet.getAccounts(); + const [{ address: walletAddress }] = accounts; const memo = "My first contract on chain"; const msg1: MsgSend = { type: "cosmos-sdk/MsgSend", value: { - from_address: address1, + from_address: walletAddress, to_address: defaultRecipientAddress, amount: [ { @@ -658,7 +660,7 @@ describe("LcdClient", () => { const msg2: MsgSend = { type: "cosmos-sdk/MsgSend", value: { - from_address: address1, + from_address: walletAddress, to_address: defaultRecipientAddress, amount: [ { @@ -680,10 +682,10 @@ describe("LcdClient", () => { }; const client = LcdClient.withExtensions({ apiUrl: wasmd.endpoint }, setupAuthExtension); - const { account_number, sequence } = (await client.auth.account(address1)).result.value; + const { account_number, sequence } = (await client.auth.account(walletAddress)).result.value; const signBytes = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, account_number, sequence); - const signature1 = await account1.sign(signBytes); + const signature1 = await wallet.sign(walletAddress, signBytes); const signedTx = { msg: [msg1, msg2], fee: fee, @@ -696,10 +698,13 @@ describe("LcdClient", () => { it("can send multiple messages with multiple signatures", async () => { pendingWithoutWasmd(); - const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); - const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); - const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); - const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); + const account1 = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const account2 = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const [address1, address2] = await Promise.all( + [account1, account2].map(async (wallet) => { + return (await wallet.getAccounts())[0].address; + }), + ); const memo = "My first contract on chain"; const msg1: MsgSend = { @@ -745,8 +750,8 @@ describe("LcdClient", () => { const signBytes1 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1); const signBytes2 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2); - const signature1 = await account1.sign(signBytes1); - const signature2 = await account2.sign(signBytes2); + const signature1 = await account1.sign(address1, signBytes1); + const signature2 = await account2.sign(address2, signBytes2); const signedTx = { msg: [msg2, msg1], fee: fee, @@ -764,10 +769,13 @@ describe("LcdClient", () => { it("can't send transaction with wrong signature order (1)", async () => { pendingWithoutWasmd(); - const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); - const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); - const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); - const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); + const account1 = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const account2 = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const [address1, address2] = await Promise.all( + [account1, account2].map(async (wallet) => { + return (await wallet.getAccounts())[0].address; + }), + ); const memo = "My first contract on chain"; const msg1: MsgSend = { @@ -813,8 +821,8 @@ describe("LcdClient", () => { const signBytes1 = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, an1, sequence1); const signBytes2 = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, an2, sequence2); - const signature1 = await account1.sign(signBytes1); - const signature2 = await account2.sign(signBytes2); + const signature1 = await account1.sign(address1, signBytes1); + const signature2 = await account2.sign(address2, signBytes2); const signedTx = { msg: [msg1, msg2], fee: fee, @@ -827,10 +835,13 @@ describe("LcdClient", () => { it("can't send transaction with wrong signature order (2)", async () => { pendingWithoutWasmd(); - const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); - const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); - const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos"); - const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos"); + const account1 = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const account2 = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const [address1, address2] = await Promise.all( + [account1, account2].map(async (wallet) => { + return (await wallet.getAccounts())[0].address; + }), + ); const memo = "My first contract on chain"; const msg1: MsgSend = { @@ -876,8 +887,8 @@ describe("LcdClient", () => { const signBytes1 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1); const signBytes2 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2); - const signature1 = await account1.sign(signBytes1); - const signature2 = await account2.sign(signBytes2); + const signature1 = await account1.sign(address1, signBytes1); + const signature2 = await account2.sign(address2, signBytes2); const signedTx = { msg: [msg2, msg1], fee: fee, diff --git a/packages/sdk38/src/pen.spec.ts b/packages/sdk38/src/pen.spec.ts deleted file mode 100644 index 89b37d1b..00000000 --- a/packages/sdk38/src/pen.spec.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto"; -import { fromHex, toAscii } from "@cosmjs/encoding"; - -import { Secp256k1Pen } from "./pen"; -import { decodeSignature } from "./signature"; - -describe("Sec256k1Pen", () => { - it("can be constructed", async () => { - const pen = await Secp256k1Pen.fromMnemonic( - "zebra slush diet army arrest purpose hawk source west glimpse custom record", - ); - expect(pen).toBeTruthy(); - }); - - describe("pubkey", () => { - it("returns compressed pubkey", async () => { - // special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling - // m/44'/118'/0'/0/0 - // pubkey: 02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6 - const pen = await Secp256k1Pen.fromMnemonic( - "special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling", - ); - expect(pen.pubkey).toEqual( - fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"), - ); - }); - }); - - describe("sign", () => { - it("creates correct signatures", async () => { - const pen = await Secp256k1Pen.fromMnemonic( - "special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling", - ); - const data = toAscii("foo bar"); - const { pubkey, signature } = decodeSignature(await pen.sign(data)); - - const valid = await Secp256k1.verifySignature( - Secp256k1Signature.fromFixedLength(signature), - new Sha256(data).digest(), - pubkey, - ); - expect(valid).toEqual(true); - }); - }); - - describe("address", () => { - it("creates same address as Go imlementation", async () => { - const pen = await Secp256k1Pen.fromMnemonic( - "oyster design unusual machine spread century engine gravity focus cave carry slot", - ); - expect(pen.address("cosmos")).toEqual("cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u"); - }); - }); -}); diff --git a/packages/sdk38/src/pen.ts b/packages/sdk38/src/pen.ts deleted file mode 100644 index 1c65a0da..00000000 --- a/packages/sdk38/src/pen.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { - Bip39, - EnglishMnemonic, - Secp256k1, - Sha256, - Sha512, - Slip10, - Slip10Curve, - Slip10RawIndex, -} from "@cosmjs/crypto"; - -import { rawSecp256k1PubkeyToAddress } from "./address"; -import { encodeSecp256k1Signature } from "./signature"; -import { StdSignature } from "./types"; - -export type PrehashType = "sha256" | "sha512" | null; - -/** - * A pen is the most basic tool you can think of for signing. It works - * everywhere and can be used intuitively by everyone. However, it does not - * come with a great amount of features. End of semi suitable metaphor. - * - * This wraps a single keypair and allows for signing. - * - * Non-goals of this types are: multi account support, persistency, data migrations, - * obfuscation of sensitive data. - */ -export interface Pen { - readonly pubkey: Uint8Array; - readonly sign: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise; -} - -function prehash(bytes: Uint8Array, type: PrehashType): Uint8Array { - switch (type) { - case null: - return new Uint8Array([...bytes]); - case "sha256": - return new Sha256(bytes).digest(); - case "sha512": - return new Sha512(bytes).digest(); - default: - throw new Error("Unknown prehash type"); - } -} - -/** - * The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a` - * with 0-based account index `a`. - */ -export function makeCosmoshubPath(a: number): readonly Slip10RawIndex[] { - return [ - Slip10RawIndex.hardened(44), - Slip10RawIndex.hardened(118), - Slip10RawIndex.hardened(0), - Slip10RawIndex.normal(0), - Slip10RawIndex.normal(a), - ]; -} - -export class Secp256k1Pen implements Pen { - public static async fromMnemonic( - mnemonic: string, - hdPath: readonly Slip10RawIndex[] = makeCosmoshubPath(0), - ): Promise { - const seed = await Bip39.mnemonicToSeed(new EnglishMnemonic(mnemonic)); - const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath); - const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; - return new Secp256k1Pen(privkey, Secp256k1.compressPubkey(uncompressed)); - } - - public readonly pubkey: Uint8Array; - private readonly privkey: Uint8Array; - - private constructor(privkey: Uint8Array, pubkey: Uint8Array) { - this.privkey = privkey; - this.pubkey = pubkey; - } - - /** - * Creates and returns a signature - */ - public async sign(signBytes: Uint8Array, prehashType: PrehashType = "sha256"): Promise { - const message = prehash(signBytes, prehashType); - const signature = await Secp256k1.createSignature(message, this.privkey); - const fixedLengthSignature = new Uint8Array([...signature.r(32), ...signature.s(32)]); - return encodeSecp256k1Signature(this.pubkey, fixedLengthSignature); - } - - public address(prefix: string): string { - return rawSecp256k1PubkeyToAddress(this.pubkey, prefix); - } -} diff --git a/packages/sdk38/src/signingcosmosclient.spec.ts b/packages/sdk38/src/signingcosmosclient.spec.ts index ece3473c..ac5c6e5f 100644 --- a/packages/sdk38/src/signingcosmosclient.spec.ts +++ b/packages/sdk38/src/signingcosmosclient.spec.ts @@ -2,9 +2,9 @@ import { assert } from "@cosmjs/utils"; import { Coin } from "./coins"; import { isPostTxFailure, PrivateCosmWasmClient } from "./cosmosclient"; -import { Secp256k1Pen } from "./pen"; import { SigningCosmosClient } from "./signingcosmosclient"; import { makeRandomAddress, pendingWithoutWasmd } from "./testutils.spec"; +import { Secp256k1Wallet } from "./wallet"; const httpUrl = "http://localhost:1317"; @@ -21,8 +21,8 @@ const faucet = { describe("SigningCosmosClient", () => { describe("makeReadOnly", () => { it("can be constructed", async () => { - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmosClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmosClient(httpUrl, faucet.address, wallet); expect(client).toBeTruthy(); }); }); @@ -30,8 +30,8 @@ describe("SigningCosmosClient", () => { describe("getHeight", () => { it("always uses authAccount implementation", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmosClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmosClient(httpUrl, faucet.address, wallet); const openedClient = (client as unknown) as PrivateCosmWasmClient; const blockLatestSpy = spyOn(openedClient.lcdClient, "blocksLatest").and.callThrough(); @@ -48,8 +48,8 @@ describe("SigningCosmosClient", () => { describe("sendTokens", () => { it("works", async () => { pendingWithoutWasmd(); - const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); - const client = new SigningCosmosClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmosClient(httpUrl, faucet.address, wallet); // instantiate const transferAmount: readonly Coin[] = [ diff --git a/packages/sdk38/src/signingcosmosclient.ts b/packages/sdk38/src/signingcosmosclient.ts index 74f23fef..63d7f89a 100644 --- a/packages/sdk38/src/signingcosmosclient.ts +++ b/packages/sdk38/src/signingcosmosclient.ts @@ -3,11 +3,8 @@ import { Account, CosmosClient, GetNonceResult, PostTxResult } from "./cosmoscli import { makeSignBytes } from "./encoding"; import { BroadcastMode } from "./lcdapi"; import { MsgSend } from "./msgs"; -import { StdFee, StdSignature, StdTx } from "./types"; - -export interface SigningCallback { - (signBytes: Uint8Array): Promise; -} +import { StdFee, StdTx } from "./types"; +import { OfflineSigner } from "./wallet"; export interface FeeTable { readonly upload: StdFee; @@ -38,7 +35,7 @@ const defaultFees: FeeTable = { export class SigningCosmosClient extends CosmosClient { public readonly senderAddress: string; - private readonly signCallback: SigningCallback; + private readonly signer: OfflineSigner; private readonly fees: FeeTable; /** @@ -49,14 +46,14 @@ export class SigningCosmosClient extends CosmosClient { * * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) * @param senderAddress The address that will sign and send transactions using this instance - * @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction. + * @param signer An implementation of OfflineSigner which can provide signatures for transactions, potentially requiring user input. * @param customFees The fees that are paid for transactions * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns */ public constructor( apiUrl: string, senderAddress: string, - signCallback: SigningCallback, + signer: OfflineSigner, customFees?: Partial, broadcastMode = BroadcastMode.Block, ) { @@ -64,7 +61,7 @@ export class SigningCosmosClient extends CosmosClient { this.anyValidAddress = senderAddress; this.senderAddress = senderAddress; - this.signCallback = signCallback; + this.signer = signer; this.fees = { ...defaultFees, ...(customFees || {}) }; } @@ -95,7 +92,7 @@ export class SigningCosmosClient extends CosmosClient { const { accountNumber, sequence } = await this.getNonce(); const chainId = await this.getChainId(); const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); - const signature = await this.signCallback(signBytes); + const signature = await this.signer.sign(this.senderAddress, signBytes); const signedTx: StdTx = { msg: [sendMsg], fee: fee, diff --git a/packages/sdk38/src/wallet.spec.ts b/packages/sdk38/src/wallet.spec.ts new file mode 100644 index 00000000..c016b1a8 --- /dev/null +++ b/packages/sdk38/src/wallet.spec.ts @@ -0,0 +1,52 @@ +import { Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto"; +import { fromBase64, fromHex, toAscii } from "@cosmjs/encoding"; + +import { Secp256k1Wallet } from "./wallet"; + +describe("Secp256k1Wallet", () => { + // m/44'/118'/0'/0/0 + // pubkey: 02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6 + const defaultMnemonic = "special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling"; + const defaultPubkey = fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"); + const defaultAddress = "cosmos1jhg0e7s6gn44tfc5k37kr04sznyhedtc9rzys5"; + + it("can be constructed", async () => { + const wallet = await Secp256k1Wallet.fromMnemonic(defaultMnemonic); + expect(wallet).toBeTruthy(); + }); + + describe("getAccounts", () => { + it("resolves to a list of accounts if enabled", async () => { + const wallet = await Secp256k1Wallet.fromMnemonic(defaultMnemonic); + const accounts = await wallet.getAccounts(); + expect(accounts.length).toEqual(1); + expect(accounts[0]).toEqual({ + address: defaultAddress, + algo: "secp256k1", + pubkey: defaultPubkey, + }); + }); + + it("creates the same address as Go implementation", async () => { + const wallet = await Secp256k1Wallet.fromMnemonic( + "oyster design unusual machine spread century engine gravity focus cave carry slot", + ); + const [{ address }] = await wallet.getAccounts(); + expect(address).toEqual("cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u"); + }); + }); + + describe("sign", () => { + it("resolves to valid signature if enabled", async () => { + const wallet = await Secp256k1Wallet.fromMnemonic(defaultMnemonic); + const message = toAscii("foo bar"); + const signature = await wallet.sign(defaultAddress, message); + const valid = await Secp256k1.verifySignature( + Secp256k1Signature.fromFixedLength(fromBase64(signature.signature)), + new Sha256(message).digest(), + defaultPubkey, + ); + expect(valid).toEqual(true); + }); + }); +}); diff --git a/packages/sdk38/src/wallet.ts b/packages/sdk38/src/wallet.ts new file mode 100644 index 00000000..c252d9a7 --- /dev/null +++ b/packages/sdk38/src/wallet.ts @@ -0,0 +1,116 @@ +import { + Bip39, + EnglishMnemonic, + Secp256k1, + Sha256, + Sha512, + Slip10, + Slip10Curve, + Slip10RawIndex, +} from "@cosmjs/crypto"; + +import { rawSecp256k1PubkeyToAddress } from "./address"; +import { encodeSecp256k1Signature } from "./signature"; +import { StdSignature } from "./types"; + +export type PrehashType = "sha256" | "sha512" | null; + +export type Algo = "secp256k1" | "ed25519" | "sr25519"; + +export interface AccountData { + // bech32-encoded + readonly address: string; + readonly algo: Algo; + readonly pubkey: Uint8Array; +} + +export interface OfflineSigner { + /** + * Get AccountData array from wallet. Rejects if not enabled. + */ + readonly getAccounts: () => Promise; + + /** + * Request signature from whichever key corresponds to provided bech32-encoded address. Rejects if not enabled. + */ + readonly sign: (address: string, message: Uint8Array, prehashType?: PrehashType) => Promise; +} + +function prehash(bytes: Uint8Array, type: PrehashType): Uint8Array { + switch (type) { + case null: + return new Uint8Array([...bytes]); + case "sha256": + return new Sha256(bytes).digest(); + case "sha512": + return new Sha512(bytes).digest(); + default: + throw new Error("Unknown prehash type"); + } +} + +/** + * The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a` + * with 0-based account index `a`. + */ +export function makeCosmoshubPath(a: number): readonly Slip10RawIndex[] { + return [ + Slip10RawIndex.hardened(44), + Slip10RawIndex.hardened(118), + Slip10RawIndex.hardened(0), + Slip10RawIndex.normal(0), + Slip10RawIndex.normal(a), + ]; +} + +export class Secp256k1Wallet implements OfflineSigner { + public static async fromMnemonic( + mnemonic: string, + hdPath: readonly Slip10RawIndex[] = makeCosmoshubPath(0), + prefix = "cosmos", + ): Promise { + const seed = await Bip39.mnemonicToSeed(new EnglishMnemonic(mnemonic)); + const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath); + const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; + return new Secp256k1Wallet(privkey, Secp256k1.compressPubkey(uncompressed), prefix); + } + + private readonly pubkey: Uint8Array; + private readonly privkey: Uint8Array; + private readonly prefix: string; + private readonly algo: Algo = "secp256k1"; + + private constructor(privkey: Uint8Array, pubkey: Uint8Array, prefix: string) { + this.privkey = privkey; + this.pubkey = pubkey; + this.prefix = prefix; + } + + private get address(): string { + return rawSecp256k1PubkeyToAddress(this.pubkey, this.prefix); + } + + public async getAccounts(): Promise { + return [ + { + address: this.address, + algo: this.algo, + pubkey: this.pubkey, + }, + ]; + } + + public async sign( + address: string, + message: Uint8Array, + prehashType: PrehashType = "sha256", + ): Promise { + if (address !== this.address) { + throw new Error(`Address ${address} not found in wallet`); + } + const hashedMessage = prehash(message, prehashType); + const signature = await Secp256k1.createSignature(hashedMessage, this.privkey); + const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]); + return encodeSecp256k1Signature(this.pubkey, signatureBytes); + } +} diff --git a/packages/sdk38/types/index.d.ts b/packages/sdk38/types/index.d.ts index fe529970..4fd2a02e 100644 --- a/packages/sdk38/types/index.d.ts +++ b/packages/sdk38/types/index.d.ts @@ -48,9 +48,9 @@ export { TxsResponse, } from "./lcdapi"; export { isMsgDelegate, isMsgSend, Msg, MsgDelegate, MsgSend } from "./msgs"; -export { Pen, Secp256k1Pen, makeCosmoshubPath } from "./pen"; export { decodeBech32Pubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; export { findSequenceForSignedTx } from "./sequence"; export { encodeSecp256k1Signature, decodeSignature } from "./signature"; -export { FeeTable, SigningCallback, SigningCosmosClient } from "./signingcosmosclient"; +export { FeeTable, SigningCosmosClient } from "./signingcosmosclient"; export { isStdTx, pubkeyType, CosmosSdkTx, PubKey, StdFee, StdSignature, StdTx } from "./types"; +export { OfflineSigner, Secp256k1Wallet, makeCosmoshubPath } from "./wallet"; diff --git a/packages/sdk38/types/pen.d.ts b/packages/sdk38/types/pen.d.ts deleted file mode 100644 index 39e899f4..00000000 --- a/packages/sdk38/types/pen.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Slip10RawIndex } from "@cosmjs/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 - * everywhere and can be used intuitively by everyone. However, it does not - * come with a great amount of features. End of semi suitable metaphor. - * - * This wraps a single keypair and allows for signing. - * - * Non-goals of this types are: multi account support, persistency, data migrations, - * obfuscation of sensitive data. - */ -export interface Pen { - readonly pubkey: Uint8Array; - readonly sign: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise; -} -/** - * The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a` - * with 0-based account index `a`. - */ -export declare function makeCosmoshubPath(a: number): readonly Slip10RawIndex[]; -export declare class Secp256k1Pen implements Pen { - static fromMnemonic(mnemonic: string, hdPath?: readonly Slip10RawIndex[]): Promise; - readonly pubkey: Uint8Array; - private readonly privkey; - private constructor(); - /** - * Creates and returns a signature - */ - sign(signBytes: Uint8Array, prehashType?: PrehashType): Promise; - address(prefix: string): string; -} diff --git a/packages/sdk38/types/signingcosmosclient.d.ts b/packages/sdk38/types/signingcosmosclient.d.ts index a733b8f6..616b3e25 100644 --- a/packages/sdk38/types/signingcosmosclient.d.ts +++ b/packages/sdk38/types/signingcosmosclient.d.ts @@ -1,10 +1,8 @@ import { Coin } from "./coins"; import { Account, CosmosClient, GetNonceResult, PostTxResult } from "./cosmosclient"; import { BroadcastMode } from "./lcdapi"; -import { StdFee, StdSignature } from "./types"; -export interface SigningCallback { - (signBytes: Uint8Array): Promise; -} +import { StdFee } from "./types"; +import { OfflineSigner } from "./wallet"; export interface FeeTable { readonly upload: StdFee; readonly init: StdFee; @@ -13,7 +11,7 @@ export interface FeeTable { } export declare class SigningCosmosClient extends CosmosClient { readonly senderAddress: string; - private readonly signCallback; + private readonly signer; private readonly fees; /** * Creates a new client with signing capability to interact with a CosmWasm blockchain. This is the bigger brother of CosmWasmClient. @@ -23,14 +21,14 @@ export declare class SigningCosmosClient extends CosmosClient { * * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) * @param senderAddress The address that will sign and send transactions using this instance - * @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction. + * @param signer An implementation of OfflineSigner which can provide signatures for transactions, potentially requiring user input. * @param customFees The fees that are paid for transactions * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns */ constructor( apiUrl: string, senderAddress: string, - signCallback: SigningCallback, + signer: OfflineSigner, customFees?: Partial, broadcastMode?: BroadcastMode, ); diff --git a/packages/sdk38/types/wallet.d.ts b/packages/sdk38/types/wallet.d.ts new file mode 100644 index 00000000..463ce554 --- /dev/null +++ b/packages/sdk38/types/wallet.d.ts @@ -0,0 +1,39 @@ +import { Slip10RawIndex } from "@cosmjs/crypto"; +import { StdSignature } from "./types"; +export declare type PrehashType = "sha256" | "sha512" | null; +export declare type Algo = "secp256k1" | "ed25519" | "sr25519"; +export interface AccountData { + readonly address: string; + readonly algo: Algo; + readonly pubkey: Uint8Array; +} +export interface OfflineSigner { + /** + * Get AccountData array from wallet. Rejects if not enabled. + */ + readonly getAccounts: () => Promise; + /** + * Request signature from whichever key corresponds to provided bech32-encoded address. Rejects if not enabled. + */ + readonly sign: (address: string, message: Uint8Array, prehashType?: PrehashType) => Promise; +} +/** + * The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a` + * with 0-based account index `a`. + */ +export declare function makeCosmoshubPath(a: number): readonly Slip10RawIndex[]; +export declare class Secp256k1Wallet implements OfflineSigner { + static fromMnemonic( + mnemonic: string, + hdPath?: readonly Slip10RawIndex[], + prefix?: string, + ): Promise; + private readonly pubkey; + private readonly privkey; + private readonly prefix; + private readonly algo; + private constructor(); + private get address(); + getAccounts(): Promise; + sign(address: string, message: Uint8Array, prehashType?: PrehashType): Promise; +} diff --git a/scripts/wasmd/deploy_erc20.js b/scripts/wasmd/deploy_erc20.js index 140b5b06..6ce0f5c9 100755 --- a/scripts/wasmd/deploy_erc20.js +++ b/scripts/wasmd/deploy_erc20.js @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/camelcase */ const { SigningCosmWasmClient } = require("@cosmjs/cosmwasm"); -const { Secp256k1Pen } = require("@cosmjs/sdk38"); +const { Secp256k1Wallet } = require("@cosmjs/sdk38"); const fs = require("fs"); const httpUrl = "http://localhost:1317"; @@ -134,8 +134,8 @@ const initDataJade = { }; async function main() { - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); const wasm = fs.readFileSync(__dirname + "/contracts/cw-erc20.wasm"); const uploadReceipt = await client.upload(wasm, codeMeta, "Upload ERC20 contract"); diff --git a/scripts/wasmd/deploy_nameservice.js b/scripts/wasmd/deploy_nameservice.js index 3f7ad0d1..1a20340e 100755 --- a/scripts/wasmd/deploy_nameservice.js +++ b/scripts/wasmd/deploy_nameservice.js @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/camelcase */ const { SigningCosmWasmClient } = require("@cosmjs/cosmwasm"); -const { Secp256k1Pen } = require("@cosmjs/sdk38"); +const { Secp256k1Wallet } = require("@cosmjs/sdk38"); const fs = require("fs"); const httpUrl = "http://localhost:1317"; @@ -36,8 +36,8 @@ const luxury = { }; async function main() { - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes)); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet); const wasm = fs.readFileSync(__dirname + "/contracts/cw-nameservice.wasm"); const uploadReceipt = await client.upload(wasm, codeMeta, "Upload Name Service code"); diff --git a/scripts/wasmd/deploy_staking.js b/scripts/wasmd/deploy_staking.js index 43ae93e5..ce9b32bb 100755 --- a/scripts/wasmd/deploy_staking.js +++ b/scripts/wasmd/deploy_staking.js @@ -2,7 +2,7 @@ /* eslint-disable @typescript-eslint/camelcase */ const { SigningCosmWasmClient } = require("@cosmjs/cosmwasm"); -const { coins, Secp256k1Pen } = require("@cosmjs/sdk38"); +const { coins, Secp256k1Wallet } = require("@cosmjs/sdk38"); const fs = require("fs"); const httpUrl = "http://localhost:1317"; @@ -38,8 +38,8 @@ const fees = { }; async function main() { - const pen = await Secp256k1Pen.fromMnemonic(alice.mnemonic); - const client = new SigningCosmWasmClient(httpUrl, alice.address0, (signBytes) => pen.sign(signBytes), fees); + const wallet = await Secp256k1Wallet.fromMnemonic(alice.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, alice.address0, wallet, fees); const wasm = fs.readFileSync(__dirname + "/contracts/staking.wasm"); const uploadReceipt = await client.upload(wasm, codeMeta, "Upload Staking code");