Skip to content

Commit 3473bf7

Browse files
committed
imp(sdk/js): compatible for solana & viem
1 parent 52e94ef commit 3473bf7

File tree

5 files changed

+274
-35
lines changed

5 files changed

+274
-35
lines changed
Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,96 @@
1-
import { expect, describe, it } from 'vitest'
1+
import crypto from 'crypto'
2+
import { expect, describe, it, vi } from 'vitest'
23
import { Keypair } from '@solana/web3.js'
34

4-
import { DstackClient } from '../index'
5-
import { toKeypair } from '../solana'
5+
import { DstackClient, TappdClient } from '../index'
6+
import { toKeypair, toKeypairSecure } from '../solana'
67

78
describe('solana support', () => {
8-
it('should able to get keypair from deriveKey', async () => {
9-
const client = new DstackClient()
10-
const result = await client.getKey('/', 'test')
11-
const keypair = toKeypair(result)
12-
expect(keypair).toBeInstanceOf(Keypair)
13-
console.log(keypair.publicKey.toBase58())
9+
describe('toKeypair (legacy)', () => {
10+
it('should able to get keypair from getKey with DstackClient', async () => {
11+
const client = new DstackClient()
12+
const result = await client.getKey('/', 'test')
13+
const keypair = toKeypair(result)
14+
expect(keypair).toBeInstanceOf(Keypair)
15+
expect(keypair.secretKey.length).toBe(64)
16+
})
17+
18+
it('should able to get keypair from deriveKey with TappdClient', async () => {
19+
const client = new TappdClient()
20+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
21+
22+
const result = await client.deriveKey('/', 'test')
23+
const keypair = toKeypair(result)
24+
expect(keypair).toBeInstanceOf(Keypair)
25+
expect(keypair.secretKey.length).toBe(64)
26+
expect(consoleSpy).toHaveBeenCalledWith('toKeypair: Please don\'t use `deriveKey` method to get key, use `getKey` instead.')
27+
28+
consoleSpy.mockRestore()
29+
})
30+
31+
it('should able to get keypair from getTlsKey with DstackClient', async () => {
32+
const client = new DstackClient()
33+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
34+
35+
const result = await client.getTlsKey()
36+
const keypair = toKeypair(result)
37+
expect(keypair).toBeInstanceOf(Keypair)
38+
expect(keypair.secretKey.length).toBe(64)
39+
expect(consoleSpy).toHaveBeenCalledWith('toKeypair: Please don\'t use `deriveKey` method to get key, use `getKey` instead.')
40+
41+
consoleSpy.mockRestore()
42+
})
43+
})
44+
45+
describe('toKeypairSecure', () => {
46+
it('should able to get keypair from getKey with DstackClient', async () => {
47+
const client = new DstackClient()
48+
const result = await client.getKey('/', 'test')
49+
const keypair = toKeypairSecure(result)
50+
expect(keypair).toBeInstanceOf(Keypair)
51+
expect(keypair.secretKey.length).toBe(64)
52+
})
53+
54+
it('should able to get keypair from deriveKey with TappdClient', async () => {
55+
const client = new TappdClient()
56+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
57+
58+
const result = await client.deriveKey('/', 'test')
59+
const keypair = toKeypairSecure(result)
60+
expect(keypair).toBeInstanceOf(Keypair)
61+
expect(keypair.secretKey.length).toBe(64)
62+
expect(consoleSpy).toHaveBeenCalledWith('toKeypairSecure: Please don\'t use `deriveKey` method to get key, use `getKey` instead.')
63+
64+
consoleSpy.mockRestore()
65+
})
66+
67+
it('should able to get keypair from getTlsKey with DstackClient', async () => {
68+
const client = new DstackClient()
69+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
70+
71+
const result = await client.getTlsKey()
72+
const keypair = toKeypairSecure(result)
73+
expect(keypair).toBeInstanceOf(Keypair)
74+
expect(keypair.secretKey.length).toBe(64)
75+
expect(consoleSpy).toHaveBeenCalledWith('toKeypairSecure: Please don\'t use `deriveKey` method to get key, use `getKey` instead.')
76+
77+
consoleSpy.mockRestore()
78+
})
79+
80+
it('should throw error when sha256 is not supported', async () => {
81+
const client = new DstackClient()
82+
const result = await client.getTlsKey()
83+
84+
// Mock crypto.createHash to simulate missing sha256 support
85+
const originalCreateHash = crypto.createHash
86+
crypto.createHash = () => {
87+
throw new Error('sha256 not supported')
88+
}
89+
90+
expect(() => toKeypairSecure(result)).toThrow('toKeypairSecure: missing sha256 support')
91+
92+
// Restore original createHash
93+
crypto.createHash = originalCreateHash
94+
})
1495
})
1596
})

sdk/js/src/__tests__/viem.test.ts

Lines changed: 102 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,106 @@
1-
import { expect, describe, it } from 'vitest'
2-
import { DstackClient } from '../index'
3-
import { toViemAccount } from '../viem'
1+
import crypto from 'crypto'
2+
import { expect, describe, it, vi } from 'vitest'
3+
import { DstackClient, TappdClient } from '../index'
4+
import { toViemAccount, toViemAccountSecure } from '../viem'
45

56
describe('viem support', () => {
6-
it('should able to get account from getKey', async () => {
7-
const client = new DstackClient()
8-
const result = await client.getKey('/', 'test')
9-
const account = toViemAccount(result)
10-
11-
expect(account.source).toBe('privateKey')
12-
expect(typeof account.sign).toBe('function')
13-
expect(typeof account.signMessage).toBe('function')
7+
describe('toViemAccount (legacy)', () => {
8+
it('should able to get account from getKey with DstackClient', async () => {
9+
const client = new DstackClient()
10+
const result = await client.getKey('/', 'test')
11+
const account = toViemAccount(result)
12+
13+
expect(account.source).toBe('privateKey')
14+
expect(typeof account.sign).toBe('function')
15+
expect(typeof account.signMessage).toBe('function')
16+
})
17+
18+
it('should able to get account from deriveKey with TappdClient', async () => {
19+
const client = new TappdClient()
20+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
21+
22+
const result = await client.deriveKey('/', 'test')
23+
const account = toViemAccount(result)
24+
25+
expect(account.source).toBe('privateKey')
26+
expect(typeof account.sign).toBe('function')
27+
expect(typeof account.signMessage).toBe('function')
28+
expect(consoleSpy).toHaveBeenCalledWith('toViemAccount: Please don\'t use `deriveKey` method to get key, use `getKey` instead.')
29+
30+
consoleSpy.mockRestore()
31+
})
32+
33+
it('should able to get account from getTlsKey with DstackClient', async () => {
34+
const client = new DstackClient()
35+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
36+
37+
const result = await client.getTlsKey()
38+
const account = toViemAccount(result)
39+
40+
expect(account.source).toBe('privateKey')
41+
expect(typeof account.sign).toBe('function')
42+
expect(typeof account.signMessage).toBe('function')
43+
expect(consoleSpy).toHaveBeenCalledWith('toViemAccount: Please don\'t use `deriveKey` method to get key, use `getKey` instead.')
44+
45+
consoleSpy.mockRestore()
46+
})
47+
})
48+
49+
describe('toViemAccountSecure', () => {
50+
it('should able to get account from getKey with DstackClient', async () => {
51+
const client = new DstackClient()
52+
const result = await client.getKey('/', 'test')
53+
const account = toViemAccountSecure(result)
54+
55+
expect(account.source).toBe('privateKey')
56+
expect(typeof account.sign).toBe('function')
57+
expect(typeof account.signMessage).toBe('function')
58+
})
59+
60+
it('should able to get account from deriveKey with TappdClient', async () => {
61+
const client = new TappdClient()
62+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
63+
64+
const result = await client.deriveKey('/', 'test')
65+
const account = toViemAccountSecure(result)
66+
67+
expect(account.source).toBe('privateKey')
68+
expect(typeof account.sign).toBe('function')
69+
expect(typeof account.signMessage).toBe('function')
70+
expect(consoleSpy).toHaveBeenCalledWith('toViemAccountSecure: Please don\'t use `deriveKey` method to get key, use `getKey` instead.')
71+
72+
consoleSpy.mockRestore()
73+
})
74+
75+
it('should able to get account from getTlsKey with DstackClient', async () => {
76+
const client = new DstackClient()
77+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
78+
79+
const result = await client.getTlsKey()
80+
const account = toViemAccountSecure(result)
81+
82+
expect(account.source).toBe('privateKey')
83+
expect(typeof account.sign).toBe('function')
84+
expect(typeof account.signMessage).toBe('function')
85+
expect(consoleSpy).toHaveBeenCalledWith('toViemAccountSecure: Please don\'t use `deriveKey` method to get key, use `getKey` instead.')
86+
87+
consoleSpy.mockRestore()
88+
})
89+
90+
it('should throw error when sha256 is not supported', async () => {
91+
const client = new DstackClient()
92+
const result = await client.getTlsKey()
93+
94+
// Mock crypto.createHash to simulate missing sha256 support
95+
const originalCreateHash = crypto.createHash
96+
crypto.createHash = () => {
97+
throw new Error('sha256 not supported')
98+
}
99+
100+
expect(() => toViemAccountSecure(result)).toThrow('toViemAccountSecure: missing sha256 support')
101+
102+
// Restore original createHash
103+
crypto.createHash = originalCreateHash
104+
})
14105
})
15106
})

sdk/js/src/index.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ export { getComposeHash } from './get-compose-hash'
55
export { verifyEnvEncryptPublicKey } from './verify-env-encrypt-public-key'
66

77
export interface GetTlsKeyResponse {
8+
__name__: Readonly<'GetTlsKeyResponse'>
9+
810
key: string
911
certificate_chain: string[]
1012

1113
asUint8Array: (max_length?: number) => Uint8Array
1214
}
1315

1416
export interface GetKeyResponse {
17+
__name__: Readonly<'GetKeyResponse'>
18+
1519
key: Uint8Array
1620
signature_chain: Uint8Array[]
1721
}
@@ -150,7 +154,8 @@ export class DstackClient {
150154
const result = await send_rpc_request<{ key: string, signature_chain: string[] }>(this.endpoint, '/GetKey', payload)
151155
return Object.freeze({
152156
key: new Uint8Array(Buffer.from(result.key, 'hex')),
153-
signature_chain: result.signature_chain.map(sig => new Uint8Array(Buffer.from(sig, 'hex')))
157+
signature_chain: result.signature_chain.map(sig => new Uint8Array(Buffer.from(sig, 'hex'))),
158+
__name__: 'GetKeyResponse',
154159
})
155160
}
156161

@@ -174,12 +179,12 @@ export class DstackClient {
174179
}
175180
const payload = JSON.stringify(raw)
176181
const result = await send_rpc_request<GetTlsKeyResponse>(this.endpoint, '/GetTlsKey', payload)
177-
Object.defineProperty(result, 'asUint8Array', {
178-
get: () => (length?: number) => x509key_to_uint8array(result.key, length),
179-
enumerable: true,
180-
configurable: false,
182+
const asUint8Array = (length?: number) => x509key_to_uint8array(result.key, length)
183+
return Object.freeze({
184+
...result,
185+
asUint8Array,
186+
__name__: 'GetTlsKeyResponse',
181187
})
182-
return Object.freeze(result)
183188
}
184189

185190
async getQuote(report_data: string | Buffer | Uint8Array): Promise<GetQuoteResponse> {
@@ -303,12 +308,12 @@ export class TappdClient extends DstackClient {
303308
}
304309
const payload = JSON.stringify(raw)
305310
const result = await send_rpc_request<GetTlsKeyResponse>(this.endpoint, '/prpc/Tappd.DeriveKey', payload)
306-
Object.defineProperty(result, 'asUint8Array', {
307-
get: () => (length?: number) => x509key_to_uint8array(result.key, length),
308-
enumerable: true,
309-
configurable: false,
311+
const asUint8Array = (length?: number) => x509key_to_uint8array(result.key, length)
312+
return Object.freeze({
313+
...result,
314+
asUint8Array,
315+
__name__: 'GetTlsKeyResponse',
310316
})
311-
return Object.freeze(result)
312317
}
313318

314319
/**

sdk/js/src/solana.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,37 @@
1-
import { type GetKeyResponse } from './index'
1+
import crypto from 'crypto'
2+
import { type GetKeyResponse, type GetTlsKeyResponse } from './index'
23
import { Keypair } from '@solana/web3.js'
34

4-
export function toKeypair(keyResponse: GetKeyResponse) {
5+
/**
6+
* @deprecated use toKeypairSecure instead. This method has security concerns.
7+
* Current implementation uses raw key material without proper hashing.
8+
*/
9+
export function toKeypair(keyResponse: GetTlsKeyResponse | GetKeyResponse) {
10+
// Keep legacy behavior for GetTlsKeyResponse, but with warning.
11+
if (keyResponse.__name__ === 'GetTlsKeyResponse') {
12+
console.warn('toKeypair: Please don\'t use `deriveKey` method to get key, use `getKey` instead.')
13+
// Restored original behavior: using first 32 bytes directly
14+
const bytes = keyResponse.asUint8Array(32)
15+
return Keypair.fromSeed(bytes)
16+
}
17+
return Keypair.fromSeed(keyResponse.key)
18+
}
19+
20+
/**
21+
* Creates a Solana Keypair from DeriveKeyResponse using secure key derivation.
22+
* This method applies SHA256 hashing to the complete key material for enhanced security.
23+
*/
24+
export function toKeypairSecure(keyResponse: GetTlsKeyResponse | GetKeyResponse) {
25+
// Keep legacy behavior for GetTlsKeyResponse, but with warning.
26+
if (keyResponse.__name__ === 'GetTlsKeyResponse') {
27+
try {
28+
console.warn('toKeypairSecure: Please don\'t use `deriveKey` method to get key, use `getKey` instead.')
29+
// Get supported hash algorithm by `openssl list -digest-algorithms`, but it's not guaranteed to be supported by node.js
30+
const buf = crypto.createHash('sha256').update(keyResponse.asUint8Array()).digest()
31+
return Keypair.fromSeed(buf)
32+
} catch (err) {
33+
throw new Error('toKeypairSecure: missing sha256 support, please upgrade your openssl and node.js')
34+
}
35+
}
536
return Keypair.fromSeed(keyResponse.key)
637
}

sdk/js/src/viem.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,38 @@
1-
import { type GetKeyResponse } from './index'
1+
import crypto from 'crypto'
2+
import { type GetKeyResponse, type GetTlsKeyResponse } from './index'
23
import { privateKeyToAccount } from 'viem/accounts'
34

4-
export function toViemAccount(keyResponse: GetKeyResponse) {
5+
/**
6+
* @deprecated use toViemAccountSecure instead. This method has security concerns.
7+
* Current implementation uses raw key material without proper hashing.
8+
*/
9+
export function toViemAccount(keyResponse: GetKeyResponse | GetTlsKeyResponse) {
10+
// Keep legacy behavior for GetTlsKeyResponse, but with warning.
11+
if (keyResponse.__name__ === 'GetTlsKeyResponse') {
12+
console.warn('toViemAccount: Please don\'t use `deriveKey` method to get key, use `getKey` instead.')
13+
const hex = Array.from(keyResponse.asUint8Array(32)).map(b => b.toString(16).padStart(2, '0')).join('')
14+
return privateKeyToAccount(`0x${hex}`)
15+
}
516
const hex = Array.from(keyResponse.key).map(b => b.toString(16).padStart(2, '0')).join('')
617
return privateKeyToAccount(`0x${hex}`)
718
}
19+
20+
/**
21+
* Creates a Viem account from DeriveKeyResponse using secure key derivation.
22+
* This method applies SHA256 hashing to the complete key material for enhanced security.
23+
*/
24+
export function toViemAccountSecure(keyResponse: GetKeyResponse | GetTlsKeyResponse) {
25+
// Keep legacy behavior for GetTlsKeyResponse, but with warning.
26+
if (keyResponse.__name__ === 'GetTlsKeyResponse') {
27+
console.warn('toViemAccountSecure: Please don\'t use `deriveKey` method to get key, use `getKey` instead.')
28+
try {
29+
// Get supported hash algorithm by `openssl list -digest-algorithms`, but it's not guaranteed to be supported by node.js
30+
const hex = crypto.createHash('sha256').update(keyResponse.asUint8Array()).digest('hex')
31+
return privateKeyToAccount(`0x${hex}`)
32+
} catch (err) {
33+
throw new Error('toViemAccountSecure: missing sha256 support, please upgrade your openssl and node.js')
34+
}
35+
}
36+
const hex = Array.from(keyResponse.key).map(b => b.toString(16).padStart(2, '0')).join('')
37+
return privateKeyToAccount(`0x${hex}`)
38+
}

0 commit comments

Comments
 (0)