Skip to content

Commit b5b56fc

Browse files
committed
Separate prepare and complete for implicit session authorization
1 parent 73a17c1 commit b5b56fc

File tree

6 files changed

+100
-52
lines changed

6 files changed

+100
-52
lines changed

packages/wallet/primitives/src/payload.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex, TypedData } from 'ox'
1+
import { AbiFunction, AbiParameters, Address, Bytes, Hash, Hex } from 'ox'
2+
import { getSignPayload } from 'ox/TypedData'
23
import { RECOVER_SAPIENT_SIGNATURE } from './constants.js'
4+
import { Attestation } from './index.js'
35
import { minBytesFor } from './utils.js'
4-
import { getSignPayload } from 'ox/TypedData'
56

67
export type Call = {
78
to: Address.Address
@@ -35,11 +36,17 @@ export type Digest = {
3536
digest: Hex.Hex
3637
}
3738

39+
export type SessionImplicitAuthorize = {
40+
type: 'session-implicit-authorize'
41+
sessionAddress: Address.Address
42+
attestation: Attestation.Attestation
43+
}
44+
3845
export type Parent = {
3946
parentWallets?: Address.Address[]
4047
}
4148

42-
export type Payload = Calls | Message | ConfigUpdate | Digest
49+
export type Payload = Calls | Message | ConfigUpdate | Digest | SessionImplicitAuthorize
4350

4451
export type Parented = Payload & Parent
4552

@@ -101,6 +108,10 @@ export function isDigest(payload: Payload): payload is Digest {
101108
return payload.type === 'digest'
102109
}
103110

111+
export function isSessionImplicitAuthorize(payload: Payload): payload is SessionImplicitAuthorize {
112+
return payload.type === 'session-implicit-authorize'
113+
}
114+
104115
export function encode(payload: Calls, self?: Address.Address): Bytes.Bytes {
105116
const callsLen = payload.calls.length
106117
const nonceBytesNeeded = minBytesFor(payload.nonce)
@@ -302,6 +313,9 @@ export function hash(wallet: Address.Address, chainId: bigint, payload: Parented
302313
if (isDigest(payload)) {
303314
return Bytes.fromHex(payload.digest)
304315
}
316+
if (isSessionImplicitAuthorize(payload)) {
317+
return Attestation.hash(payload.attestation)
318+
}
305319
const typedData = toTyped(wallet, chainId, payload)
306320
return Bytes.fromHex(getSignPayload(typedData))
307321
}
@@ -402,7 +416,11 @@ export function toTyped(wallet: Address.Address, chainId: bigint, payload: Paren
402416
}
403417

404418
case 'digest': {
405-
throw new Error('Digest is not supported - Use message instead')
419+
throw new Error('Digest does not support typed data - Use message instead')
420+
}
421+
422+
case 'session-implicit-authorize': {
423+
throw new Error('Payload does not support typed data')
406424
}
407425
}
408426
}

packages/wallet/primitives/src/signature.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1389,5 +1389,8 @@ function encode(
13891389
digest: payload.digest,
13901390
parentWallets: payload.parentWallets ?? [],
13911391
}
1392+
1393+
default:
1394+
throw new Error('Invalid payload type')
13921395
}
13931396
}

packages/wallet/wdk/src/sequence/manager.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -430,15 +430,19 @@ export class Manager {
430430
return this.shared.modules.sessions.getSessionTopology(walletAddress)
431431
}
432432

433-
public async authorizeImplicitSession(
433+
public async prepareAuthorizeImplicitSession(
434434
walletAddress: Address.Address,
435435
sessionAddress: Address.Address,
436436
args: AuthorizeImplicitSessionArgs,
437-
): Promise<{
437+
): Promise<string> {
438+
return this.shared.modules.sessions.prepareAuthorizeImplicitSession(walletAddress, sessionAddress, args)
439+
}
440+
441+
public async completeAuthorizeImplicitSession(requestId: string): Promise<{
438442
attestation: Attestation.Attestation
439443
signature: SequenceSignature.RSY
440444
}> {
441-
return this.shared.modules.sessions.authorizeImplicitSession(walletAddress, sessionAddress, args)
445+
return this.shared.modules.sessions.completeAuthorizeImplicitSession(requestId)
442446
}
443447

444448
public async addExplicitSession(

packages/wallet/wdk/src/sequence/sessions.ts

+48-36
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { Signers as CoreSigners, Envelope, Wallet } from '@0xsequence/wallet-core'
2-
import { Attestation, Payload, SessionConfig, Signature as SequenceSignature } from '@0xsequence/wallet-primitives'
2+
import {
3+
Attestation,
4+
Payload,
5+
SessionConfig,
6+
Signature as SequenceSignature,
7+
Config,
8+
} from '@0xsequence/wallet-primitives'
39
import { Address, Bytes, Hex, Provider, RpcTransport } from 'ox'
410
import { SessionController } from '../session/index.js'
511
import { IdentityHandler, identityTypeToHex } from './handlers/identity.js'
612
import { Shared } from './manager.js'
713
import { AuthCodePkceHandler } from './handlers/authcode-pkce.js'
814
import { IdentityType } from '../identity/index.js'
9-
import { isSignature } from '../../../core/dist/envelope.js'
1015

1116
export type AuthorizeImplicitSessionArgs = {
1217
target: string
@@ -58,14 +63,11 @@ export class Sessions {
5863
return controller.getTopology()
5964
}
6065

61-
async authorizeImplicitSession(
66+
async prepareAuthorizeImplicitSession(
6267
walletAddress: Address.Address,
6368
sessionAddress: Address.Address,
6469
args: AuthorizeImplicitSessionArgs,
65-
): Promise<{
66-
attestation: Attestation.Attestation
67-
signature: SequenceSignature.RSY
68-
}> {
70+
): Promise<string> {
6971
const topology = await this.getSessionTopology(walletAddress)
7072
const identitySignerAddress = SessionConfig.getIdentitySigner(topology)
7173
if (!identitySignerAddress) {
@@ -80,7 +82,7 @@ export class Sessions {
8082
throw new Error('No identity handler found')
8183
}
8284

83-
// Create the digest to sign
85+
// Create the attestation to sign
8486
let identityType: IdentityType | undefined
8587
let issuerHash: Hex.Hex = '0x'
8688
let audienceHash: Hex.Hex = '0x'
@@ -103,52 +105,62 @@ export class Sessions {
103105
redirectUrl: args.target,
104106
},
105107
}
106-
const attestationHash = Attestation.hash(attestation)
107-
const walletStatus = await this.getCoreWallet(walletAddress).getStatus()
108-
const envelope: Envelope.Envelope<Payload.Digest> = {
108+
// Fake the configuration with the single required signer
109+
const configuration: Config.Config = {
110+
threshold: 1n,
111+
checkpoint: 0n,
112+
topology: {
113+
type: 'signer',
114+
address: identitySignerAddress,
115+
weight: 1n,
116+
},
117+
}
118+
const envelope: Envelope.Envelope<Payload.SessionImplicitAuthorize> = {
109119
payload: {
110-
type: 'digest',
111-
digest: Hex.fromBytes(attestationHash),
120+
type: 'session-implicit-authorize',
121+
sessionAddress,
122+
attestation,
112123
},
113124
wallet: walletAddress,
114125
chainId: 0n,
115-
configuration: walletStatus.configuration,
126+
configuration,
116127
}
117128

118129
// Request the signature from the identity handler
119-
const requestId = await this.shared.modules.signatures.request(envelope, 'sign-digest', {
130+
return this.shared.modules.signatures.request(envelope, 'session-implicit-authorize', {
120131
origin: args.target,
121132
})
122-
let signatureRequest = await this.shared.modules.signatures.get(requestId)
123-
const identitySigner = signatureRequest.signers.find((s) => s.address === identitySignerAddress)
124-
if (!identitySigner || (identitySigner.status !== 'actionable' && identitySigner.status !== 'ready')) {
125-
throw new Error(`Identity signer not found or not ready: ${identitySigner?.status}`)
126-
}
127-
const handled = await identitySigner.handle()
128-
if (!handled) {
129-
throw new Error('Failed to handle identity handler')
133+
}
134+
135+
async completeAuthorizeImplicitSession(requestId: string): Promise<{
136+
attestation: Attestation.Attestation
137+
signature: SequenceSignature.RSY
138+
}> {
139+
// Get the updated signature request
140+
const signatureRequest = await this.shared.modules.signatures.get(requestId)
141+
if (
142+
signatureRequest.action !== 'session-implicit-authorize' ||
143+
!Payload.isSessionImplicitAuthorize(signatureRequest.envelope.payload)
144+
) {
145+
throw new Error('Invalid action')
130146
}
131-
// Get the updated signature request. Then delete it, we don't need it anymore
132-
signatureRequest = await this.shared.modules.signatures.get(requestId)
133-
await this.shared.modules.signatures.cancel(requestId)
134-
// Find the handler signature
135-
const signatures = signatureRequest.envelope.signatures.filter(
136-
(sig) => isSignature(sig) && sig.address === identitySignerAddress,
137-
)
138-
if (signatures.length === 0) {
139-
throw new Error('No signatures found')
147+
148+
if (!Envelope.isSigned(signatureRequest.envelope) || !Envelope.reachedThreshold(signatureRequest.envelope)) {
149+
throw new Error('Envelope not signed or threshold not reached')
140150
}
141-
const signature = signatures[0]
142-
if (!signature) {
143-
throw new Error('No signature found')
151+
152+
// Find any valid signature
153+
const signature = signatureRequest.envelope.signatures[0]
154+
if (!signature || !Envelope.isSignature(signature)) {
155+
throw new Error('No valid signature found')
144156
}
145157
if (signature.signature.type !== 'hash') {
146158
// Should never happen
147159
throw new Error('Unsupported signature type')
148160
}
149161

150162
return {
151-
attestation,
163+
attestation: signatureRequest.envelope.payload.attestation,
152164
signature: signature.signature,
153165
}
154166
}

packages/wallet/wdk/src/sequence/types/signature-request.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ export type ActionToPayload = {
88
[Actions.Login]: Payload.ConfigUpdate
99
[Actions.SendTransaction]: Payload.Calls
1010
[Actions.SessionUpdate]: Payload.ConfigUpdate
11-
[Actions.SignDigest]: Payload.Digest
11+
[Actions.SessionImplicitAuthorize]: Payload.SessionImplicitAuthorize
1212
}
1313

1414
export const Actions = {
1515
Logout: 'logout',
1616
Login: 'login',
1717
SendTransaction: 'send-transaction',
1818
SessionUpdate: 'session-update',
19-
SignDigest: 'sign-digest',
19+
SessionImplicitAuthorize: 'session-implicit-authorize',
2020
} as const
2121

2222
export type Action = (typeof Actions)[keyof typeof Actions]

packages/wallet/wdk/test/sessions.test.ts

+18-7
Original file line numberDiff line numberDiff line change
@@ -250,13 +250,24 @@ describe('Sessions (via Manager)', () => {
250250
}
251251

252252
// Request the session authorization from the WDK
253-
const { attestation, signature: identitySignature } = await wdk.manager.authorizeImplicitSession(
254-
dapp.wallet.address,
255-
e.address,
256-
{
257-
target: 'https://example.com',
258-
},
259-
)
253+
const requestId = await wdk.manager.prepareAuthorizeImplicitSession(dapp.wallet.address, e.address, {
254+
target: 'https://example.com',
255+
})
256+
257+
// Sign the request (Wallet UI action)
258+
const sigRequest = await wdk.manager.getSignatureRequest(requestId)
259+
const identitySigner = sigRequest.signers[0]
260+
if (!identitySigner || identitySigner.status !== 'ready') {
261+
throw new Error(`Identity signer not found or not ready: ${identitySigner?.status}`)
262+
}
263+
const handled = await identitySigner.handle()
264+
if (!handled) {
265+
throw new Error('Failed to handle identity signer')
266+
}
267+
268+
// Complete the request
269+
const { attestation, signature: identitySignature } =
270+
await wdk.manager.completeAuthorizeImplicitSession(requestId)
260271

261272
// Load the implicit signer
262273
const implicitSigner = new CoreSigners.Session.Implicit(

0 commit comments

Comments
 (0)