Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions src/core/internal/mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,11 @@ export type Mode = {
internal: ActionsInternal
/** Label to associate with the WebAuthn credential. */
label?: string | undefined
/** Permissions to grant. */
permissions?: PermissionsRequest.PermissionsRequest | undefined
/** Permissions to grant. Can be a single request or an array for batching multiple session keys. */
permissions?:
| PermissionsRequest.PermissionsRequest
| readonly PermissionsRequest.PermissionsRequest[]
| undefined
/** Adds support for offchain authentication using ERC-4361. */
signInWithEthereum?: Capabilities.signInWithEthereum.Request | undefined
}) => Promise<{
Expand Down Expand Up @@ -170,8 +173,11 @@ export type Mode = {
| undefined
/** Internal properties. */
internal: ActionsInternal
/** Permissions to grant. */
permissions?: PermissionsRequest.PermissionsRequest | undefined
/** Permissions to grant. Can be a single request or an array for batching multiple session keys. */
permissions?:
| PermissionsRequest.PermissionsRequest
| readonly PermissionsRequest.PermissionsRequest[]
| undefined
/** Adds support for offchain authentication using ERC-4361. */
signInWithEthereum?: Capabilities.signInWithEthereum.Request | undefined
}) => Promise<{
Expand Down Expand Up @@ -231,8 +237,11 @@ export type Mode = {
label?: string | undefined
/** Internal properties. */
internal: ActionsInternal
/** Permissions to grant. */
permissions?: PermissionsRequest.PermissionsRequest | undefined
/** Permissions to grant. Can be a single request or an array for batching multiple session keys. */
permissions?:
| PermissionsRequest.PermissionsRequest
| readonly PermissionsRequest.PermissionsRequest[]
| undefined
}) => Promise<{
/** Digests to sign. */
digests: {
Expand Down
107 changes: 72 additions & 35 deletions src/core/internal/modes/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,21 +159,30 @@ export function dialog(parameters: dialog.Parameters = {}) {
const signInWithEthereum =
request.params?.[0]?.capabilities?.signInWithEthereum

// Parse the authorize key into a structured key.
const key = await PermissionsRequest.toKey(
capabilities?.grantPermissions,
// Parse the authorize key(s) into structured keys.
const grantPermissionsInput = capabilities?.grantPermissions
const permissionsInputArray = Array.isArray(grantPermissionsInput)
? grantPermissionsInput
: grantPermissionsInput
? [grantPermissionsInput]
: []
const generatedKeys = await PermissionsRequest.toKeys(
permissionsInputArray,
{
chainId: client.chain.id,
},
)

// Convert the key into a permission.
const permissionsRequest = key
? z.encode(
PermissionsRequest.Schema,
PermissionsRequest.fromKey(key),
)
: undefined
// Convert the keys back into permission requests (with generated key info).
const permissionsPayload =
generatedKeys.length === 0
? undefined
: generatedKeys.map((k) =>
z.encode(
PermissionsRequest.Schema,
PermissionsRequest.fromKey(k),
),
)

// Send a request off to the dialog to create an account.
const { accounts } = await provider.request({
Expand All @@ -182,7 +191,7 @@ export function dialog(parameters: dialog.Parameters = {}) {
{
capabilities: {
...request.params?.[0]?.capabilities,
grantPermissions: permissionsRequest,
grantPermissions: permissionsPayload,
signInWithEthereum:
authUrl || signInWithEthereum
? {
Expand Down Expand Up @@ -210,10 +219,13 @@ export function dialog(parameters: dialog.Parameters = {}) {
const key_permission = Permissions.toKey(
z.decode(Permissions.Schema, permission),
)
if (key_permission.id === key?.id)
const matchedKey = generatedKeys.find(
(gk) => gk.id === key_permission.id,
)
if (matchedKey)
return {
...key_permission,
...key,
...matchedKey,
permissions: key_permission.permissions,
}
return key_permission
Expand Down Expand Up @@ -488,21 +500,30 @@ export function dialog(parameters: dialog.Parameters = {}) {
const signInWithEthereum =
request.params?.[0]?.capabilities?.signInWithEthereum

// Parse provided (RPC) key into a structured key.
const key = await PermissionsRequest.toKey(
capabilities?.grantPermissions,
// Parse provided (RPC) key(s) into structured keys.
const grantPermissionsInput = capabilities?.grantPermissions
const permissionsInputArray = Array.isArray(grantPermissionsInput)
? grantPermissionsInput
: grantPermissionsInput
? [grantPermissionsInput]
: []
const generatedKeys = await PermissionsRequest.toKeys(
permissionsInputArray,
{
chainId: client.chain.id,
},
)

// Convert the key into a permissions request.
const permissionsRequest = key
? z.encode(
PermissionsRequest.Schema,
PermissionsRequest.fromKey(key),
)
: undefined
// Convert the keys back into permission requests (with generated key info).
const permissionsPayload =
generatedKeys.length === 0
? undefined
: generatedKeys.map((k) =>
z.encode(
PermissionsRequest.Schema,
PermissionsRequest.fromKey(k),
),
)

// Send a request to the dialog.
const { accounts } = await provider.request({
Expand All @@ -512,7 +533,7 @@ export function dialog(parameters: dialog.Parameters = {}) {
...request.params?.[0],
capabilities: {
...request.params?.[0]?.capabilities,
grantPermissions: permissionsRequest,
grantPermissions: permissionsPayload,
signInWithEthereum:
authUrl || signInWithEthereum
? {
Expand All @@ -536,10 +557,13 @@ export function dialog(parameters: dialog.Parameters = {}) {
const key_permission = Permissions.toKey(
z.decode(Permissions.Schema, permission),
)
if (key_permission.id === key?.id)
const matchedKey = generatedKeys.find(
(gk) => gk.id === key_permission.id,
)
if (matchedKey)
return {
...key_permission,
...key,
...matchedKey,
permissions: key_permission.permissions,
}
return key_permission
Expand Down Expand Up @@ -644,18 +668,30 @@ export function dialog(parameters: dialog.Parameters = {}) {
// Extract the capabilities from the request.
const [{ capabilities }] = request._decoded.params ?? [{}]

// Parse the authorize key into a structured key.
const key = await PermissionsRequest.toKey(
capabilities?.grantPermissions,
// Parse the authorize key(s) into structured keys.
const grantPermissionsInput = capabilities?.grantPermissions
const permissionsInputArray = Array.isArray(grantPermissionsInput)
? grantPermissionsInput
: grantPermissionsInput
? [grantPermissionsInput]
: []
const generatedKeys = await PermissionsRequest.toKeys(
permissionsInputArray,
{
chainId: client.chain.id,
},
)

// Convert the key into a permission.
const permissionsRequest = key
? z.encode(PermissionsRequest.Schema, PermissionsRequest.fromKey(key))
: undefined
// Convert the keys back into permission requests (with generated key info).
const permissionsPayload =
generatedKeys.length === 0
? undefined
: generatedKeys.map((k) =>
z.encode(
PermissionsRequest.Schema,
PermissionsRequest.fromKey(k),
),
)

// Send a request off to the dialog to prepare the upgrade.
const provider = getProvider(store)
Expand All @@ -666,15 +702,16 @@ export function dialog(parameters: dialog.Parameters = {}) {
...request.params?.[0],
capabilities: {
...request.params?.[0]?.capabilities,
grantPermissions: permissionsRequest,
grantPermissions: permissionsPayload,
},
},
],
})

type Context = { account: Account.Account }
const keys = (context as Context).account.keys?.map((k) => {
if (k.id === key?.id) return { ...k, ...key }
const matchedKey = generatedKeys.find((gk) => gk.id === k.id)
if (matchedKey) return { ...k, ...matchedKey }
return k
})

Expand Down
79 changes: 46 additions & 33 deletions src/core/internal/modes/relay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@ export function relay(parameters: relay.Parameters = {}) {
userId: Bytes.from(eoa.address),
})
: Key.createHeadlessWebAuthnP256()
const sessionKey = await PermissionsRequest.toKey(permissions, {
const permissionsArray = Array.isArray(permissions)
? permissions
: permissions
? [permissions]
: []
const sessionKeys = await PermissionsRequest.toKeys(permissionsArray, {
chainId: client.chain.id,
feeTokens,
})
Expand All @@ -89,11 +94,7 @@ export function relay(parameters: relay.Parameters = {}) {

const account = await RelayActions.upgradeAccount(client, {
account: eoa,
authorizeKeys: [
adminKey,
...(adminKeys ?? []),
...(sessionKey ? [sessionKey] : []),
],
authorizeKeys: [adminKey, ...(adminKeys ?? []), ...sessionKeys],
})

address_internal = eoa.address
Expand Down Expand Up @@ -325,6 +326,7 @@ export function relay(parameters: relay.Parameters = {}) {
const signature = await Key.sign(adminKey, {
address: null,
payload: digest,
webAuthn,
})
await RelayActions.sendPreparedCalls(client, {
context,
Expand All @@ -341,10 +343,18 @@ export function relay(parameters: relay.Parameters = {}) {

const feeTokens = await Tokens.getTokens(client)

const authorizeKey = await PermissionsRequest.toKey(permissions, {
chainId: client.chain.id,
feeTokens,
})
const permissionsArray = Array.isArray(permissions)
? permissions
: permissions
? [permissions]
: []
const authorizeKeys_ = await PermissionsRequest.toKeys(
permissionsArray,
{
chainId: client.chain.id,
feeTokens,
},
)

// Prepare calls to sign over the session key or SIWE message to authorize.
// TODO: figure out with relay if we can prepare the "precall" here also.
Expand Down Expand Up @@ -417,24 +427,22 @@ export function relay(parameters: relay.Parameters = {}) {
// Instantiate the account based off the extracted address and keys.
const account = Account.from({
address,
keys: [...keys, ...(authorizeKey ? [authorizeKey] : [])].map(
(key, i) => {
// Assume that the first key is the admin WebAuthn key.
if (i === 0) {
if (key.type === 'webauthn-p256')
return Key.fromWebAuthnP256({
...key,
credential: {
id: credentialId!,
publicKey: PublicKey.fromHex(key.publicKey),
},
id: address,
rpId: keystoreHost,
})
}
return key
},
),
keys: [...keys, ...authorizeKeys_].map((key, i) => {
// Assume that the first key is the admin WebAuthn key.
if (i === 0) {
if (key.type === 'webauthn-p256')
return Key.fromWebAuthnP256({
...key,
credential: {
id: credentialId!,
publicKey: PublicKey.fromHex(key.publicKey),
},
id: address,
rpId: keystoreHost,
})
}
return key
}),
})

const adminKey = Account.getKey(account, { role: 'admin' })!
Expand Down Expand Up @@ -464,11 +472,11 @@ export function relay(parameters: relay.Parameters = {}) {
})
})()

// Prepare and send the authorize key pre-call.
if (authorizeKey) {
// Prepare and send the authorize keys pre-call.
if (authorizeKeys_.length > 0) {
const { context, digest } = await RelayActions.prepareCalls(client, {
account,
authorizeKeys: [authorizeKey],
authorizeKeys: authorizeKeys_,
preCalls: true,
})
const signature = await Key.sign(adminKey, {
Expand Down Expand Up @@ -606,7 +614,12 @@ export function relay(parameters: relay.Parameters = {}) {
userId: Bytes.from(address),
})
: Key.createHeadlessWebAuthnP256()
const sessionKey = await PermissionsRequest.toKey(permissions, {
const permissionsArray = Array.isArray(permissions)
? permissions
: permissions
? [permissions]
: []
const sessionKeys = await PermissionsRequest.toKeys(permissionsArray, {
chainId: client.chain.id,
feeTokens: tokens,
})
Expand All @@ -615,7 +628,7 @@ export function relay(parameters: relay.Parameters = {}) {
client,
{
address,
authorizeKeys: [adminKey, ...(sessionKey ? [sessionKey] : [])],
authorizeKeys: [adminKey, ...sessionKeys],
feeToken: feeToken?.address,
},
)
Expand Down
8 changes: 8 additions & 0 deletions src/core/internal/permissionsRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,14 @@ export declare namespace toKey {
}
}

export async function toKeys(
requests: readonly PermissionsRequest[],
options: toKey.Options = {},
): Promise<Key.Key[]> {
const results = await Promise.all(requests.map((r) => toKey(r, options)))
return results.filter((k): k is Key.Key => k !== undefined)
}

function isWebCryptoUnavailable(error: unknown) {
if (!(error instanceof Error)) return false
const message = error.message?.toLowerCase() ?? ''
Expand Down
5 changes: 4 additions & 1 deletion src/core/internal/schema/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ export namespace feeToken {
}

export namespace grantPermissions {
export const Request = Permissions.Request
export const Request = z.union([
Permissions.Request,
z.readonly(z.array(Permissions.Request)),
])
export type Request = z.infer<typeof Request>
}

Expand Down