Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(wallet/ui): interactive, background signing #5877

Merged
merged 30 commits into from
Aug 6, 2022

Conversation

dckc
Copy link
Member

@dckc dckc commented Aug 2, 2022

closes: #4406
refs: #3628; obsoletes #5761

Description

makeInteractiveSigner supports signing offers as in the initial smart wallet swap demo. (#5354)

It also supports authorizing a background signer which we plan to use in other user stories.
In particular, it uses authz. (It should use feegrant too, but see #5825 as well as cosmos/cosmjs#1155 )

Security Considerations

The secret key to the background signer is stored in localStorage in the clear, protected only by the same-origin policy; this risk is moderate, given:

  • the authz grant is limited to low-privilege MsgWalletAction messages
  • the grant is limited to 4hrs
  • the grant is limited to spending 0.25IST

Documentation Considerations

End-user documentation for this feature is in order, not to mention completed UI support.

Testing Considerations

Testing so far is manual. (integration test built using a temporary button removed on request)

@dckc
Copy link
Member Author

dckc commented Aug 3, 2022

naming idea: makeNonspendingKey

brainstorming with @turadg

@dckc dckc changed the base branch from master to cosmic-swingset-proto August 3, 2022 23:42
@kriskowal kriskowal force-pushed the cosmic-swingset-proto branch 2 times, most recently from 256f5d5 to a91d1ca Compare August 4, 2022 04:00
Base automatically changed from cosmic-swingset-proto to master August 4, 2022 05:16
dckc added 18 commits August 4, 2022 00:22
without ledger

 - toward keplr/ledger demo: copy SuggestChain.js
 - sign simple msg/doc with keplr/ledger (WIP)
   - ISSUE: started with cosmjs API integration but
     couldn't get it to work without skipping it
 - chore(demo-signing): figured out offlignSigner.signAmino() API
   no longer skipping all the way to keplr.signAmino()
 - feat(demo-signing): sign WALLET_ACTION with keplr, ledger
 - send signed transaction to chain (WIP)
   - can connect to chain and query for sequence
   - aminoTypes seem to work
   - TODO: generate protobuf codec for MsgWalletAction
 - pass connectWithSigner authority explicitly
 - integrate MsgWalletAction proto stub via registry
 - MsgWalletAction.owner is bytes, not string

provide enough gas

avoid:

Error: Broadcasting transaction failed with code 11 (codespace: sdk). Log: out of gas in location: ReadFlat; gasWanted: 250, gasUsed: 1000: out of gas
    at SigningStargateClient.broadcastTx (stargateclient.ts:413:9)
    at async HTMLButtonElement.<anonymous> (keplr-ledger-demo.js:215:16)
 - workaround: cosmjs lacks support for fee-account in MsgExec
 - misc static types

addressed:

Error: Broadcasting transaction failed with code 2 (codespace: sdk). Log: no concrete type registered for type URL /agoric.swingset.MsgWalletAction against interface *authz.Authorization: tx parse error
    at SigningStargateClient.broadcastTx (stargateclient.ts:413:9)
    at async Object.authorizeMessagingKey (keplr-ledger-demo.js:183:18)

basic fee grant to messaging key

example result:

agoric-sdk/packages/cosmic-swingset$ ~/go/bin/agd query tx E0EDE9B200AB0B9FF33DA288BC9E455C228854D76D5AD12C80EA5138C6706D06
...
timestamp: "2022-07-20T03:07:21Z"
tx:
  '@type': /cosmos.tx.v1beta1.Tx
  auth_info:
    fee:
      amount:
      - amount: "0"
        denom: uist
      gas_limit: "100000"
      granter: ""
      payer: ""
    signer_infos:
    - mode_info:
        single:
          mode: SIGN_MODE_DIRECT
      public_key:
        '@type': /cosmos.crypto.secp256k1.PubKey
        key: AggWzZHxF8d7Quy/N9UUGWwU05pIEJKNFGFPpfx01Ika
      sequence: "4"
  body:
    extension_options: []
    memo: ""
    messages:
    - '@type': /cosmos.authz.v1beta1.MsgGrant
      grant:
        authorization:
          '@type': /cosmos.authz.v1beta1.GenericAuthorization
          msg: /agoric.swingset.MsgWalletAction
        expiration: "2022-07-20T07:07:22Z"
      grantee: agoric12ufmm960ynt0svr8c7gwy9he909z34w73fkue8
      granter: agoric1uh4ynz9h3ea9vc80tl30s4ykmcyqwfl0d7zelw
    - '@type': /cosmos.feegrant.v1beta1.MsgGrantAllowance
      allowance:
        '@type': /cosmos.feegrant.v1beta1.BasicAllowance
        expiration: "2022-07-20T07:07:22Z"
        spend_limit:
        - amount: "250000"
          denom: uist
      grantee: agoric12ufmm960ynt0svr8c7gwy9he909z34w73fkue8
      granter: agoric1uh4ynz9h3ea9vc80tl30s4ykmcyqwfl0d7zelw
    non_critical_extension_options: []
    timeout_height: "0"
  signatures:
  - JTO/ouDtWgTB8fO8ngHceQk+AxIup8tWClaKEFCOqiE1N5gC2TEVN2CiKch2tmE6/Pj2NXJH9DoGFEpz5WBB7A==
txhash: E0EDE9B200AB0B9FF33DA288BC9E455C228854D76D5AD12C80EA5138C6706D06

feat(demo-signing): use ephemeral messaging key to do actions (WIP)

struggling to get it to work.

currently gives:

"failed to execute message; message index: 0: authorization not found: unauthorized"

despite:

$ agd query authz grants agoric1uh4ynz9h3ea9vc80tl30s4ykmcyqwfl0d7zelw agoric1prh40kcgtx4uxd870aye5swmqzdrfu4ggp5pfm
grants:
- authorization:
    '@type': /cosmos.authz.v1beta1.GenericAuthorization
    msg: /agoric.swingset.MsgWalletAction
  expiration: "2022-07-20T08:42:41Z"
grant fails thusly:

aminotypes.ts:38 Uncaught (in promise) Error: The message type '/cosmos.authz.v1beta1.MsgGrant' cannot be signed using the Amino JSON sign mode because this is not supported by chain.
    at AminoTypes.toAmino (aminotypes.ts:38:13)
    at signingstargateclient.ts:351:56
    at Array.map (<anonymous>)
    at SigningStargateClient.signAmino (signingstargateclient.ts:351:27)
    at async SigningStargateClient.signAndBroadcast (signingstargateclient.ts:295:19)
    at async Object.authorizeLocalKey (keyManagement.js:350:18)

and submit offer thusly:

Uncaught (in promise) Error: Broadcasting transaction failed with code 4 (codespace: sdk). Log: signature verification failed; please verify account number (14), sequence (0) and chain-id (agoric): unauthorized
    at SigningStargateClient.broadcastTx (stargateclient.ts:413:9)
    at async Object.submitSpendAction (keyManagement.js:381:18)
 - allow caller to pass authority explicitly
 - include interactiveSigner, backgroundSigner in state.keplrConnection
 - punt window.cosmJS, whatever that was
 - use tryKeplrConnect in ChainConnector
 - move ambient authority to the edges: Provider.jsx
 - clean up SuggestChain
@dckc dckc changed the title feat(wallet/ui): WalletAction, WalletSpendAction signing (WIP) feat(wallet/ui): interactive, background signing Aug 4, 2022
@dckc dckc marked this pull request as ready for review August 4, 2022 22:08
Copy link
Contributor

@samsiegart samsiegart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a grasp of all the stuff in keyManagement.js but the rest looks good to me assuming we're getting keplr to actually pop up with an approval window. Will defer approval to @michaelfig for the rest.

return (
<div className="Requests">
{requests.length ? (
requests.map(Item)
) : (
<div className="Empty">
<button type="button" onClick={signOffer}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's okay to not include this button in the merge, we have the code in other branches to reference

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok; it's gone

@dckc
Copy link
Member Author

dckc commented Aug 5, 2022

chatting with @michaelfig

the grant is limited to 4hrs

he suggests aligning with a browser localStorage norm.

Also, maybe @rowgraus should weigh in? should that be a week?

Copy link
Member

@michaelfig michaelfig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given fixes, this looks good to merge!

@@ -8,6 +8,7 @@
"type": "module",
"devDependencies": {
"@agoric/assert": "^0.4.0",
"@agoric/cosmic-proto": "0.1.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yaay! Thanks @kriskowal!

@@ -2,6 +2,8 @@
import { observeIterator } from '@agoric/notifier';
import { E } from '@endo/far';
import { useEffect, useState, useReducer } from 'react';
import { SigningStargateClient } from '@cosmjs/stargate';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's (later) introduce a powerful module that closes over these dependencies and is imported by Provider.jsx to keep Provider.jsx simpler.

It's fine for now.

@@ -0,0 +1,26 @@
// @ts-check

/** @type {import('@keplr-wallet/types').Currency} */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noice toipes.

packages/wallet/ui/src/util/keyManagement.js Outdated Show resolved Hide resolved
packages/wallet/ui/src/util/keyManagement.js Outdated Show resolved Hide resolved
*
* arbitrary; suffix chosen randomly to avoid collisions
*/
export const STORAGE_KEY = 'agoric.eis0Aigi';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const STORAGE_KEY = 'agoric.eis0Aigi';
export const BROWSER_LOCAL_STORAGE_KEY = 'agoric.eis0Aigi';

const accounts = await wallet.getAccounts();
console.debug('device account(s):', accounts);

const [{ address }] = accounts;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the same ?. expression here as elsewhere and throw a nicer error?

Copy link
Member Author

@dckc dckc Aug 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On further consideration, elsewhere we were dealing with the keplr API, which is doing IO to come up with an address and hence may fail. In this case, we're getting the address from DirectSecp256k1Wallet, which is doing only integer calculations that don't have that sort of failure mode. Any failures would show up at the await wallet.getAccounts() above.

packages/wallet/ui/src/util/keyManagement.js Outdated Show resolved Hide resolved
packages/wallet/ui/src/util/keyManagement.js Outdated Show resolved Hide resolved
packages/wallet/ui/src/util/keyManagement.js Outdated Show resolved Hide resolved
Copy link
Member

@turadg turadg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small changes requested.

/**
* key for use in localStorage
*
* arbitrary; suffix chosen randomly to avoid collisions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get this random string. Wouldn't it be better to be human readable as to what's kept inside? e.g. agoric.wallet.backgroundSignerKey.

It could be versioned to signal breaking changes, but I think if backwards compatibility breaks you'd just want to empty it out and start over.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not terribly well motivated... sort of a vague fear of colliding with... I don't know what.

Giving it some thought, I suppose it's scoped to our origin. So I just need to be sure I don't collide with any other code we write. But again... genpass -1 is kind of a quick-and-dirty way to be 99.99999% sure of that :)

The alternative seems to be some sort of central registry agreed by all (potential) contributors.

Or maybe agoric.wallet.backgroundSignerKey has as effectively as much entropy as genpass -1 and I shouldn't worry about collisions?

Copy link
Member

@turadg turadg Aug 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe agoric.wallet.backgroundSignerKey has as effectively as much entropy as genpass -1 and I shouldn't worry about collisions?

Yes, I believe that to be the case.

@@ -148,11 +154,36 @@ const Provider = ({ children }) => {
);
const [keplrConnection, setKeplrConnection] = useState(null);

/**
* NOTE: relies on ambient window.fetch, window.keplr, Random.getBytes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to document this with code, which will provide feedback when the assumptions are violated. e.g. down below,

assert(fetch, 'Missing window.fetch');
assert(keplr, 'Missing window.keplr');
assert(getBytes, 'Missing Random.getBytes');

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asserts for fetch and keplr make sense, but Missing Random.getBytes would be a static error.
Random.getBytes is still noteworthy because it is ambient authority, contrary to OCap Discipline.

@@ -202,7 +233,9 @@ const Provider = ({ children }) => {
if (
connectionConfig?.smartConnectionMethod === SmartConnectionMethod.KEPLR
) {
tryKeplrConnect();
tryKeplrConnect().catch(reason =>
console.error('tryKeplrConnect failed', reason),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should have an error toast for failures like this. if we don't have a toast component yet, adding one is out of scope but should be scheduled work before launch. cc @samsiegart

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea we have toasts but I think I need to do some work to make the toast service more robust and reusable across the app

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm adding a TODO.


export const AGORIC_COIN_TYPE = 564;
export const COSMOS_COIN_TYPE = 118;

export async function suggestChain(networkConfig, caption = undefined) {
const makeChainInfo = (networkConfig, rpcAddr, chainId, caption) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please type

Comment on lines 42 to 46
export async function suggestChain(
networkConfig,
/** @type {string} */ caption = undefined,
io = {},
) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if caption can be undefined then it's not string. let's type the function signature instead of inline.

Suggested change
export async function suggestChain(
networkConfig,
/** @type {string} */ caption = undefined,
io = {},
) {
/** @type {{
* networkConfig: NetworkConfig
* caption?: string
* io?: Partial<IoPowers>
* }}
*/
export async function suggestChain(networkConfig,, caption, io = {}) {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though second thought, why make io optional? Please make them required so this module does not depend anything ambient. Then you can remove /* global globalThis */.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why indeed. It looks like we addressed the only call site in this regard.

* @param {typeof window.localStorage} io.localStorage
* @param {typeof import('@cosmjs/crypto').Random.getBytes} io.getBytes for key generation
*/
export const makeBackgroundSigner = async ({ localStorage, getBytes }) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getBytes without the Random namespace reads overly generic. please rename to something that conveys that it's generating random bytes. consider makeRandomBytes.

that could mean having to map the name at the callsite but I'd also suggest changing this from an option map to positional parameters. then the caller doesn't have to be concerned with the name in this context.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty fond of named parameters for IO powers and have spread the convention pretty far by now :) IOU docs to institutionalize it, perhaps.

It's also my habit to inherit the names from whatever API they come from, but I'll grant that getBytes is pretty worthless in this context. Changing to io.csprng.

packages/wallet/ui/src/util/keyManagement.js Outdated Show resolved Hide resolved
* For example, to check whether `delegateWalletAction()` is necessary.
*
* @param {string} granter address
* @param {import('@cosmjs/tendermint-rpc').Tendermint34Client} rpcClient
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yay types!

});
const decoded = result.grants.map(
g =>
g.authorization && GenericAuthorization.decode(g.authorization.value),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should decoded contain falsy? if not, filter before returning.

also if it wasn't supposed to, consider defining the return type of queryGrants as that would have highlighted the problem.

@dckc dckc requested a review from turadg August 6, 2022 17:18
@dckc dckc added the automerge:squash Automatically squash merge label Aug 6, 2022
},
};

// ack: justerest Sep 2019 https://stackoverflow.com/a/58110124/7963
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a little ugly but I see it's necessary. might want to reference UNTIL https://github.com/microsoft/TypeScript/issues/16655 so we know when we can remove this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh. JS Arrays seem to have flatMap now... i.e. map and filter in one...

@mergify mergify bot merged commit e7e6529 into master Aug 6, 2022
@mergify mergify bot deleted the dc-wallet-signing-prod branch August 6, 2022 19:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge:squash Automatically squash merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

delegate from interactive signer to background signer for low-privilege actions
4 participants