@wishknish/knishio-client-ts 0.9.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,200 @@
1
+ /*
2
+ (
3
+ (/(
4
+ (//(
5
+ (///(
6
+ (/////(
7
+ (//////( )
8
+ (////////( (/)
9
+ (////////( (///)
10
+ (//////////( (////)
11
+ (//////////( (//////)
12
+ (////////////( (///////)
13
+ (/////////////( (/////////)
14
+ (//////////////( (///////////)
15
+ (///////////////( (/////////////)
16
+ (////////////////( (//////////////)
17
+ ((((((((((((((((((( (((((((((((((((
18
+ ((((((((((((((((((( ((((((((((((((
19
+ ((((((((((((((((((( ((((((((((((((
20
+ (((((((((((((((((((( (((((((((((((
21
+ (((((((((((((((((((( ((((((((((((
22
+ ((((((((((((((((((( ((((((((((((
23
+ ((((((((((((((((((( ((((((((((
24
+ ((((((((((((((((((/ (((((((((
25
+ (((((((((((((((((( ((((((((
26
+ ((((((((((((((((( (((((((
27
+ (((((((((((((((((( (((((
28
+ ################# ##
29
+ ################ #
30
+ ################# ##
31
+ %################ ###
32
+ ###############( ####
33
+ ############### ####
34
+ ############### ######
35
+ %#############( (#######
36
+ %############# #########
37
+ ############( ##########
38
+ ########### #############
39
+ ######### ##############
40
+ %######
41
+
42
+ Powered by Knish.IO: Connecting a Decentralized World
43
+
44
+ Please visit https://github.com/WishKnish/KnishIO-Client-TS for information.
45
+
46
+ License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
47
+ */
48
+
49
+ import type { EncryptedSecretPayload, SecretStorageMetadata } from '@/types/storage'
50
+ import SecretStorageException from '@/exception/SecretStorageException'
51
+ import { zeroizeBytes } from '@/libraries/secureMemory'
52
+
53
+ export const ENVELOPE_ALGORITHM = 'AES-GCM'
54
+ export const DEFAULT_ITERATIONS = 100000
55
+ export const SECRET_KEY_PREFIX = 'knishio:secret:'
56
+ export const RECOVERY_KEY_PREFIX = 'knishio:recovery:'
57
+ const GCM_IV_LENGTH = 12
58
+ const SALT_LENGTH = 16
59
+
60
+ const textEncoder = new TextEncoder()
61
+
62
+ /**
63
+ * Helper to convert Uint8Array to base64
64
+ */
65
+ export function uint8ArrayToBase64(bytes: Uint8Array): string {
66
+ let binary = ''
67
+ const len = bytes.byteLength
68
+ for (let i = 0; i < len; i++) {
69
+ const byte = bytes[i]
70
+ if (byte !== undefined) {
71
+ binary += String.fromCharCode(byte)
72
+ }
73
+ }
74
+ return btoa(binary)
75
+ }
76
+
77
+ /**
78
+ * Helper to convert base64 to Uint8Array
79
+ */
80
+ export function base64ToUint8Array(base64: string): Uint8Array {
81
+ const binary = atob(base64)
82
+ const len = binary.length
83
+ const bytes = new Uint8Array(len)
84
+ for (let i = 0; i < len; i++) {
85
+ bytes[i] = binary.charCodeAt(i)
86
+ }
87
+ return bytes
88
+ }
89
+
90
+ /**
91
+ * Derive an AES-GCM CryptoKey from a passphrase and salt using PBKDF2
92
+ */
93
+ export async function deriveEnvelopeKey(
94
+ passphrase: string,
95
+ salt: Uint8Array,
96
+ iterations = DEFAULT_ITERATIONS
97
+ ): Promise<CryptoKey> {
98
+ if (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle === 'undefined') {
99
+ throw new SecretStorageException('WebCrypto API is not available')
100
+ }
101
+
102
+ const passphraseBytes = textEncoder.encode(passphrase)
103
+ try {
104
+ const baseKey = await globalThis.crypto.subtle.importKey(
105
+ 'raw',
106
+ passphraseBytes,
107
+ 'PBKDF2',
108
+ false,
109
+ ['deriveKey']
110
+ )
111
+
112
+ return await globalThis.crypto.subtle.deriveKey(
113
+ {
114
+ name: 'PBKDF2',
115
+ salt: salt as BufferSource,
116
+ iterations,
117
+ hash: 'SHA-256'
118
+ },
119
+ baseKey,
120
+ { name: 'AES-GCM', length: 256 },
121
+ false,
122
+ ['encrypt', 'decrypt']
123
+ )
124
+ } finally {
125
+ zeroizeBytes(passphraseBytes)
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Seal a secret string into an EncryptedSecretPayload envelope
131
+ */
132
+ export async function sealEnvelope(
133
+ secret: string,
134
+ passphrase: string,
135
+ metadata: SecretStorageMetadata
136
+ ): Promise<EncryptedSecretPayload> {
137
+ if (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle === 'undefined') {
138
+ throw new SecretStorageException('WebCrypto API is not available')
139
+ }
140
+
141
+ const salt = new Uint8Array(SALT_LENGTH)
142
+ const iv = new Uint8Array(GCM_IV_LENGTH)
143
+ globalThis.crypto.getRandomValues(salt)
144
+ globalThis.crypto.getRandomValues(iv)
145
+
146
+ const key = await deriveEnvelopeKey(passphrase, salt, DEFAULT_ITERATIONS)
147
+ const secretBytes = textEncoder.encode(secret)
148
+
149
+ try {
150
+ const encryptedBuffer = await globalThis.crypto.subtle.encrypt(
151
+ {
152
+ name: ENVELOPE_ALGORITHM,
153
+ iv: iv as BufferSource
154
+ },
155
+ key,
156
+ secretBytes
157
+ )
158
+
159
+ const ciphertext = uint8ArrayToBase64(new Uint8Array(encryptedBuffer))
160
+ return {
161
+ version: 1,
162
+ ciphertext,
163
+ iv: uint8ArrayToBase64(iv),
164
+ salt: uint8ArrayToBase64(salt),
165
+ algorithm: ENVELOPE_ALGORITHM,
166
+ iterations: DEFAULT_ITERATIONS,
167
+ metadata
168
+ }
169
+ } finally {
170
+ zeroizeBytes(secretBytes)
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Open an EncryptedSecretPayload envelope with a passphrase, returning the decrypted secret bytes
176
+ */
177
+ export async function openEnvelope(
178
+ payload: EncryptedSecretPayload,
179
+ passphrase: string
180
+ ): Promise<Uint8Array> {
181
+ if (typeof globalThis.crypto === 'undefined' || typeof globalThis.crypto.subtle === 'undefined') {
182
+ throw new SecretStorageException('WebCrypto API is not available')
183
+ }
184
+
185
+ const salt = base64ToUint8Array(payload.salt)
186
+ const iv = base64ToUint8Array(payload.iv)
187
+ const ciphertext = base64ToUint8Array(payload.ciphertext)
188
+
189
+ const key = await deriveEnvelopeKey(passphrase, salt, payload.iterations ?? DEFAULT_ITERATIONS)
190
+ const decryptedBuffer = await globalThis.crypto.subtle.decrypt(
191
+ {
192
+ name: ENVELOPE_ALGORITHM,
193
+ iv: iv as BufferSource
194
+ },
195
+ key,
196
+ ciphertext as BufferSource
197
+ )
198
+
199
+ return new Uint8Array(decryptedBuffer)
200
+ }
@@ -58,8 +58,8 @@ import type { WalletAddress, BundleHash, Position, TokenSlug } from './index'
58
58
  // =============================================================================
59
59
 
60
60
  export type HashAlgorithm = 'SHAKE256' | 'SHA3-256' | 'BLAKE2B'
61
- export type SignatureAlgorithm = 'XMSS' | 'ML-KEM768' | 'SPHINCS+'
62
- export type EncryptionAlgorithm = 'ML-KEM768' | 'Kyber768' | 'ChaCha20Poly1305'
61
+ export type SignatureAlgorithm = 'XMSS' | 'SPHINCS+'
62
+ export type EncryptionAlgorithm = 'ML-KEM-1024' | 'ML-KEM-768'
63
63
 
64
64
  // =============================================================================
65
65
  // SHAKE256 SPECIFIC TYPES
@@ -139,13 +139,13 @@ export interface XMSSSignature {
139
139
  }
140
140
 
141
141
  // =============================================================================
142
- // ML-KEM768 (Post-Quantum Key Encapsulation) TYPES
142
+ // ML-KEM (Post-Quantum Key Encapsulation) TYPES
143
143
  // =============================================================================
144
144
 
145
145
  export interface MLKEMKeyPair {
146
146
  privateKey: Uint8Array
147
147
  publicKey: Uint8Array
148
- algorithm: 'ML-KEM768'
148
+ algorithm: EncryptionAlgorithm
149
149
  }
150
150
 
151
151
  export interface MLKEMEncapsulationResult {
@@ -475,6 +475,11 @@ export const CRYPTO_CONSTANTS = {
475
475
  ML_KEM768_PRIVATE_KEY_SIZE: 2400,
476
476
  ML_KEM768_CIPHERTEXT_SIZE: 1088,
477
477
  ML_KEM768_SHARED_SECRET_SIZE: 32,
478
+
479
+ ML_KEM1024_PUBLIC_KEY_SIZE: 1568,
480
+ ML_KEM1024_PRIVATE_KEY_SIZE: 3168,
481
+ ML_KEM1024_CIPHERTEXT_SIZE: 1568,
482
+ ML_KEM1024_SHARED_SECRET_SIZE: 32,
478
483
 
479
484
  KEY_FRAGMENT_SIZE: 128,
480
485
  OTS_FRAGMENT_COUNT: 16,
@@ -69,6 +69,13 @@ export interface EncryptedSecretPayload {
69
69
  iterations?: number
70
70
  metadata: SecretStorageMetadata
71
71
  }
72
+ export interface StorageOptions {
73
+ label?: string
74
+ passphrase?: string
75
+ recoveryPassphrase?: string
76
+ allowUnrecoverable?: boolean
77
+ }
78
+
72
79
 
73
80
  export interface ISecretStorageProvider {
74
81
  /**
@@ -77,7 +84,11 @@ export interface ISecretStorageProvider {
77
84
  readonly providerType: string
78
85
 
79
86
  /**
80
- * Whether this provider is backed by hardware (TPM, Secure Enclave, StrongBox)
87
+ * True only when this provider holds a non-exportable key inside platform-secure
88
+ * hardware (Android TEE/StrongBox, Secure Enclave, TPM) and learned that from the
89
+ * platform itself — never from a caller argument. Software envelope providers
90
+ * return false. The value is persisted as `metadata.hardwareBacked` in every
91
+ * envelope this provider writes.
81
92
  */
82
93
  isHardwareBacked(): boolean
83
94
 
@@ -92,7 +103,7 @@ export interface ISecretStorageProvider {
92
103
  storeSecret(
93
104
  bundleHash: string,
94
105
  secret: string,
95
- options?: { label?: string; passphrase?: string }
106
+ options?: StorageOptions
96
107
  ): Promise<void>
97
108
 
98
109
  /**
@@ -126,4 +137,13 @@ export interface ISecretStorageProvider {
126
137
  fn: (secret: string) => Promise<T> | T,
127
138
  options?: { passphrase?: string }
128
139
  ): Promise<T>
140
+
141
+ /**
142
+ * Recover a secret using its recovery envelope and re-enroll it under a fresh KEK
143
+ */
144
+ recoverSecret(
145
+ bundleHash: string,
146
+ recoveryPassphrase: string,
147
+ options?: { label?: string }
148
+ ): Promise<void>
129
149
  }
@@ -231,7 +231,8 @@ export const KnishIOClientConfigSchema = z.object({
231
231
  // isn't rejected.
232
232
  defaultRequestPolicy: z.enum(['cache-first', 'cache-only', 'network-only', 'cache-and-network']).nullable().optional(),
233
233
  // Pluggable hardware envelope encryption secret storage provider
234
- secretStorage: z.unknown().optional()
234
+ secretStorage: z.unknown().optional(),
235
+ mlKemParameterSet: z.union([z.literal(1024), z.literal(768)]).optional()
235
236
  }).strict()
236
237
 
237
238
  // Environment configuration with validation.