@wishknish/knishio-client-ts 0.9.5 → 0.9.7
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.
- package/dist/index.cjs +847 -240
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +154 -29
- package/dist/index.d.ts +154 -29
- package/dist/index.iife.js +1340 -452
- package/dist/index.iife.js.map +1 -1
- package/dist/index.js +840 -241
- package/dist/index.js.map +1 -1
- package/package.json +18 -21
- package/src/KnishIOClient.ts +100 -10
- package/src/core/AtomMeta.ts +12 -4
- package/src/core/Molecule.ts +61 -0
- package/src/core/Wallet.ts +7 -5
- package/src/exception/SecretStorageException.ts +97 -0
- package/src/exception/index.ts +1 -0
- package/src/index.ts +28 -1
- package/src/libraries/GraphQLClient.ts +21 -7
- package/src/libraries/UrqlClientWrapper.ts +8 -3
- package/src/libraries/crypto.ts +4 -6
- package/src/libraries/secureMemory.ts +121 -0
- package/src/mutation/MutationReplenishToken.ts +28 -0
- package/src/schemas/index.ts +3 -2
- package/src/storage/MemorySecretStorageProvider.ts +156 -0
- package/src/storage/WebCryptoSecretStorageProvider.ts +407 -0
- package/src/storage/index.ts +87 -0
- package/src/types/assertions.ts +2 -2
- package/src/types/index.ts +1 -1
- package/src/types/storage.ts +129 -0
- package/src/validation/schemas.ts +9 -7
|
@@ -0,0 +1,121 @@
|
|
|
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
|
+
/**
|
|
50
|
+
* Memory hygiene and zeroization utilities for sensitive cryptographic material
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
const textEncoder = new TextEncoder()
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Overwrite byte array contents with zeros
|
|
57
|
+
*/
|
|
58
|
+
export function zeroizeBytes(buffer: Uint8Array | number[]): void {
|
|
59
|
+
if (buffer instanceof Uint8Array) {
|
|
60
|
+
buffer.fill(0)
|
|
61
|
+
} else if (Array.isArray(buffer)) {
|
|
62
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
63
|
+
buffer[i] = 0
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Execute a callback with a byte buffer and guarantee zeroization upon completion
|
|
70
|
+
*/
|
|
71
|
+
export async function withSecureBytes<T>(
|
|
72
|
+
bytes: Uint8Array,
|
|
73
|
+
fn: (bytes: Uint8Array) => Promise<T> | T
|
|
74
|
+
): Promise<T> {
|
|
75
|
+
try {
|
|
76
|
+
return await fn(bytes)
|
|
77
|
+
} finally {
|
|
78
|
+
zeroizeBytes(bytes)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Execute a callback with a secret string, ensuring temporary byte buffers are cleared
|
|
84
|
+
*/
|
|
85
|
+
export async function withSecureString<T>(
|
|
86
|
+
secret: string,
|
|
87
|
+
fn: (cleanSecret: string) => Promise<T> | T
|
|
88
|
+
): Promise<T> {
|
|
89
|
+
const bytes = textEncoder.encode(secret)
|
|
90
|
+
try {
|
|
91
|
+
return await fn(secret)
|
|
92
|
+
} finally {
|
|
93
|
+
zeroizeBytes(bytes)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Constant-time comparison of two byte arrays or strings to prevent timing attacks
|
|
99
|
+
*/
|
|
100
|
+
export function constantTimeCompare(
|
|
101
|
+
a: Uint8Array | string,
|
|
102
|
+
b: Uint8Array | string
|
|
103
|
+
): boolean {
|
|
104
|
+
const bytesA = typeof a === 'string' ? textEncoder.encode(a) : a
|
|
105
|
+
const bytesB = typeof b === 'string' ? textEncoder.encode(b) : b
|
|
106
|
+
|
|
107
|
+
let result = bytesA.length === bytesB.length ? 0 : 1
|
|
108
|
+
const len = Math.min(bytesA.length, bytesB.length)
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < len; i++) {
|
|
111
|
+
const byteA = bytesA[i] ?? 0
|
|
112
|
+
const byteB = bytesB[i] ?? 0
|
|
113
|
+
result |= byteA ^ byteB
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Zeroize temporary buffers if they were converted from strings
|
|
117
|
+
if (typeof a === 'string') zeroizeBytes(bytesA)
|
|
118
|
+
if (typeof b === 'string') zeroizeBytes(bytesB)
|
|
119
|
+
|
|
120
|
+
return result === 0
|
|
121
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Powered by Knish.IO: Connecting a Decentralized World
|
|
3
|
+
|
|
4
|
+
Please visit https://github.com/WishKnish/KnishIO-Client-TS for information.
|
|
5
|
+
|
|
6
|
+
License: https://github.com/WishKnish/KnishIO-Client-TS/blob/master/LICENSE
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import MutationProposeMolecule from './MutationProposeMolecule'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Mutation for replenishing a non-finite token supply.
|
|
13
|
+
*
|
|
14
|
+
* The JS SDK submits replenishment through `MutationProposeMolecule` directly
|
|
15
|
+
* (KnishIOClient.js:2226-2230), but that class is abstract here because it declares
|
|
16
|
+
* `fillMolecule`. This subclass exists to provide the concrete type; it inherits the identical
|
|
17
|
+
* `ProposeMolecule(molecule: $molecule)` document from the base, so the request on the wire is
|
|
18
|
+
* the same one the JS SDK sends.
|
|
19
|
+
*
|
|
20
|
+
* `fillMolecule` is intentionally a no-op: KnishIOClient.replenishToken builds the V-atom pair
|
|
21
|
+
* via Molecule.replenishToken, then signs and checks the molecule, before this mutation is
|
|
22
|
+
* constructed. There is nothing left to fill.
|
|
23
|
+
*/
|
|
24
|
+
export default class MutationReplenishToken extends MutationProposeMolecule {
|
|
25
|
+
override fillMolecule(): void {
|
|
26
|
+
// Intentionally empty — the molecule arrives fully built, signed and checked.
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/schemas/index.ts
CHANGED
|
@@ -117,7 +117,7 @@ export const MetaIdSchema = z.string()
|
|
|
117
117
|
.brand<'MetaId'>()
|
|
118
118
|
|
|
119
119
|
export const BatchIdSchema = z.string()
|
|
120
|
-
.
|
|
120
|
+
.regex(/^[0-9a-fA-F]{64}$/, 'Batch ID must be 64 hexadecimal characters')
|
|
121
121
|
.brand<'BatchId'>()
|
|
122
122
|
|
|
123
123
|
export const CellSlugSchema = z.string()
|
|
@@ -207,7 +207,8 @@ export const KnishIOClientConfigSchema = z.object({
|
|
|
207
207
|
socket: z.unknown().optional(),
|
|
208
208
|
serverSdkVersion: z.number().int().min(1).optional(),
|
|
209
209
|
logging: z.boolean().optional(),
|
|
210
|
-
defaultRequestPolicy: z.enum(['cache-first', 'cache-only', 'network-only', 'cache-and-network']).nullable().optional()
|
|
210
|
+
defaultRequestPolicy: z.enum(['cache-first', 'cache-only', 'network-only', 'cache-and-network']).nullable().optional(),
|
|
211
|
+
secretStorage: z.unknown().optional()
|
|
211
212
|
}).strict()
|
|
212
213
|
|
|
213
214
|
// =============================================================================
|
|
@@ -0,0 +1,156 @@
|
|
|
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 { ISecretStorageProvider, SecretStorageMetadata } from '@/types/storage'
|
|
50
|
+
import SecretStorageException from '@/exception/SecretStorageException'
|
|
51
|
+
import { withSecureString } from '@/libraries/secureMemory'
|
|
52
|
+
|
|
53
|
+
interface MemorySecretEntry {
|
|
54
|
+
secret: string
|
|
55
|
+
metadata: SecretStorageMetadata
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* In-memory secret storage provider
|
|
60
|
+
* Used for testing, headless runners, and backward-compatible fallback
|
|
61
|
+
*/
|
|
62
|
+
export default class MemorySecretStorageProvider implements ISecretStorageProvider {
|
|
63
|
+
public readonly providerType = 'memory'
|
|
64
|
+
private secrets: Map<string, MemorySecretEntry> = new Map()
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Memory storage is not hardware backed
|
|
68
|
+
*/
|
|
69
|
+
isHardwareBacked(): boolean {
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Memory storage is always available
|
|
75
|
+
*/
|
|
76
|
+
async isAvailable(): Promise<boolean> {
|
|
77
|
+
return true
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Store a secret in memory
|
|
82
|
+
*/
|
|
83
|
+
async storeSecret(
|
|
84
|
+
bundleHash: string,
|
|
85
|
+
secret: string,
|
|
86
|
+
options?: { label?: string; passphrase?: string }
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
if (!bundleHash) {
|
|
89
|
+
throw new SecretStorageException('Bundle hash cannot be empty')
|
|
90
|
+
}
|
|
91
|
+
if (!secret) {
|
|
92
|
+
throw new SecretStorageException('Secret cannot be empty')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const metadata: SecretStorageMetadata = {
|
|
96
|
+
bundleHash,
|
|
97
|
+
label: options?.label,
|
|
98
|
+
createdAt: Date.now(),
|
|
99
|
+
hardwareBacked: false,
|
|
100
|
+
providerType: this.providerType
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.secrets.set(bundleHash, { secret, metadata })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Retrieve a secret from memory
|
|
108
|
+
*/
|
|
109
|
+
async retrieveSecret(bundleHash: string): Promise<string | null> {
|
|
110
|
+
const entry = this.secrets.get(bundleHash)
|
|
111
|
+
return entry ? entry.secret : null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Delete a stored secret
|
|
116
|
+
*/
|
|
117
|
+
async deleteSecret(bundleHash: string): Promise<boolean> {
|
|
118
|
+
return this.secrets.delete(bundleHash)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check if a secret exists
|
|
123
|
+
*/
|
|
124
|
+
async hasSecret(bundleHash: string): Promise<boolean> {
|
|
125
|
+
return this.secrets.has(bundleHash)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* List all stored secret metadata
|
|
130
|
+
*/
|
|
131
|
+
async listSecrets(): Promise<SecretStorageMetadata[]> {
|
|
132
|
+
return Array.from(this.secrets.values()).map(entry => ({ ...entry.metadata }))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Execute callback with unwrapped secret and ensure cleanup
|
|
137
|
+
*/
|
|
138
|
+
async withSecret<T>(
|
|
139
|
+
bundleHash: string,
|
|
140
|
+
fn: (secret: string) => Promise<T> | T
|
|
141
|
+
): Promise<T> {
|
|
142
|
+
const entry = this.secrets.get(bundleHash)
|
|
143
|
+
if (!entry) {
|
|
144
|
+
throw SecretStorageException.notFound(bundleHash)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return withSecureString(entry.secret, fn)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Clear all secrets from memory
|
|
152
|
+
*/
|
|
153
|
+
clear(): void {
|
|
154
|
+
this.secrets.clear()
|
|
155
|
+
}
|
|
156
|
+
}
|