@optimystic/quereus-plugin-crypto 0.2.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.
- package/README.md +279 -0
- package/dist/index.d.ts +317 -0
- package/dist/index.js +474 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +42 -0
- package/dist/plugin.js +212 -0
- package/dist/plugin.js.map +1 -0
- package/package.json +71 -0
- package/src/crypto.ts +335 -0
- package/src/digest.ts +173 -0
- package/src/index.ts +33 -0
- package/src/plugin.ts +87 -0
- package/src/sign.ts +245 -0
- package/src/signature-valid.ts +279 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SignatureValid Function for Quereus
|
|
3
|
+
*
|
|
4
|
+
* Returns true if the ECC signature is valid for the given digest and public key.
|
|
5
|
+
* Uses @noble/curves for portable implementation.
|
|
6
|
+
* Compatible with React Native and all JS environments.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { secp256k1 } from '@noble/curves/secp256k1';
|
|
10
|
+
import { p256 } from '@noble/curves/nist';
|
|
11
|
+
import { ed25519 } from '@noble/curves/ed25519';
|
|
12
|
+
import { hexToBytes } from '@noble/curves/abstract/utils';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Supported elliptic curve types
|
|
16
|
+
*/
|
|
17
|
+
export type CurveType = 'secp256k1' | 'p256' | 'ed25519';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Input types that can be Uint8Array or hex string
|
|
21
|
+
*/
|
|
22
|
+
export type BytesInput = Uint8Array | string;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Options for signature verification
|
|
26
|
+
*/
|
|
27
|
+
export interface VerifyOptions {
|
|
28
|
+
/** Elliptic curve to use (default: secp256k1) */
|
|
29
|
+
curve?: CurveType;
|
|
30
|
+
/** Signature format (default: auto-detect) */
|
|
31
|
+
signatureFormat?: 'compact' | 'der' | 'raw';
|
|
32
|
+
/** Allow malleable signatures (default: false for ECDSA, true for EdDSA) */
|
|
33
|
+
allowMalleableSignatures?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Normalize input to Uint8Array
|
|
38
|
+
*/
|
|
39
|
+
function normalizeBytes(input: BytesInput): Uint8Array {
|
|
40
|
+
if (input instanceof Uint8Array) {
|
|
41
|
+
return input;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof input === 'string') {
|
|
45
|
+
return hexToBytes(input);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw new Error('Invalid input format - expected Uint8Array or hex string');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Auto-detect signature format based on length and curve
|
|
53
|
+
*/
|
|
54
|
+
function detectSignatureFormat(signature: Uint8Array, curve: CurveType): 'compact' | 'der' | 'raw' {
|
|
55
|
+
const length = signature.length;
|
|
56
|
+
|
|
57
|
+
if (curve === 'ed25519') {
|
|
58
|
+
return 'raw'; // Ed25519 signatures are always 64 bytes
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// For ECDSA curves (secp256k1, p256)
|
|
62
|
+
if (length === 64) {
|
|
63
|
+
return 'compact'; // r + s concatenated (32 + 32 bytes)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (length >= 70 && length <= 72 && signature[0] === 0x30) {
|
|
67
|
+
return 'der'; // DER encoding starts with 0x30
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Default to compact for shorter signatures
|
|
71
|
+
return 'compact';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Parse signature based on format and curve
|
|
76
|
+
*/
|
|
77
|
+
function parseSignature(signature: Uint8Array, format: 'compact' | 'der' | 'raw', curve: CurveType): any {
|
|
78
|
+
if (curve === 'ed25519') {
|
|
79
|
+
// Ed25519 signatures are always raw 64-byte format
|
|
80
|
+
return signature;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// For ECDSA curves
|
|
84
|
+
switch (format) {
|
|
85
|
+
case 'compact':
|
|
86
|
+
if (curve === 'secp256k1') {
|
|
87
|
+
return secp256k1.Signature.fromCompact(signature);
|
|
88
|
+
} else if (curve === 'p256') {
|
|
89
|
+
return p256.Signature.fromCompact(signature);
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
|
|
93
|
+
case 'der':
|
|
94
|
+
if (curve === 'secp256k1') {
|
|
95
|
+
return secp256k1.Signature.fromDER(signature);
|
|
96
|
+
} else if (curve === 'p256') {
|
|
97
|
+
return p256.Signature.fromDER(signature);
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
case 'raw':
|
|
102
|
+
// Treat as compact for ECDSA
|
|
103
|
+
if (curve === 'secp256k1') {
|
|
104
|
+
return secp256k1.Signature.fromCompact(signature);
|
|
105
|
+
} else if (curve === 'p256') {
|
|
106
|
+
return p256.Signature.fromCompact(signature);
|
|
107
|
+
}
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
throw new Error(`Failed to parse signature for curve ${curve} with format ${format}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Verify if an ECC signature is valid
|
|
116
|
+
*
|
|
117
|
+
* @param {BytesInput} digest - The digest/hash that was signed
|
|
118
|
+
* @param {BytesInput} signature - The signature to verify
|
|
119
|
+
* @param {BytesInput} publicKey - The public key to verify against
|
|
120
|
+
* @param {VerifyOptions} [options] - Optional verification parameters
|
|
121
|
+
* @returns {boolean} True if the signature is valid, false otherwise
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* // Basic usage with secp256k1
|
|
126
|
+
* const isValid = SignatureValid(digest, signature, publicKey);
|
|
127
|
+
*
|
|
128
|
+
* // With specific curve
|
|
129
|
+
* const isValid = SignatureValid(digest, signature, publicKey, {
|
|
130
|
+
* curve: 'p256'
|
|
131
|
+
* });
|
|
132
|
+
*
|
|
133
|
+
* // With specific signature format
|
|
134
|
+
* const isValid = SignatureValid(digest, signature, publicKey, {
|
|
135
|
+
* curve: 'secp256k1',
|
|
136
|
+
* signatureFormat: 'der'
|
|
137
|
+
* });
|
|
138
|
+
*
|
|
139
|
+
* // Allow malleable signatures
|
|
140
|
+
* const isValid = SignatureValid(digest, signature, publicKey, {
|
|
141
|
+
* allowMalleableSignatures: true
|
|
142
|
+
* });
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export function SignatureValid(
|
|
146
|
+
digest: BytesInput,
|
|
147
|
+
signature: BytesInput,
|
|
148
|
+
publicKey: BytesInput,
|
|
149
|
+
options: VerifyOptions = {}
|
|
150
|
+
): boolean {
|
|
151
|
+
try {
|
|
152
|
+
const {
|
|
153
|
+
curve = 'secp256k1',
|
|
154
|
+
signatureFormat,
|
|
155
|
+
allowMalleableSignatures,
|
|
156
|
+
} = options;
|
|
157
|
+
|
|
158
|
+
const normalizedDigest = normalizeBytes(digest);
|
|
159
|
+
const normalizedSignature = normalizeBytes(signature);
|
|
160
|
+
const normalizedPublicKey = normalizeBytes(publicKey);
|
|
161
|
+
|
|
162
|
+
// Auto-detect signature format if not specified
|
|
163
|
+
const detectedFormat = signatureFormat || detectSignatureFormat(normalizedSignature, curve);
|
|
164
|
+
|
|
165
|
+
// Parse the signature
|
|
166
|
+
const parsedSignature = parseSignature(normalizedSignature, detectedFormat, curve);
|
|
167
|
+
|
|
168
|
+
// Set up verification options
|
|
169
|
+
const verifyOptions: any = {};
|
|
170
|
+
|
|
171
|
+
// Handle malleable signatures for ECDSA curves
|
|
172
|
+
if (curve !== 'ed25519' && allowMalleableSignatures !== undefined) {
|
|
173
|
+
verifyOptions.lowS = !allowMalleableSignatures;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Verify the signature
|
|
177
|
+
switch (curve) {
|
|
178
|
+
case 'secp256k1':
|
|
179
|
+
return secp256k1.verify(parsedSignature, normalizedDigest, normalizedPublicKey, verifyOptions);
|
|
180
|
+
|
|
181
|
+
case 'p256':
|
|
182
|
+
return p256.verify(parsedSignature, normalizedDigest, normalizedPublicKey, verifyOptions);
|
|
183
|
+
|
|
184
|
+
case 'ed25519':
|
|
185
|
+
return ed25519.verify(parsedSignature, normalizedDigest, normalizedPublicKey);
|
|
186
|
+
|
|
187
|
+
default:
|
|
188
|
+
throw new Error(`Unsupported curve: ${curve}`);
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
// If any error occurs during verification, the signature is invalid
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Convenience functions for specific curves
|
|
198
|
+
*/
|
|
199
|
+
SignatureValid.secp256k1 = (
|
|
200
|
+
digest: BytesInput,
|
|
201
|
+
signature: BytesInput,
|
|
202
|
+
publicKey: BytesInput,
|
|
203
|
+
options: Omit<VerifyOptions, 'curve'> = {}
|
|
204
|
+
): boolean => {
|
|
205
|
+
return SignatureValid(digest, signature, publicKey, { ...options, curve: 'secp256k1' });
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
SignatureValid.p256 = (
|
|
209
|
+
digest: BytesInput,
|
|
210
|
+
signature: BytesInput,
|
|
211
|
+
publicKey: BytesInput,
|
|
212
|
+
options: Omit<VerifyOptions, 'curve'> = {}
|
|
213
|
+
): boolean => {
|
|
214
|
+
return SignatureValid(digest, signature, publicKey, { ...options, curve: 'p256' });
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
SignatureValid.ed25519 = (
|
|
218
|
+
digest: BytesInput,
|
|
219
|
+
signature: BytesInput,
|
|
220
|
+
publicKey: BytesInput,
|
|
221
|
+
options: Omit<VerifyOptions, 'curve'> = {}
|
|
222
|
+
): boolean => {
|
|
223
|
+
return SignatureValid(digest, signature, publicKey, { ...options, curve: 'ed25519' });
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Batch verify multiple signatures (more efficient for multiple verifications)
|
|
228
|
+
*/
|
|
229
|
+
SignatureValid.batch = (
|
|
230
|
+
verifications: Array<{
|
|
231
|
+
digest: BytesInput;
|
|
232
|
+
signature: BytesInput;
|
|
233
|
+
publicKey: BytesInput;
|
|
234
|
+
options?: VerifyOptions;
|
|
235
|
+
}>
|
|
236
|
+
): boolean[] => {
|
|
237
|
+
return verifications.map(({ digest, signature, publicKey, options }) =>
|
|
238
|
+
SignatureValid(digest, signature, publicKey, options)
|
|
239
|
+
);
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Verify and return detailed information about the verification
|
|
244
|
+
*/
|
|
245
|
+
SignatureValid.detailed = (
|
|
246
|
+
digest: BytesInput,
|
|
247
|
+
signature: BytesInput,
|
|
248
|
+
publicKey: BytesInput,
|
|
249
|
+
options: VerifyOptions = {}
|
|
250
|
+
): {
|
|
251
|
+
valid: boolean;
|
|
252
|
+
curve: CurveType;
|
|
253
|
+
signatureFormat: string;
|
|
254
|
+
error?: string;
|
|
255
|
+
} => {
|
|
256
|
+
const curve = options.curve || 'secp256k1';
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const normalizedSignature = normalizeBytes(signature);
|
|
260
|
+
const detectedFormat = options.signatureFormat || detectSignatureFormat(normalizedSignature, curve);
|
|
261
|
+
|
|
262
|
+
const valid = SignatureValid(digest, signature, publicKey, options);
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
valid,
|
|
266
|
+
curve,
|
|
267
|
+
signatureFormat: detectedFormat,
|
|
268
|
+
};
|
|
269
|
+
} catch (error) {
|
|
270
|
+
return {
|
|
271
|
+
valid: false,
|
|
272
|
+
curve,
|
|
273
|
+
signatureFormat: 'unknown',
|
|
274
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
export default SignatureValid;
|