Skip to content

Commit 6bb3e32

Browse files
committed
Merge branch 'master' of github.com:0xsequence/sequence.js into test-wdk
2 parents 0835476 + 47cb53d commit 6bb3e32

19 files changed

Lines changed: 381 additions & 66 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "@0xsequence/identity-instrument",
3+
"version": "0.0.0",
4+
"license": "Apache-2.0",
5+
"type": "module",
6+
"scripts": {
7+
"build": "tsc",
8+
"dev": "tsc --watch",
9+
"test": "vitest run"
10+
},
11+
"exports": {
12+
".": {
13+
"types": "./dist/index.d.ts",
14+
"default": "./dist/index.js"
15+
}
16+
},
17+
"devDependencies": {
18+
"@repo/typescript-config": "workspace:^",
19+
"@types/node": "^22.13.9",
20+
"typescript": "^5.7.3",
21+
"vitest": "^3.1.2"
22+
},
23+
"dependencies": {
24+
"jwt-decode": "^4.0.0",
25+
"ox": "^0.7.0"
26+
}
27+
}

packages/wallet/wdk/src/identity/challenge.ts renamed to packages/services/identity-instrument/src/challenge.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import { Bytes, Hash, Hex } from 'ox'
22
import { jwtDecode } from 'jwt-decode'
3-
import { IdentityType, AuthMode } from './nitro/index.js'
3+
import { IdentityType, AuthMode } from './identity-instrument.gen.js'
44

5-
export interface CommitChallengeParams {
5+
interface CommitChallengeParams {
66
authMode: AuthMode
77
identityType: IdentityType
88
handle?: string
99
signer?: string
1010
metadata: { [key: string]: string }
1111
}
1212

13-
export interface CompleteChallengeParams {
13+
interface CompleteChallengeParams {
1414
authMode: AuthMode
1515
identityType: IdentityType
1616
verifier: string

packages/wallet/wdk/src/identity/nitro/identity-instrument.gen.ts renamed to packages/services/identity-instrument/src/identity-instrument.gen.ts

File renamed without changes.
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Hex, Bytes } from 'ox'
2+
import {
3+
CommitVerifierReturn,
4+
CompleteAuthReturn,
5+
IdentityInstrument as IdentityInstrumentRpc,
6+
KeyType,
7+
IdentityType,
8+
AuthMode,
9+
} from './identity-instrument.gen.js'
10+
import { Challenge } from './challenge.js'
11+
12+
export type { CommitVerifierReturn, CompleteAuthReturn }
13+
export { KeyType, IdentityType, AuthMode }
14+
export * from './challenge.js'
15+
16+
export class IdentityInstrument {
17+
private rpc: IdentityInstrumentRpc
18+
19+
constructor(hostname: string, fetch = window.fetch) {
20+
this.rpc = new IdentityInstrumentRpc(hostname.endsWith('/') ? hostname.slice(0, -1) : hostname, fetch)
21+
}
22+
23+
async commitVerifier(authKey: AuthKey, challenge: Challenge) {
24+
return this.rpc.commitVerifier({
25+
params: {
26+
...challenge.getCommitParams(),
27+
authKey: {
28+
publicKey: authKey.address,
29+
keyType: authKey.keyType,
30+
},
31+
},
32+
})
33+
}
34+
35+
async completeAuth(authKey: AuthKey, challenge: Challenge) {
36+
return this.rpc.completeAuth({
37+
params: {
38+
...challenge.getCompleteParams(),
39+
authKey: {
40+
publicKey: authKey.address,
41+
keyType: authKey.keyType,
42+
},
43+
},
44+
})
45+
}
46+
47+
async sign(authKey: AuthKey, digest: Bytes.Bytes) {
48+
const res = await this.rpc.sign({
49+
params: {
50+
signer: authKey.signer,
51+
digest: Hex.fromBytes(digest),
52+
authKey: {
53+
publicKey: authKey.address,
54+
keyType: authKey.keyType,
55+
},
56+
signature: await authKey.sign(digest),
57+
},
58+
})
59+
Hex.assert(res.signature)
60+
return res.signature
61+
}
62+
}
63+
64+
export interface AuthKey {
65+
signer: string
66+
address: string
67+
keyType: KeyType
68+
sign(digest: Bytes.Bytes): Promise<Hex.Hex>
69+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { AuthCodeChallenge, AuthCodePkceChallenge, IdTokenChallenge, OtpChallenge } from '../src/challenge.js'
3+
import { IdentityType } from '../src/index.js'
4+
5+
describe('IdTokenChallenge', () => {
6+
const idToken =
7+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaXNzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsImF1ZCI6ImF1ZGllbmNlIiwiaWF0IjoxNzE2MjM5MDIyLCJleHAiOjE4MTYyMzkwMjJ9.vo-hzFNUd8uzKmMVEj04eIiqeXfOQahZu9ZWGnJPE74'
8+
9+
it('returns correct commit params', () => {
10+
const challenge = new IdTokenChallenge('https://example.com', 'audience', idToken)
11+
const params = challenge.getCommitParams()
12+
expect(params).toBeDefined()
13+
expect(params.authMode).toBe('IDToken')
14+
expect(params.identityType).toBe('OIDC')
15+
expect(params.handle).toBe('0x800fa2a1ca87f4a37d7f0a2e1858d36cd622cc2970d886e7e8a00f82edca3455')
16+
expect(params.metadata).toBeDefined()
17+
expect(params.metadata.iss).toBe('https://example.com')
18+
expect(params.metadata.aud).toBe('audience')
19+
expect(params.metadata.exp).toBe('1816239022')
20+
})
21+
22+
it('returns correct complete params', () => {
23+
const challenge = new IdTokenChallenge('https://example.com', 'audience', idToken)
24+
const params = challenge.getCompleteParams()
25+
expect(params).toBeDefined()
26+
expect(params.authMode).toBe('IDToken')
27+
expect(params.identityType).toBe('OIDC')
28+
expect(params.verifier).toBe('0x800fa2a1ca87f4a37d7f0a2e1858d36cd622cc2970d886e7e8a00f82edca3455')
29+
expect(params.answer).toBe(idToken)
30+
})
31+
})
32+
33+
describe('AuthCodeChallenge', () => {
34+
const authCode = '1234567890'
35+
const signer = '0x26F5B2b3Feed8f02051c0b1c5b40cc088107935e'
36+
37+
it('returns correct commit params', () => {
38+
const challenge = new AuthCodeChallenge('https://example.com', 'audience', 'https://dapp.com/redirect', authCode)
39+
const params = challenge.getCommitParams()
40+
expect(params).toBeDefined()
41+
expect(params.authMode).toBe('AuthCode')
42+
expect(params.identityType).toBe('OIDC')
43+
expect(params.handle).toBe('0x38301fb0b5fcf3aaa4b97c4771bb6c75546e313b4ce7057c51a8cc6a3ace9d7e')
44+
expect(params.signer).toBeUndefined()
45+
expect(params.metadata).toBeDefined()
46+
expect(params.metadata.iss).toBe('https://example.com')
47+
expect(params.metadata.aud).toBe('audience')
48+
expect(params.metadata.redirect_uri).toBe('https://dapp.com/redirect')
49+
})
50+
51+
it('returns correct commit params with signer', () => {
52+
const challenge = new AuthCodeChallenge('https://example.com', 'audience', 'https://dapp.com/redirect', authCode)
53+
const params = challenge.withSigner(signer).getCommitParams()
54+
expect(params).toBeDefined()
55+
expect(params.authMode).toBe('AuthCode')
56+
expect(params.identityType).toBe('OIDC')
57+
expect(params.signer).toBe(signer)
58+
expect(params.handle).toBe('0x38301fb0b5fcf3aaa4b97c4771bb6c75546e313b4ce7057c51a8cc6a3ace9d7e')
59+
expect(params.metadata).toBeDefined()
60+
expect(params.metadata.iss).toBe('https://example.com')
61+
expect(params.metadata.aud).toBe('audience')
62+
expect(params.metadata.redirect_uri).toBe('https://dapp.com/redirect')
63+
})
64+
65+
it('returns correct complete params', () => {
66+
const challenge = new AuthCodeChallenge('https://example.com', 'audience', 'https://dapp.com/redirect', authCode)
67+
const params = challenge.getCompleteParams()
68+
expect(params).toBeDefined()
69+
expect(params.authMode).toBe('AuthCode')
70+
expect(params.identityType).toBe('OIDC')
71+
expect(params.verifier).toBe('0x38301fb0b5fcf3aaa4b97c4771bb6c75546e313b4ce7057c51a8cc6a3ace9d7e')
72+
expect(params.answer).toBe(authCode)
73+
})
74+
})
75+
76+
describe('AuthCodePkceChallenge', () => {
77+
const challenge = new AuthCodePkceChallenge('https://example.com', 'audience', 'https://dapp.com/redirect')
78+
const authCode = '1234567890'
79+
const verifier = 'verifier'
80+
const signer = '0x26F5B2b3Feed8f02051c0b1c5b40cc088107935e'
81+
82+
it('returns correct commit params', () => {
83+
const params = challenge.getCommitParams()
84+
expect(params).toBeDefined()
85+
expect(params.authMode).toBe('AuthCodePKCE')
86+
expect(params.identityType).toBe('OIDC')
87+
expect(params.handle).toBeUndefined()
88+
expect(params.metadata).toBeDefined()
89+
expect(params.metadata.iss).toBe('https://example.com')
90+
expect(params.metadata.aud).toBe('audience')
91+
expect(params.metadata.redirect_uri).toBe('https://dapp.com/redirect')
92+
})
93+
94+
it('returns correct commit params with signer', () => {
95+
const params = challenge.withSigner(signer).getCommitParams()
96+
expect(params).toBeDefined()
97+
expect(params.authMode).toBe('AuthCodePKCE')
98+
expect(params.identityType).toBe('OIDC')
99+
expect(params.signer).toBe(signer)
100+
expect(params.handle).toBeUndefined()
101+
expect(params.metadata).toBeDefined()
102+
expect(params.metadata.iss).toBe('https://example.com')
103+
expect(params.metadata.aud).toBe('audience')
104+
expect(params.metadata.redirect_uri).toBe('https://dapp.com/redirect')
105+
})
106+
107+
it('returns correct complete params with answer and verifier', () => {
108+
const params = challenge.withAnswer(verifier, authCode).getCompleteParams()
109+
expect(params).toBeDefined()
110+
expect(params.authMode).toBe('AuthCodePKCE')
111+
expect(params.identityType).toBe('OIDC')
112+
expect(params.verifier).toBe(verifier)
113+
expect(params.answer).toBe(authCode)
114+
})
115+
116+
it('throws if answer and verifier are not provided', () => {
117+
expect(() => challenge.getCompleteParams()).toThrow()
118+
})
119+
120+
it('throws if answer is not provided', () => {
121+
expect(() => challenge.withAnswer(verifier, '').getCompleteParams()).toThrow()
122+
})
123+
124+
it('throws if verifier is not provided', () => {
125+
expect(() => challenge.withAnswer('', authCode).getCompleteParams()).toThrow()
126+
})
127+
})
128+
129+
describe('OtpChallenge', () => {
130+
const otp = '123456'
131+
const codeChallenge = 'codeChallenge'
132+
133+
// finalAnswer = keccak256(codeChallenge + otp)
134+
const finalAnswer = '0xab1b443dd7ae1f1dd51f81f8d346565c1a63e7d090a1c220e44ed578183b08f5'
135+
136+
describe('fromRecipient', () => {
137+
const recipient = 'test@example.com'
138+
139+
describe('getCommitParams', () => {
140+
it('returns correct commit params', () => {
141+
const challenge = OtpChallenge.fromRecipient(IdentityType.Email, recipient)
142+
const params = challenge.getCommitParams()
143+
expect(params).toBeDefined()
144+
expect(params.authMode).toBe('OTP')
145+
expect(params.identityType).toBe('Email')
146+
expect(params.handle).toBe(recipient)
147+
expect(params.signer).toBeUndefined()
148+
})
149+
150+
it('throws if recipient is not provided', () => {
151+
const challenge = OtpChallenge.fromRecipient(IdentityType.Email, '')
152+
expect(() => challenge.getCommitParams()).toThrow()
153+
})
154+
})
155+
156+
describe('getCompleteParams', () => {
157+
it('returns correct complete params', () => {
158+
const challenge = OtpChallenge.fromRecipient(IdentityType.Email, recipient)
159+
const params = challenge.withAnswer(codeChallenge, otp).getCompleteParams()
160+
expect(params).toBeDefined()
161+
expect(params.authMode).toBe('OTP')
162+
expect(params.identityType).toBe('Email')
163+
expect(params.verifier).toBe(recipient)
164+
expect(params.answer).toBe(finalAnswer)
165+
})
166+
167+
it('throws if answer is not provided', () => {
168+
const challenge = OtpChallenge.fromRecipient(IdentityType.Email, recipient)
169+
expect(() => challenge.getCompleteParams()).toThrow()
170+
})
171+
})
172+
})
173+
174+
describe('fromSigner', () => {
175+
const signer = '0x26F5B2b3Feed8f02051c0b1c5b40cc088107935e'
176+
177+
describe('getCommitParams', () => {
178+
it('returns correct commit params', () => {
179+
const challenge = OtpChallenge.fromSigner(IdentityType.Email, signer)
180+
const params = challenge.getCommitParams()
181+
expect(params).toBeDefined()
182+
expect(params.authMode).toBe('OTP')
183+
expect(params.identityType).toBe('Email')
184+
expect(params.handle).toBeUndefined()
185+
expect(params.signer).toBe(signer)
186+
})
187+
188+
it('throws if signer is not provided', () => {
189+
const challenge = OtpChallenge.fromSigner(IdentityType.Email, '')
190+
expect(() => challenge.getCommitParams()).toThrow()
191+
})
192+
})
193+
})
194+
})
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"extends": "@repo/typescript-config/base.json",
3+
"compilerOptions": {
4+
"rootDir": "src",
5+
"outDir": "dist",
6+
"types": ["node"]
7+
},
8+
"include": ["src"],
9+
"exclude": ["node_modules", "dist"]
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { defineConfig } from 'vitest/config'
2+
3+
export default defineConfig({
4+
test: {
5+
environment: 'happy-dom',
6+
globals: true,
7+
},
8+
})

packages/wallet/wdk/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"vitest": "^3.1.2"
2525
},
2626
"dependencies": {
27+
"@0xsequence/identity-instrument": "workspace:^",
2728
"@0xsequence/tee-verifier": "^0.1.0",
2829
"@0xsequence/wallet-core": "workspace:^",
2930
"@0xsequence/wallet-primitives": "workspace:^",

packages/wallet/wdk/src/identity/index.ts

Lines changed: 0 additions & 4 deletions
This file was deleted.

packages/wallet/wdk/src/identity/nitro/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)