-
Notifications
You must be signed in to change notification settings - Fork 206
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
Conversation
naming idea: brainstorming with @turadg |
256f5d5
to
a91d1ca
Compare
2189f62
to
eb8f575
Compare
a91d1ca
to
03cd1b2
Compare
eb8f575
to
bd078d3
Compare
bd078d3
to
40b4902
Compare
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
40b4902
to
5aa2863
Compare
- 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
There was a problem hiding this 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}> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok; it's gone
chatting with @michaelfig
he suggests aligning with a browser localStorage norm. Also, maybe @rowgraus should weigh in? should that be a week? |
There was a problem hiding this 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", |
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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} */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noice toipes.
* | ||
* arbitrary; suffix chosen randomly to avoid collisions | ||
*/ | ||
export const STORAGE_KEY = 'agoric.eis0Aigi'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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');
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert
s 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), |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please type
export async function suggestChain( | ||
networkConfig, | ||
/** @type {string} */ caption = undefined, | ||
io = {}, | ||
) { |
There was a problem hiding this comment.
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.
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 = {}) { |
There was a problem hiding this comment.
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 */
.
There was a problem hiding this comment.
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 }) => { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
* For example, to check whether `delegateWalletAction()` is necessary. | ||
* | ||
* @param {string} granter address | ||
* @param {import('@cosmjs/tendermint-rpc').Tendermint34Client} rpcClient |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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.
Co-authored-by: Michael FIG <michael+github@fig.org>
Co-authored-by: Turadg Aleahmad <turadg@agoric.com> Co-authored-by: Michael FIG <michael+github@fig.org>
Co-authored-by: Michael FIG <michael+github@fig.org>
}, | ||
}; | ||
|
||
// ack: justerest Sep 2019 https://stackoverflow.com/a/58110124/7963 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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...
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 usefeegrant
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:authz
grant is limited to low-privilegeMsgWalletAction
messagesDocumentation 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)