@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
package/src/digest.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Digest Function for Quereus
|
|
3
|
+
*
|
|
4
|
+
* Computes the hash of all arguments combined.
|
|
5
|
+
* Uses SHA-256 from @noble/hashes for portable implementation.
|
|
6
|
+
* Compatible with React Native and all JS environments.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { sha256 } from '@noble/hashes/sha2';
|
|
10
|
+
import { sha512 } from '@noble/hashes/sha2';
|
|
11
|
+
import { blake3 } from '@noble/hashes/blake3';
|
|
12
|
+
import { concatBytes, utf8ToBytes } from '@noble/hashes/utils';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Hash algorithm options
|
|
16
|
+
*/
|
|
17
|
+
export type HashAlgorithm = 'sha256' | 'sha512' | 'blake3';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Input type for digest function - can be string, Uint8Array, or number
|
|
21
|
+
*/
|
|
22
|
+
export type DigestInput = string | Uint8Array | number | boolean | null | undefined;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Options for the digest function
|
|
26
|
+
*/
|
|
27
|
+
export interface DigestOptions {
|
|
28
|
+
/** Hash algorithm to use (default: sha256) */
|
|
29
|
+
algorithm?: HashAlgorithm;
|
|
30
|
+
/** Output format (default: uint8array) */
|
|
31
|
+
output?: 'uint8array' | 'hex';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Convert various input types to Uint8Array for hashing
|
|
36
|
+
*/
|
|
37
|
+
function inputToBytes(input: DigestInput): Uint8Array {
|
|
38
|
+
if (input === null || input === undefined) {
|
|
39
|
+
return new Uint8Array(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (typeof input === 'string') {
|
|
43
|
+
return utf8ToBytes(input);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (input instanceof Uint8Array) {
|
|
47
|
+
return input;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (typeof input === 'number') {
|
|
51
|
+
// Convert number to 8-byte big-endian representation
|
|
52
|
+
const buffer = new ArrayBuffer(8);
|
|
53
|
+
const view = new DataView(buffer);
|
|
54
|
+
view.setFloat64(0, input, false); // big-endian
|
|
55
|
+
return new Uint8Array(buffer);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (typeof input === 'boolean') {
|
|
59
|
+
return new Uint8Array([input ? 1 : 0]);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Fallback: convert to string then to bytes
|
|
63
|
+
return utf8ToBytes(String(input));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get hash function based on algorithm
|
|
68
|
+
*/
|
|
69
|
+
function getHashFunction(algorithm: HashAlgorithm): (data: Uint8Array) => Uint8Array {
|
|
70
|
+
switch (algorithm) {
|
|
71
|
+
case 'sha256':
|
|
72
|
+
return sha256;
|
|
73
|
+
case 'sha512':
|
|
74
|
+
return sha512;
|
|
75
|
+
case 'blake3':
|
|
76
|
+
return blake3;
|
|
77
|
+
default:
|
|
78
|
+
throw new Error(`Unsupported hash algorithm: ${algorithm}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Computes the hash of all arguments
|
|
84
|
+
*
|
|
85
|
+
* @param {...DigestInput} args - Variable number of arguments to hash
|
|
86
|
+
* @returns {Uint8Array} The computed hash as a Uint8Array
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* // Hash a string
|
|
91
|
+
* const hash1 = Digest('hello world');
|
|
92
|
+
*
|
|
93
|
+
* // Hash multiple arguments
|
|
94
|
+
* const hash2 = Digest('user:', 123, 'session');
|
|
95
|
+
*
|
|
96
|
+
* // Hash with specific algorithm
|
|
97
|
+
* const hash3 = Digest.withOptions({ algorithm: 'sha512' })('data1', 'data2');
|
|
98
|
+
*
|
|
99
|
+
* // Get hex output
|
|
100
|
+
* const hexHash = Digest.withOptions({ output: 'hex' })('hello');
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export function Digest(...args: DigestInput[]): Uint8Array {
|
|
104
|
+
return DigestWithOptions({ algorithm: 'sha256', output: 'uint8array' }, ...args) as Uint8Array;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Digest function with custom options
|
|
109
|
+
*/
|
|
110
|
+
export function DigestWithOptions(options: DigestOptions, ...args: DigestInput[]): Uint8Array | string {
|
|
111
|
+
const algorithm = options.algorithm || 'sha256';
|
|
112
|
+
const output = options.output || 'uint8array';
|
|
113
|
+
|
|
114
|
+
// Convert all arguments to bytes and concatenate
|
|
115
|
+
const byteArrays = args.map(inputToBytes);
|
|
116
|
+
const combined = concatBytes(...byteArrays);
|
|
117
|
+
|
|
118
|
+
// Hash the combined data
|
|
119
|
+
const hashFunction = getHashFunction(algorithm);
|
|
120
|
+
const hash = hashFunction(combined);
|
|
121
|
+
|
|
122
|
+
// Return in requested format
|
|
123
|
+
if (output === 'hex') {
|
|
124
|
+
return Array.from(hash)
|
|
125
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
126
|
+
.join('');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return hash;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Create a digest function with preset options
|
|
134
|
+
*/
|
|
135
|
+
Digest.withOptions = (options: DigestOptions) => {
|
|
136
|
+
return (...args: DigestInput[]) => DigestWithOptions(options, ...args);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Convenience functions for specific algorithms
|
|
141
|
+
*/
|
|
142
|
+
Digest.sha256 = (...args: DigestInput[]): Uint8Array => {
|
|
143
|
+
return DigestWithOptions({ algorithm: 'sha256' }, ...args) as Uint8Array;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
Digest.sha512 = (...args: DigestInput[]): Uint8Array => {
|
|
147
|
+
return DigestWithOptions({ algorithm: 'sha512' }, ...args) as Uint8Array;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
Digest.blake3 = (...args: DigestInput[]): Uint8Array => {
|
|
151
|
+
return DigestWithOptions({ algorithm: 'blake3' }, ...args) as Uint8Array;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Hex output variants
|
|
156
|
+
*/
|
|
157
|
+
Digest.hex = (...args: DigestInput[]): string => {
|
|
158
|
+
return DigestWithOptions({ algorithm: 'sha256', output: 'hex' }, ...args) as string;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
Digest.sha256Hex = (...args: DigestInput[]): string => {
|
|
162
|
+
return DigestWithOptions({ algorithm: 'sha256', output: 'hex' }, ...args) as string;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
Digest.sha512Hex = (...args: DigestInput[]): string => {
|
|
166
|
+
return DigestWithOptions({ algorithm: 'sha512', output: 'hex' }, ...args) as string;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
Digest.blake3Hex = (...args: DigestInput[]): string => {
|
|
170
|
+
return DigestWithOptions({ algorithm: 'blake3', output: 'hex' }, ...args) as string;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export default Digest;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quereus Crypto Functions Plugin
|
|
3
|
+
*
|
|
4
|
+
* Provides cryptographic functions for SQL queries and ES modules:
|
|
5
|
+
* - digest: Hash functions (SHA-256, SHA-512, BLAKE3) with base64url encoding
|
|
6
|
+
* - sign: ECC signature generation (secp256k1, P-256, Ed25519)
|
|
7
|
+
* - verify: ECC signature verification
|
|
8
|
+
* - hashMod: Hash with modulo for fixed-size outputs
|
|
9
|
+
* - randomBytes: Generate cryptographically secure random bytes
|
|
10
|
+
*
|
|
11
|
+
* All functions use base64url encoding by default for SQL compatibility.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Export idiomatic lowercase functions (primary API)
|
|
15
|
+
export {
|
|
16
|
+
digest,
|
|
17
|
+
sign,
|
|
18
|
+
verify,
|
|
19
|
+
hashMod,
|
|
20
|
+
randomBytes,
|
|
21
|
+
generatePrivateKey,
|
|
22
|
+
getPublicKey,
|
|
23
|
+
type HashAlgorithm,
|
|
24
|
+
type CurveType,
|
|
25
|
+
type Encoding,
|
|
26
|
+
} from './crypto.js';
|
|
27
|
+
|
|
28
|
+
// Legacy exports for backwards compatibility (if needed)
|
|
29
|
+
export { Digest, type DigestInput, type DigestOptions } from './digest.js';
|
|
30
|
+
export { Sign, type PrivateKeyInput, type SignOptions } from './sign.js';
|
|
31
|
+
export { SignatureValid, type BytesInput, type VerifyOptions } from './signature-valid.js';
|
|
32
|
+
|
|
33
|
+
|
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quereus Plugin Entry Point for Crypto Functions
|
|
3
|
+
*
|
|
4
|
+
* This module provides the plugin registration following Quereus 0.4.5 format.
|
|
5
|
+
* All metadata is in package.json - no manifest export needed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Database, FunctionFlags, SqlValue } from '@quereus/quereus';
|
|
9
|
+
import { digest, sign, verify, hashMod, randomBytes } from './crypto.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Plugin registration function
|
|
13
|
+
* This is called by Quereus when the plugin is loaded
|
|
14
|
+
*/
|
|
15
|
+
export default function register(_db: Database, _config: Record<string, SqlValue> = {}) {
|
|
16
|
+
// Register crypto functions with Quereus
|
|
17
|
+
const functions = [
|
|
18
|
+
{
|
|
19
|
+
schema: {
|
|
20
|
+
name: 'digest',
|
|
21
|
+
numArgs: -1, // Variable arguments: data, algorithm?, inputEncoding?, outputEncoding?
|
|
22
|
+
flags: 1 as FunctionFlags, // UTF8
|
|
23
|
+
returnType: { typeClass: 'scalar' as const, sqlType: 'TEXT' },
|
|
24
|
+
implementation: (...args: SqlValue[]) => {
|
|
25
|
+
const [data, algorithm = 'sha256', inputEncoding = 'base64url', outputEncoding = 'base64url'] = args;
|
|
26
|
+
return digest(data as string, algorithm as any, inputEncoding as any, outputEncoding as any);
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
schema: {
|
|
32
|
+
name: 'sign',
|
|
33
|
+
numArgs: -1, // Variable arguments: data, privateKey, curve?, inputEncoding?, keyEncoding?, outputEncoding?
|
|
34
|
+
flags: 1 as FunctionFlags,
|
|
35
|
+
returnType: { typeClass: 'scalar' as const, sqlType: 'TEXT' },
|
|
36
|
+
implementation: (...args: SqlValue[]) => {
|
|
37
|
+
const [data, privateKey, curve = 'secp256k1', inputEncoding = 'base64url', keyEncoding = 'base64url', outputEncoding = 'base64url'] = args;
|
|
38
|
+
return sign(data as string, privateKey as string, curve as any, inputEncoding as any, keyEncoding as any, outputEncoding as any);
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
schema: {
|
|
44
|
+
name: 'verify',
|
|
45
|
+
numArgs: -1, // Variable arguments: data, signature, publicKey, curve?, inputEncoding?, sigEncoding?, keyEncoding?
|
|
46
|
+
flags: 1 as FunctionFlags,
|
|
47
|
+
returnType: { typeClass: 'scalar' as const, sqlType: 'INTEGER' },
|
|
48
|
+
implementation: (...args: SqlValue[]) => {
|
|
49
|
+
const [data, signature, publicKey, curve = 'secp256k1', inputEncoding = 'base64url', sigEncoding = 'base64url', keyEncoding = 'base64url'] = args;
|
|
50
|
+
const result = verify(data as string, signature as string, publicKey as string, curve as any, inputEncoding as any, sigEncoding as any, keyEncoding as any);
|
|
51
|
+
return result ? 1 : 0;
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
schema: {
|
|
57
|
+
name: 'hash_mod',
|
|
58
|
+
numArgs: -1, // Variable arguments: data, bits, algorithm?, inputEncoding?
|
|
59
|
+
flags: 1 as FunctionFlags,
|
|
60
|
+
returnType: { typeClass: 'scalar' as const, sqlType: 'INTEGER' },
|
|
61
|
+
implementation: (...args: SqlValue[]) => {
|
|
62
|
+
const [data, bits, algorithm = 'sha256', inputEncoding = 'base64url'] = args;
|
|
63
|
+
return hashMod(data as string, bits as number, algorithm as any, inputEncoding as any);
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
schema: {
|
|
69
|
+
name: 'random_bytes',
|
|
70
|
+
numArgs: -1, // Variable arguments: bits?, encoding?
|
|
71
|
+
flags: 1 as FunctionFlags,
|
|
72
|
+
returnType: { typeClass: 'scalar' as const, sqlType: 'TEXT' },
|
|
73
|
+
implementation: (...args: SqlValue[]) => {
|
|
74
|
+
const [bits = 256, encoding = 'base64url'] = args;
|
|
75
|
+
return randomBytes(bits as number, encoding as any);
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
functions,
|
|
83
|
+
vtables: [],
|
|
84
|
+
collations: [],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
package/src/sign.ts
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sign Function for Quereus
|
|
3
|
+
*
|
|
4
|
+
* Returns the signature for the given payload using an ECC private key.
|
|
5
|
+
* Uses secp256k1 from @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 { bytesToHex, hexToBytes } from '@noble/curves/abstract/utils';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Supported elliptic curve types
|
|
16
|
+
*/
|
|
17
|
+
export type CurveType = 'secp256k1' | 'p256' | 'ed25519';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Private key input - can be Uint8Array, hex string, or bigint
|
|
21
|
+
*/
|
|
22
|
+
export type PrivateKeyInput = Uint8Array | string | bigint;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Digest input - can be Uint8Array or hex string
|
|
26
|
+
*/
|
|
27
|
+
export type DigestInput = Uint8Array | string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Signature output format options
|
|
31
|
+
*/
|
|
32
|
+
export type SignatureFormat = 'uint8array' | 'hex' | 'compact' | 'der';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Options for the Sign function
|
|
36
|
+
*/
|
|
37
|
+
export interface SignOptions {
|
|
38
|
+
/** Elliptic curve to use (default: secp256k1) */
|
|
39
|
+
curve?: CurveType;
|
|
40
|
+
/** Output format for signature (default: uint8array) */
|
|
41
|
+
format?: SignatureFormat;
|
|
42
|
+
/** Additional entropy for signatures (hedged signatures) */
|
|
43
|
+
extraEntropy?: boolean | Uint8Array;
|
|
44
|
+
/** Use low-S canonical signatures (default: true) */
|
|
45
|
+
lowS?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Normalize private key input to Uint8Array
|
|
50
|
+
*/
|
|
51
|
+
function normalizePrivateKey(privateKey: PrivateKeyInput): Uint8Array {
|
|
52
|
+
if (privateKey instanceof Uint8Array) {
|
|
53
|
+
return privateKey;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (typeof privateKey === 'string') {
|
|
57
|
+
// Assume hex string
|
|
58
|
+
return hexToBytes(privateKey);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof privateKey === 'bigint') {
|
|
62
|
+
// Convert bigint to 32-byte array (for secp256k1/p256)
|
|
63
|
+
const hex = privateKey.toString(16).padStart(64, '0');
|
|
64
|
+
return hexToBytes(hex);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
throw new Error('Invalid private key format');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Normalize digest input to Uint8Array
|
|
72
|
+
*/
|
|
73
|
+
function normalizeDigest(digest: DigestInput): Uint8Array {
|
|
74
|
+
if (digest instanceof Uint8Array) {
|
|
75
|
+
return digest;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (typeof digest === 'string') {
|
|
79
|
+
return hexToBytes(digest);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
throw new Error('Invalid digest format');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Format signature based on requested format
|
|
87
|
+
*/
|
|
88
|
+
function formatSignature(signature: any, format: SignatureFormat, curve: CurveType): Uint8Array | string {
|
|
89
|
+
switch (format) {
|
|
90
|
+
case 'uint8array':
|
|
91
|
+
if (curve === 'ed25519') {
|
|
92
|
+
return signature;
|
|
93
|
+
}
|
|
94
|
+
return signature.toCompactRawBytes();
|
|
95
|
+
|
|
96
|
+
case 'hex':
|
|
97
|
+
if (curve === 'ed25519') {
|
|
98
|
+
return bytesToHex(signature);
|
|
99
|
+
}
|
|
100
|
+
return signature.toCompactHex();
|
|
101
|
+
|
|
102
|
+
case 'compact':
|
|
103
|
+
if (curve === 'ed25519') {
|
|
104
|
+
return signature;
|
|
105
|
+
}
|
|
106
|
+
return signature.toCompactRawBytes();
|
|
107
|
+
|
|
108
|
+
case 'der':
|
|
109
|
+
if (curve === 'ed25519') {
|
|
110
|
+
throw new Error('DER format not supported for ed25519');
|
|
111
|
+
}
|
|
112
|
+
// Note: DER encoding would require additional implementation
|
|
113
|
+
// For now, fall back to compact
|
|
114
|
+
return signature.toCompactRawBytes();
|
|
115
|
+
|
|
116
|
+
default:
|
|
117
|
+
throw new Error(`Unsupported signature format: ${format}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Sign a digest using the specified private key and curve
|
|
123
|
+
*
|
|
124
|
+
* @param {DigestInput} digest - The digest/hash to sign
|
|
125
|
+
* @param {PrivateKeyInput} privateKey - The private key to use for signing
|
|
126
|
+
* @param {SignOptions} [options] - Optional signing parameters
|
|
127
|
+
* @returns {Uint8Array | string} The signature in the requested format
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* // Basic usage with secp256k1
|
|
132
|
+
* const digest = new Uint8Array(32).fill(1); // Your hash here
|
|
133
|
+
* const privateKey = 'a'.repeat(64); // Your private key hex
|
|
134
|
+
* const signature = Sign(digest, privateKey);
|
|
135
|
+
*
|
|
136
|
+
* // With specific curve and format
|
|
137
|
+
* const sig = Sign(digest, privateKey, {
|
|
138
|
+
* curve: 'p256',
|
|
139
|
+
* format: 'hex'
|
|
140
|
+
* });
|
|
141
|
+
*
|
|
142
|
+
* // With hedged signatures for extra security
|
|
143
|
+
* const hedgedSig = Sign(digest, privateKey, {
|
|
144
|
+
* extraEntropy: true
|
|
145
|
+
* });
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
export function Sign(
|
|
149
|
+
digest: DigestInput,
|
|
150
|
+
privateKey: PrivateKeyInput,
|
|
151
|
+
options: SignOptions = {}
|
|
152
|
+
): Uint8Array | string {
|
|
153
|
+
const {
|
|
154
|
+
curve = 'secp256k1',
|
|
155
|
+
format = 'uint8array',
|
|
156
|
+
extraEntropy = false,
|
|
157
|
+
lowS = true,
|
|
158
|
+
} = options;
|
|
159
|
+
|
|
160
|
+
const normalizedDigest = normalizeDigest(digest);
|
|
161
|
+
const normalizedPrivateKey = normalizePrivateKey(privateKey);
|
|
162
|
+
|
|
163
|
+
let signature: any;
|
|
164
|
+
|
|
165
|
+
switch (curve) {
|
|
166
|
+
case 'secp256k1': {
|
|
167
|
+
const signOptions: any = { lowS };
|
|
168
|
+
if (extraEntropy) {
|
|
169
|
+
signOptions.extraEntropy = extraEntropy;
|
|
170
|
+
}
|
|
171
|
+
signature = secp256k1.sign(normalizedDigest, normalizedPrivateKey, signOptions);
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
case 'p256': {
|
|
176
|
+
const signOptions: any = { lowS };
|
|
177
|
+
if (extraEntropy) {
|
|
178
|
+
signOptions.extraEntropy = extraEntropy;
|
|
179
|
+
}
|
|
180
|
+
signature = p256.sign(normalizedDigest, normalizedPrivateKey, signOptions);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
case 'ed25519': {
|
|
185
|
+
signature = ed25519.sign(normalizedDigest, normalizedPrivateKey);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
default:
|
|
190
|
+
throw new Error(`Unsupported curve: ${curve}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return formatSignature(signature, format, curve);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Convenience functions for specific curves
|
|
198
|
+
*/
|
|
199
|
+
Sign.secp256k1 = (digest: DigestInput, privateKey: PrivateKeyInput, options: Omit<SignOptions, 'curve'> = {}) => {
|
|
200
|
+
return Sign(digest, privateKey, { ...options, curve: 'secp256k1' });
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
Sign.p256 = (digest: DigestInput, privateKey: PrivateKeyInput, options: Omit<SignOptions, 'curve'> = {}) => {
|
|
204
|
+
return Sign(digest, privateKey, { ...options, curve: 'p256' });
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
Sign.ed25519 = (digest: DigestInput, privateKey: PrivateKeyInput, options: Omit<SignOptions, 'curve'> = {}) => {
|
|
208
|
+
return Sign(digest, privateKey, { ...options, curve: 'ed25519' });
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Generate a random private key for the specified curve
|
|
213
|
+
*/
|
|
214
|
+
Sign.generatePrivateKey = (curve: CurveType = 'secp256k1'): Uint8Array => {
|
|
215
|
+
switch (curve) {
|
|
216
|
+
case 'secp256k1':
|
|
217
|
+
return secp256k1.utils.randomPrivateKey();
|
|
218
|
+
case 'p256':
|
|
219
|
+
return p256.utils.randomPrivateKey();
|
|
220
|
+
case 'ed25519':
|
|
221
|
+
return ed25519.utils.randomPrivateKey();
|
|
222
|
+
default:
|
|
223
|
+
throw new Error(`Unsupported curve: ${curve}`);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get the public key for a given private key and curve
|
|
229
|
+
*/
|
|
230
|
+
Sign.getPublicKey = (privateKey: PrivateKeyInput, curve: CurveType = 'secp256k1'): Uint8Array => {
|
|
231
|
+
const normalizedPrivateKey = normalizePrivateKey(privateKey);
|
|
232
|
+
|
|
233
|
+
switch (curve) {
|
|
234
|
+
case 'secp256k1':
|
|
235
|
+
return secp256k1.getPublicKey(normalizedPrivateKey);
|
|
236
|
+
case 'p256':
|
|
237
|
+
return p256.getPublicKey(normalizedPrivateKey);
|
|
238
|
+
case 'ed25519':
|
|
239
|
+
return ed25519.getPublicKey(normalizedPrivateKey);
|
|
240
|
+
default:
|
|
241
|
+
throw new Error(`Unsupported curve: ${curve}`);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export default Sign;
|