-
Notifications
You must be signed in to change notification settings - Fork 369
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
KomenciKit #5436
KomenciKit #5436
Conversation
packages/komencikit/README.md
Outdated
|
||
## User Guide | ||
|
||
To start working with Komencikit you need a to pass in a ContractKit instance, the external account |
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.
nit: incomplete sentence
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 drifted off to sleep 😅
return | ||
} | ||
|
||
const events = await attestations.getPastEvents('AttestationsRequested', { |
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.
Can we make these a constant enum defined somewhere?
AttestationsRequested
AttestationIssuerSelected
WalletDeployed
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.
Yeah it's one thing I wanted to look into. Theoretically they should be autogenerated somewhere. I was expecting to see them on the contract wrapper but either that's not expected or there's something weird going on in the solidity->generated types->contract->wrapper. But I'll look into this some more.
packages/komencikit/src/actions.ts
Outdated
|
||
export const startSession = action<ActionTypes.StartSession, StartSessionPayload, StartSessionResp>( | ||
ActionTypes.StartSession, | ||
'POST', |
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.
Can we define this as an enum type if it doesn't exist already?
packages/komencikit/src/kit.ts
Outdated
while (receipt == null && waited < this.options.txRetryTimeoutMs) { | ||
receipt = await this.contractKit.web3.eth.getTransactionReceipt(txHash) | ||
if (receipt == null) { | ||
await sleep(100) |
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 can also make this a property under KomenciOptions: txRetryPollingIntervalMs
packages/komencikit/src/kit.ts
Outdated
}) | ||
|
||
const deployWalletLog = events.find( | ||
(event) => normalizeAddressWith0x(event.returnValues.owner) === this.externalAccount |
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 also verify that the implementationAddress in the event matches the one sent to the API. This prevents a compromised Komenci node from deploying a malicious MTWProxy instance and Valora using it as its own.
packages/komencikit/src/kit.ts
Outdated
const attestations = await this.contractKit.contracts.getAttestations() | ||
|
||
const approveTx = await attestations.approveAttestationFee(attestationsRequested) | ||
const approveTxSig = await wallet.signMetaTransaction(approveTx.txo, nonce) |
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.
nit: it might be better to increment nonce nonce++
here in case we add more txs in the future since we won't need to worry about ordering.
console.debug(`${TAG}/requestAttestations attempt#${attempt} error: `, error) | ||
}, | ||
}) | ||
public async requestAttestations( |
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 can add the DEK registration to this API as well.
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.
+1 to DEK registration but think that's now accounted for in the setAccount
wrapper. I like that approach because it will allow Valora control over when to submit the transaction
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.
Nice work! Left a couple of comments and questions.
One thing that may make sense to think a little about now is analytics. We've got the Valora verification flow pretty well measured at this point and Komenci will be a relative black box. I'm not sure how much tracking makes sense for the first iteration but just wanted to put it on your radar as a means to collect data for debugging and performance measurement.
import { Address } from '@celo/contractkit' | ||
import { EIP712TypedData } from '@celo/utils/lib/sign-typed-data-utils' | ||
|
||
export const buildLoginTypedData = ( |
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.
@gnardini is looking into incorporating DeviceCheck and SafetyNet. At least SafetyNet outputs a verifiable token but not sure about DeviceCheck yet. Do we want to accept these as inputs if they are available for the first iteration of Komenci?
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.
Yeah we can add them, not necessarily to the login struct because I think having the captcha token is enough for replay protection. But currently I stripped these from the client under the impression that we'll stick only with captcha. If we feel confident in adding those I can do that but I'd merge this as is in the mean time to have a starting point for the mobile work.
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.
Sounds good. Feel free to merge as is and we can always add those in once we have more clarity.
): Promise<Result<true, WalletValidationError>> => { | ||
const code = await contractKit.web3.eth.getCode(walletAddress) | ||
// XXX: I'm unsure whether this is safe or if we should store the | ||
// bytecode as a constant in `mobile` and pass it into KomenciKit |
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.
Based on my convos with @asaj, I think we landed on needing a constant bytecode to be available for comparison to ensure the deployed wallet meets expectations. I can't really speak to what other guardrails might be in place though.
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.
Yeah I guess that was more about how to track the implementation's bytecode itself. We were between using bytecode digests or the actual address of the contract. I was inclining towards bytecode digest because I was under the wrong impression that it would be constant across networks and be easier to manage on Valora - it wouldn't need a different mapping per network. But realistically because we're using library linking it's gonna have different bytecodes therefore we're ok with Valora referring to a MTW implementation by the address, and Komenci verifying server side that address maps to a known implementation.
Here I'm actually verifying the Proxy contract bytecode to ensure that the Proxy itself is what we expect. I suspect we won't need to change the Proxy anytime soon so I think it's safe to extract that from the compilation output. But I'll ping the CAP team to figure out if there are holes in this approach.
*/ | ||
getDistributedBlindedPepper = async ( | ||
e164Number: string, | ||
clientVersion: string |
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.
What's clientVersion
and how will Valora know it?
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.
@codyborn, keep me honest but AFAIK client version is the actual version of Valora, and it's something that Odis requires for tracking I guess. It's currently passed in as DeviceInfo.getVersion()
in identity/privateHashing.ts
.
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.
That's right. It's purely for monitoring and lets us know on the ODIS side how many users are still using a given version. This can be helpful when deprecating old code paths.
* pointing it to the implementation passed as an argument | ||
* The function takes care of waiting for retrying, waiting for receipt and log parsing | ||
* | ||
* @param implementationAddress the implementation address Valora requires |
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.
Nit: I think the description would be more clear if you referenced the fact that this will be Valora's EOA address
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.
But it's not - but that's also more to your point 😅
This is the address of the MetaTransactionWallet implementation that Valora will have saved as a constant.
Komenci (api) will verify that the implementation is a known implementation that we deployed, and will then deploy a Proxy and set the implementation of that proxy to this address.
Valora then needs to verify:
- That it's MTW is actually a Proxy (by verifying the bytecode)
- That it's pointing to the chosen implementation
- That Valora's EOA is the signer
- That the MTW is its own Owner
(these are all handled in KomenciKit)
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.
Ah gotcha that makes sense
console.debug(`${TAG}/requestAttestations attempt#${attempt} error: `, error) | ||
}, | ||
}) | ||
public async requestAttestations( |
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.
+1 to DEK registration but think that's now accounted for in the setAccount
wrapper. I like that approach because it will allow Valora control over when to submit the transaction
identifier: string, | ||
walletAddress: string, | ||
attestationsRequested: number | ||
): Promise<Result<TransactionReceipt, FetchError | TxError>> { |
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 may be worth adding a separate error type to symbolize the "attestation request limit reached" error, which I believe we are initially setting to 10. Perhaps you can add as an error type within KomenciErrorTypes
?
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.
Yeah I didn't add that in because I wanted to do it in a separate PR when I implement it on the server side as well.
packages/komencikit/src/kit.ts
Outdated
*/ | ||
_wallet?: MetaTransactionWalletWrapper | ||
private async getWallet(address: string): Promise<MetaTransactionWalletWrapper> { | ||
if (this._wallet === undefined || this._wallet.address !== address) { |
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.
Nit: Consider using optional chaining
packages/komencikit/src/kit.ts
Outdated
const wallet = await this.getWallet(walletAddress) | ||
let nonce = await wallet.nonce() | ||
|
||
const approveTx = await attestations.approveAttestationFee(attestationsRequested) |
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.
Seems like there's a check you can do beforehand in the event that attestations have been requested but not revealed and save yourself from submitting these transactions. Example of how we are using it in the Valora flow: https://github.com/celo-org/celo-monorepo/blob/master/packages/mobile/src/identity/verification.ts#L481
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 this depends on how we think about KomenciKit. I would leave this logic in Valora and only think of the requestAttestations
function here as a drop-in replacement for the transaction that Valora was sending before. When and if to request additional attestations should be up to Valora.
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.
Makes sense, I agree
const accounts = await this.contractKit.contracts.getAccounts() | ||
return this.submitMetaTransaction( | ||
walletAddress, | ||
accounts.setAccount(name, dataEncryptionKey, walletAddress, proofOfPossession).txo |
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.
Don't you need to await
this method call like you're doing elsewhere?
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 depends on the implementation in the wrapper - these calls result in a CeloTransactionObject
but some are async, some are not.
The KomenciKit library is a wrapper for the Komenci Service API, which is used for fee-less onboarding by allowing | ||
the consumer to execute fee-less attestations with the help of MetaTransactions. | ||
|
||
The main actions that KomenciKit exposes are: |
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.
Do we need a wrapper for escrow.withdraw
or are you thinking to just submit through the generic function? This that's the only onboarding on-chain transaction not represented in this list.
And in general, must a transaction have a wrapper in KomenciKit in order to be subsidized?
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 transaction doesn't need to have a wrapper. I was actually inclined to remove some of the wrappers and rely on the submitMetaTransaction
action that takes in an arbitrary MetaTransaction and pushes it.
That will only work, however with transaction that we allow on the API side by filtering on contract address and methodID.
The only transaction that we actually must wrap is the Attestations.request
logic because there we're doing the actual subsidy batch and not just executing a simple meta transaction.
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.
LGTM
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.
Small request regarding terminology and a needed change to the setAccount
method.
const accounts = await this.contractKit.contracts.getAccounts() | ||
return this.submitMetaTransaction( | ||
walletAddress, | ||
accounts.setAccount(name, dataEncryptionKey, walletAddress, proofOfPossession).txo |
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.
The walletAddress
expected by the accounts.setAccount
method is actually the EOA not the MTW.
In general, the use of walletAddress
in KomenciKit to refer to the MTW is confusing because in a broader context we are trying to move towards a world where accountAddress === MTW proxy address
and walletAddress === EOA address
. I understand if you want to keep the terminology as is for the rest of Komenci, but I'd suggest changing it in KomenciKit as it will be confusing for future Valora devs
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.
Changes look good. Thanks!
Left one small optional request.
@@ -193,11 +210,11 @@ export class KomenciKit { | |||
}) | |||
public async requestAttestations( | |||
identifier: string, | |||
walletAddress: string, | |||
metaTxWalletAddress: string, |
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.
Nit: this is the only transaction wrapper that has identifier
as the first argument and the address as the second. Can we swap these to avoid mistakes when calling?
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.
Yeah I actually thought of this as well I'll make all the function take the metaTxWalletAddress as the first argument.
Description
KomenciKit is a wrapper for the Komenci service.
It will be consumed by Valora and used during fee-less onboarding.
Other changes
I've extended the
BaseWrapper
with some convenience attributed:eventTypes
This will essentially act as an
enum
of events defined in the contract.This is useful because we're using the
getPastEvents
which wasn't strictly typed, it used a string as argument.And we were using it with strings all over the place which is error prone.
The extension allows the compiler to verify that the event passed in exists on the contract.
methodIds
This is a mapping between method name and method ID. It's used in Komenci to have a more ergonomic way to define meta transaction filters.